@@ -10,9 +10,12 @@ import (
10
10
"sort"
11
11
"time"
12
12
13
+ "github.com/btcsuite/btcd/btcec/v2"
13
14
"github.com/davecgh/go-spew/spew"
15
+ "github.com/lightninglabs/lightning-node-connect/mailbox"
14
16
"github.com/lightninglabs/lightning-terminal/accounts"
15
17
"github.com/lightninglabs/lightning-terminal/db/sqlc"
18
+ "github.com/lightningnetwork/lnd/fn"
16
19
"github.com/lightningnetwork/lnd/sqldb"
17
20
"github.com/pmezard/go-difflib/difflib"
18
21
"go.etcd.io/bbolt"
@@ -25,6 +28,105 @@ var (
25
28
"original session" )
26
29
)
27
30
31
+ // featureConfigEntry is a variant of a single session feature config, which
32
+ // can be inserted into an array to be compared deterministically.
33
+ type featureConfigEntry struct {
34
+ featureName string
35
+ config []byte
36
+ }
37
+
38
+ // deterministicSession is a variant of the Session struct without any struct
39
+ // methods, which represents the map in the Session as a list, so that it can be
40
+ // deterministically sorted for comparison during the kvdb to SQL migration.
41
+ type deterministicSession struct {
42
+ ID ID
43
+ Label string
44
+ State State
45
+ Type Type
46
+ Expiry time.Time
47
+ CreatedAt time.Time
48
+ RevokedAt time.Time
49
+ ServerAddr string
50
+ DevServer bool
51
+ MacaroonRootKey uint64
52
+ MacaroonRecipe * MacaroonRecipe
53
+ PairingSecret [mailbox .NumPassphraseEntropyBytes ]byte
54
+ LocalPrivateKey * btcec.PrivateKey
55
+ LocalPublicKey * btcec.PublicKey
56
+ RemotePublicKey * btcec.PublicKey
57
+ FeatureConfig []* featureConfigEntry
58
+ WithPrivacyMapper bool
59
+ PrivacyFlags PrivacyFlags
60
+
61
+ // GroupID is the Session ID of the very first Session in the linked
62
+ // group of sessions. If this is the very first session in the group
63
+ // then this will be the same as ID.
64
+ GroupID ID
65
+
66
+ // AccountID is an optional account that the session has been linked to.
67
+ AccountID fn.Option [accounts.AccountID ]
68
+ }
69
+
70
+ // newDeterministicSession creates a deterministicSession from a Session struct.
71
+ // This is used to compare the session in a deterministic way during the
72
+ // migration from the KV database to the SQL database.
73
+ func newDeterministicSession (sess * Session ) * deterministicSession {
74
+ var featuresConfig []* featureConfigEntry
75
+
76
+ // If a session has a feature config set, we'll convert it to an array
77
+ // so that we can sort it and compare it deterministically.
78
+ if sess .FeatureConfig != nil {
79
+ sessFC := * sess .FeatureConfig
80
+ featuresConfig = make ([]* featureConfigEntry , len (sessFC ))
81
+
82
+ i := 0
83
+ for featureName , config := range sessFC {
84
+ featuresConfig [i ] = & featureConfigEntry {
85
+ featureName : featureName ,
86
+ config : config ,
87
+ }
88
+
89
+ i ++
90
+ }
91
+
92
+ // Sort the feature config entries by their feature name, and
93
+ // by their config bytes if the feature names are the same.
94
+ sort .Slice (featuresConfig , func (i , j int ) bool {
95
+ iC := featuresConfig [i ]
96
+ jC := featuresConfig [j ]
97
+
98
+ if iC .featureName == jC .featureName {
99
+ return bytes .Compare (iC .config , jC .config ) < 0
100
+ }
101
+
102
+ return iC .featureName < jC .featureName
103
+ })
104
+ }
105
+
106
+ return & deterministicSession {
107
+ ID : sess .ID ,
108
+ Label : sess .Label ,
109
+ State : sess .State ,
110
+ Type : sess .Type ,
111
+ Expiry : sess .Expiry ,
112
+ CreatedAt : sess .CreatedAt ,
113
+ RevokedAt : sess .RevokedAt ,
114
+ ServerAddr : sess .ServerAddr ,
115
+ DevServer : sess .DevServer ,
116
+ MacaroonRootKey : sess .MacaroonRootKey ,
117
+ PairingSecret : sess .PairingSecret ,
118
+ LocalPrivateKey : sess .LocalPrivateKey ,
119
+ LocalPublicKey : sess .LocalPublicKey ,
120
+ RemotePublicKey : sess .RemotePublicKey ,
121
+ GroupID : sess .GroupID ,
122
+ AccountID : sess .AccountID ,
123
+ PrivacyFlags : sess .PrivacyFlags ,
124
+ MacaroonRecipe : sess .MacaroonRecipe ,
125
+ WithPrivacyMapper : sess .WithPrivacyMapper ,
126
+ FeatureConfig : featuresConfig ,
127
+ }
128
+ }
129
+
28
130
// MigrateSessionStoreToSQL runs the migration of all sessions from the KV
29
131
// database to the SQL database. The migration is done in a single transaction
30
132
// to ensure that all sessions are migrated or none at all.
@@ -149,13 +251,16 @@ func migrateSessionsToSQLAndValidate(ctx context.Context,
149
251
overrideSessionTimeZone (migratedSession )
150
252
overrideMacaroonRecipe (kvSession , migratedSession )
151
253
152
- if ! reflect .DeepEqual (kvSession , migratedSession ) {
254
+ dKvSession := newDeterministicSession (kvSession )
255
+ dMigratedSession := newDeterministicSession (migratedSession )
256
+
257
+ if ! reflect .DeepEqual (dKvSession , dMigratedSession ) {
153
258
diff := difflib.UnifiedDiff {
154
259
A : difflib .SplitLines (
155
- spew .Sdump (kvSession ),
260
+ spew .Sdump (dKvSession ),
156
261
),
157
262
B : difflib .SplitLines (
158
- spew .Sdump (migratedSession ),
263
+ spew .Sdump (dMigratedSession ),
159
264
),
160
265
FromFile : "Expected" ,
161
266
FromDate : "" ,
@@ -166,7 +271,7 @@ func migrateSessionsToSQLAndValidate(ctx context.Context,
166
271
diffText , _ := difflib .GetUnifiedDiffString (diff )
167
272
168
273
return fmt .Errorf ("%w: %v.\n %v" , ErrMigrationMismatch ,
169
- kvSession .ID , diffText )
274
+ dKvSession .ID , diffText )
170
275
}
171
276
}
172
277
0 commit comments