Skip to content

Commit e90df06

Browse files
committed
session: migration to populate group ID to session ID indexes
This commit adds a migration to the session store to back-fill the new session ID to group ID and group ID to session IDs indexes.
1 parent a802759 commit e90df06

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

session/metadata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/lightninglabs/lightning-terminal/session/migration1"
9+
"github.com/lightninglabs/lightning-terminal/session/migration2"
910
"go.etcd.io/bbolt"
1011
)
1112

@@ -37,6 +38,7 @@ var (
3738
tx, time.Now,
3839
)
3940
},
41+
migration2.MigrateSessionIDToGroupIndex,
4042
}
4143

4244
latestDBVersion = uint32(len(dbVersions))
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package migration2
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
"fmt"
7+
8+
"go.etcd.io/bbolt"
9+
)
10+
11+
var (
12+
// sessionBucketKey is the top level bucket where we can find all
13+
// information about sessions. These sessions are indexed by their
14+
// public key.
15+
//
16+
// The session bucket has the following structure:
17+
// session -> <key> -> <serialised session>
18+
// -> id-index -> <session-id> -> key -> <session key>
19+
// -> group -> <group-ID>
20+
// -> group-id-index -> <group-id> -> session-id -> sequence -> <session-id>
21+
sessionBucketKey = []byte("session")
22+
23+
// idIndexKey is the key used to define the id-index sub-bucket within
24+
// the main session bucket. This bucket will be used to store the
25+
// mapping from session ID to various other fields.
26+
idIndexKey = []byte("id-index")
27+
28+
// sessionKeyKey is the key used within the id-index bucket to store the
29+
// session key (serialised local public key) associated with the given
30+
// session ID.
31+
sessionKeyKey = []byte("key")
32+
33+
// groupIDKey is the key used within the id-index bucket to store the
34+
// group ID associated with the given session ID.
35+
groupIDKey = []byte("group")
36+
37+
// groupIDIndexKey is the key used to define the group-id-index
38+
// sub-bucket within the main session bucket. This bucket will be used
39+
// to store the mapping from group ID to various other fields.
40+
groupIDIndexKey = []byte("group-id-index")
41+
42+
// sessionIDKey is a key used in the group-id-index under a sub-bucket
43+
// defined by a specific group ID. It will be used to store the session
44+
// IDs associated with the given group ID.
45+
sessionIDKey = []byte("session-id")
46+
47+
// ErrDBInitErr is returned when a bucket that we expect to have been
48+
// set up during DB initialisation is not found.
49+
ErrDBInitErr = errors.New("db did not initialise properly")
50+
51+
// byteOrder is the default byte order we'll use for serialization
52+
// within the database.
53+
byteOrder = binary.BigEndian
54+
)
55+
56+
// MigrateSessionIDToGroupIndex back-fills the session ID to group index so that
57+
// it has an entry for all sessions that the session store is currently aware of.
58+
func MigrateSessionIDToGroupIndex(tx *bbolt.Tx) error {
59+
sessionBucket := tx.Bucket(sessionBucketKey)
60+
if sessionBucket == nil {
61+
return fmt.Errorf("session bucket not found")
62+
}
63+
64+
idIndexBkt := sessionBucket.Bucket(idIndexKey)
65+
if idIndexBkt == nil {
66+
return ErrDBInitErr
67+
}
68+
69+
groupIndexBkt := sessionBucket.Bucket(groupIDIndexKey)
70+
if groupIndexBkt == nil {
71+
return ErrDBInitErr
72+
}
73+
74+
// Collect all the index entries.
75+
return idIndexBkt.ForEach(func(sessionID, _ []byte) error {
76+
// This migration is done before the logic in LiT is added that
77+
// would allow groupIDs to differ from session IDs. And so all
78+
// this migration needs to do is add the current 1:1 mapping
79+
// from group ID to session ID and vice versa where group ID is
80+
// equal to the session ID.
81+
groupID := sessionID
82+
83+
// First we add the session ID to group ID mapping.
84+
sessionIDBkt := idIndexBkt.Bucket(sessionID)
85+
if sessionIDBkt == nil {
86+
return fmt.Errorf("unexpected non-bucket entry in " +
87+
"the id-index bucket")
88+
}
89+
90+
err := sessionIDBkt.Put(groupIDKey, groupID)
91+
if err != nil {
92+
return err
93+
}
94+
95+
// Now we will add the group ID to session ID mapping.
96+
groupIDBkt, err := groupIndexBkt.CreateBucketIfNotExists(
97+
groupID,
98+
)
99+
if err != nil {
100+
return err
101+
}
102+
103+
groupSessionIDBkt, err := groupIDBkt.CreateBucketIfNotExists(
104+
sessionIDKey,
105+
)
106+
if err != nil {
107+
return err
108+
}
109+
110+
nextSeq, err := groupSessionIDBkt.NextSequence()
111+
if err != nil {
112+
return err
113+
}
114+
var seqNoBytes [8]byte
115+
byteOrder.PutUint64(seqNoBytes[:], nextSeq)
116+
117+
return groupSessionIDBkt.Put(seqNoBytes[:], groupID[:])
118+
})
119+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package migration2
2+
3+
import (
4+
"testing"
5+
6+
"github.com/btcsuite/btcd/btcec/v2"
7+
"github.com/lightninglabs/lightning-terminal/session/migtest"
8+
"github.com/stretchr/testify/require"
9+
"go.etcd.io/bbolt"
10+
)
11+
12+
// ID represents the id of a session.
13+
type ID [4]byte
14+
15+
// TestMigrateSessionIDToGroupIDIndex tests that the
16+
// MigrateSessionIDToGroupIDIndex migration correctly back-fills the session ID
17+
// to group ID index along with the group ID to session ID index.
18+
func TestMigrateSessionIDToGroupIDIndex(t *testing.T) {
19+
t.Parallel()
20+
21+
// Make a few session IDs.
22+
sess1ID, sess1Key := newSessionID(t)
23+
sess2ID, sess2Key := newSessionID(t)
24+
sess3ID, sess3Key := newSessionID(t)
25+
26+
// Put together a sample session ID index DB based on the above.
27+
idIndexBefore := map[string]interface{}{
28+
string(sess1ID[:]): map[string]interface{}{
29+
string(sessionKeyKey): string(sess1Key),
30+
},
31+
string(sess2ID[:]): map[string]interface{}{
32+
string(sessionKeyKey): string(sess2Key),
33+
},
34+
string(sess3ID[:]): map[string]interface{}{
35+
string(sessionKeyKey): string(sess3Key),
36+
},
37+
}
38+
39+
// sessionDBBefore is what our session DB will look like before the
40+
// migration.
41+
sessionDBBefore := map[string]interface{}{
42+
string(idIndexKey): idIndexBefore,
43+
string(groupIDIndexKey): map[string]interface{}{},
44+
}
45+
46+
before := func(tx *bbolt.Tx) error {
47+
return migtest.RestoreDB(tx, sessionBucketKey, sessionDBBefore)
48+
}
49+
50+
// Put together what we expect the resulting id-index bucket to look
51+
// like after the migration.
52+
idIndexAfter := map[string]interface{}{
53+
string(sess1ID[:]): map[string]interface{}{
54+
string(sessionKeyKey): string(sess1Key),
55+
string(groupIDKey): string(sess1ID[:]),
56+
},
57+
string(sess2ID[:]): map[string]interface{}{
58+
string(sessionKeyKey): string(sess2Key),
59+
string(groupIDKey): string(sess2ID[:]),
60+
},
61+
string(sess3ID[:]): map[string]interface{}{
62+
string(sessionKeyKey): string(sess3Key),
63+
string(groupIDKey): string(sess3ID[:]),
64+
},
65+
}
66+
67+
// Put together what we expect the resulting group-ID-index bucket to
68+
// look like after the migration.
69+
groupIDIndexAfter := map[string]interface{}{
70+
string(sess1ID[:]): map[string]interface{}{
71+
string(sessionIDKey): map[string]interface{}{
72+
sequenceString(1): string(sess1ID[:]),
73+
},
74+
},
75+
string(sess2ID[:]): map[string]interface{}{
76+
string(sessionIDKey): map[string]interface{}{
77+
sequenceString(1): string(sess2ID[:]),
78+
},
79+
},
80+
string(sess3ID[:]): map[string]interface{}{
81+
string(sessionIDKey): map[string]interface{}{
82+
sequenceString(1): string(sess3ID[:]),
83+
},
84+
},
85+
}
86+
87+
// sessionDBAfter is what our session DB will look like after the
88+
// migration.
89+
sessionDBAfter := map[string]interface{}{
90+
string(idIndexKey): idIndexAfter,
91+
string(groupIDIndexKey): groupIDIndexAfter,
92+
}
93+
94+
after := func(tx *bbolt.Tx) error {
95+
return migtest.VerifyDB(tx, sessionBucketKey, sessionDBAfter)
96+
}
97+
98+
migtest.ApplyMigration(
99+
t, before, after, MigrateSessionIDToGroupIndex, false,
100+
)
101+
}
102+
103+
// newSessionID is a helper function that can be used to generate a new session
104+
// ID and key.
105+
func newSessionID(t *testing.T) (ID, []byte) {
106+
privateKey, err := btcec.NewPrivateKey()
107+
require.NoError(t, err)
108+
109+
key := privateKey.PubKey().SerializeCompressed()
110+
111+
var id ID
112+
copy(id[:], key)
113+
114+
return id, key
115+
}
116+
117+
func sequenceString(id uint64) string {
118+
var seqNoBytes [8]byte
119+
byteOrder.PutUint64(seqNoBytes[:], id)
120+
121+
return string(seqNoBytes[:])
122+
}

0 commit comments

Comments
 (0)