@@ -6,13 +6,16 @@ import (
6
6
"database/sql"
7
7
"errors"
8
8
"fmt"
9
+ "github.com/lightningnetwork/lnd/lntypes"
9
10
"math"
10
11
"reflect"
12
+ "sort"
11
13
"time"
12
14
13
15
"github.com/davecgh/go-spew/spew"
14
16
"github.com/lightninglabs/lightning-terminal/db/sqlc"
15
17
"github.com/lightningnetwork/lnd/kvdb"
18
+ "github.com/lightningnetwork/lnd/lnwire"
16
19
"github.com/pmezard/go-difflib/difflib"
17
20
)
18
21
@@ -23,6 +26,110 @@ var (
23
26
"original account" )
24
27
)
25
28
29
+ // deterministicPayment is a variant of a single account PaymentEntry, which
30
+ // can be inserted into an array to be compared deterministically.
31
+ type deterministicPayment struct {
32
+ paymentHash lntypes.Hash
33
+ paymentEntry * PaymentEntry
34
+ }
35
+
36
+ // deterministicAccount is a variant of the OffChainBalanceAccount struct
37
+ // without any struct methods, which represents the maps in the
38
+ // OffChainBalanceAccount as lists, so that they can be deterministically sorted
39
+ // for comparison during the kvdb to SQL migration.
40
+ type deterministicAccount struct {
41
+ // ID is the randomly generated account identifier.
42
+ ID AccountID
43
+
44
+ // Type is the account type.
45
+ Type AccountType
46
+
47
+ // InitialBalance stores the initial balance in millisatoshis and is
48
+ // never updated.
49
+ InitialBalance lnwire.MilliSatoshi
50
+
51
+ // CurrentBalance is the currently available balance of the account
52
+ // in millisatoshis that is updated every time an invoice is paid. This
53
+ // value can be negative (for example if the fees for a payment are
54
+ // larger than the estimate made when checking the balance and the
55
+ // account is close to zero value).
56
+ CurrentBalance int64
57
+
58
+ // LastUpdate keeps track of the last time the balance of the account
59
+ // was updated.
60
+ LastUpdate time.Time
61
+
62
+ // ExpirationDate is a specific date in the future after which the
63
+ // account is marked as expired. Can be set to zero for accounts that
64
+ // never expire.
65
+ ExpirationDate time.Time
66
+
67
+ // Invoices is a list of all invoices that are associated with the
68
+ // account.
69
+ Invoices []lntypes.Hash
70
+
71
+ // Payments is a list of all payments that are associated with the
72
+ // account and the last status we were aware of.
73
+ Payments []* deterministicPayment
74
+
75
+ // Label is an optional label that can be set for the account. If it is
76
+ // not empty then it must be unique.
77
+ Label string
78
+ }
79
+
80
+ // newDeterministicAccount creates a new deterministic account from the
81
+ // an OffChainBalanceAccount.
82
+ func newDeterministicAccount (
83
+ acct * OffChainBalanceAccount ) * deterministicAccount {
84
+
85
+ invoices := make ([]lntypes.Hash , len (acct .Invoices ))
86
+ payments := make ([]* deterministicPayment , len (acct .Payments ))
87
+
88
+ // First let's populate the invoices and payments slices with the
89
+ // invoices and payments from the account.
90
+ i := 0
91
+ for hash := range acct .Invoices {
92
+ invoices [i ] = hash
93
+ i ++
94
+ }
95
+
96
+ i = 0
97
+ for hash , paymentEntry := range acct .Payments {
98
+ payments [i ] = & deterministicPayment {
99
+ paymentHash : hash ,
100
+ paymentEntry : paymentEntry ,
101
+ }
102
+
103
+ i ++
104
+ }
105
+
106
+ // Next, let's sort the invoices and payments slices by their hashes to
107
+ // ensure deterministic ordering.
108
+ sort .Slice (invoices , func (i , j int ) bool {
109
+ return bytes .Compare (
110
+ invoices [i ][:], invoices [j ][:],
111
+ ) < 0
112
+ })
113
+
114
+ sort .Slice (payments , func (i , j int ) bool {
115
+ return bytes .Compare (
116
+ payments [i ].paymentHash [:], payments [j ].paymentHash [:],
117
+ ) < 0
118
+ })
119
+
120
+ return & deterministicAccount {
121
+ ID : acct .ID ,
122
+ Type : acct .Type ,
123
+ InitialBalance : acct .InitialBalance ,
124
+ CurrentBalance : acct .CurrentBalance ,
125
+ LastUpdate : acct .LastUpdate .UTC (),
126
+ Label : acct .Label ,
127
+ Invoices : invoices ,
128
+ Payments : payments ,
129
+ ExpirationDate : acct .ExpirationDate .UTC (),
130
+ }
131
+ }
132
+
26
133
// MigrateAccountStoreToSQL runs the migration of all accounts and indices from
27
134
// the KV database to the SQL database. The migration is done in a single
28
135
// transaction to ensure that all accounts are migrated or none at all.
@@ -79,13 +186,16 @@ func migrateAccountsToSQL(ctx context.Context, kvStore kvdb.Backend,
79
186
overrideAccountTimeZone (kvAccount )
80
187
overrideAccountTimeZone (migratedAccount )
81
188
82
- if ! reflect .DeepEqual (kvAccount , migratedAccount ) {
189
+ dKvAccount := newDeterministicAccount (kvAccount )
190
+ dMigratedAccount := newDeterministicAccount (migratedAccount )
191
+
192
+ if ! reflect .DeepEqual (dKvAccount , dMigratedAccount ) {
83
193
diff := difflib.UnifiedDiff {
84
194
A : difflib .SplitLines (
85
- spew .Sdump (kvAccount ),
195
+ spew .Sdump (dKvAccount ),
86
196
),
87
197
B : difflib .SplitLines (
88
- spew .Sdump (migratedAccount ),
198
+ spew .Sdump (dMigratedAccount ),
89
199
),
90
200
FromFile : "Expected" ,
91
201
FromDate : "" ,
@@ -96,7 +206,7 @@ func migrateAccountsToSQL(ctx context.Context, kvStore kvdb.Backend,
96
206
diffText , _ := difflib .GetUnifiedDiffString (diff )
97
207
98
208
return fmt .Errorf ("%w: %v.\n %v" , ErrMigrationMismatch ,
99
- kvAccount .ID , diffText )
209
+ dKvAccount .ID , diffText )
100
210
}
101
211
}
102
212
0 commit comments