Skip to content

Commit 3b51680

Browse files
accounts: add deterministic map compare
This commit adds a deterministic comparison for the maps in accounts during the KVDB to SQL migration.
1 parent 1af12f4 commit 3b51680

File tree

1 file changed

+114
-4
lines changed

1 file changed

+114
-4
lines changed

accounts/sql_migration.go

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import (
66
"database/sql"
77
"errors"
88
"fmt"
9+
"github.com/lightningnetwork/lnd/lntypes"
910
"math"
1011
"reflect"
12+
"sort"
1113
"time"
1214

1315
"github.com/davecgh/go-spew/spew"
1416
"github.com/lightninglabs/lightning-terminal/db/sqlc"
1517
"github.com/lightningnetwork/lnd/kvdb"
18+
"github.com/lightningnetwork/lnd/lnwire"
1619
"github.com/pmezard/go-difflib/difflib"
1720
)
1821

@@ -23,6 +26,110 @@ var (
2326
"original account")
2427
)
2528

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+
26133
// MigrateAccountStoreToSQL runs the migration of all accounts and indices from
27134
// the KV database to the SQL database. The migration is done in a single
28135
// transaction to ensure that all accounts are migrated or none at all.
@@ -79,13 +186,16 @@ func migrateAccountsToSQL(ctx context.Context, kvStore kvdb.Backend,
79186
overrideAccountTimeZone(kvAccount)
80187
overrideAccountTimeZone(migratedAccount)
81188

82-
if !reflect.DeepEqual(kvAccount, migratedAccount) {
189+
dKvAccount := newDeterministicAccount(kvAccount)
190+
dMigratedAccount := newDeterministicAccount(migratedAccount)
191+
192+
if !reflect.DeepEqual(dKvAccount, dMigratedAccount) {
83193
diff := difflib.UnifiedDiff{
84194
A: difflib.SplitLines(
85-
spew.Sdump(kvAccount),
195+
spew.Sdump(dKvAccount),
86196
),
87197
B: difflib.SplitLines(
88-
spew.Sdump(migratedAccount),
198+
spew.Sdump(dMigratedAccount),
89199
),
90200
FromFile: "Expected",
91201
FromDate: "",
@@ -96,7 +206,7 @@ func migrateAccountsToSQL(ctx context.Context, kvStore kvdb.Backend,
96206
diffText, _ := difflib.GetUnifiedDiffString(diff)
97207

98208
return fmt.Errorf("%w: %v.\n%v", ErrMigrationMismatch,
99-
kvAccount.ID, diffText)
209+
dKvAccount.ID, diffText)
100210
}
101211
}
102212

0 commit comments

Comments
 (0)