Skip to content

Commit 3072e48

Browse files
committed
accounts: add migration code from kvdb to SQL
This commit introduces the migration logic for transitioning the accounts store from kvdb to SQL. Note that as of this commit, the migration is not yet triggered by any production code, i.e. only tests execute the migration logic.
1 parent c10d981 commit 3072e48

File tree

3 files changed

+624
-0
lines changed

3 files changed

+624
-0
lines changed

accounts/kv_sql_migration_test.go

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
package accounts
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"testing"
8+
"time"
9+
10+
"github.com/lightninglabs/lightning-terminal/db"
11+
"github.com/lightningnetwork/lnd/clock"
12+
"github.com/lightningnetwork/lnd/fn"
13+
"github.com/lightningnetwork/lnd/lnrpc"
14+
"github.com/lightningnetwork/lnd/lntypes"
15+
"github.com/lightningnetwork/lnd/sqldb"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
// TestAccountStoreMigration tests the migration of account store from a bolt
20+
// backed to a SQL database. Note that this test does not attempt to be a
21+
// complete migration test.
22+
func TestAccountStoreMigration(t *testing.T) {
23+
t.Parallel()
24+
25+
ctxc, cancel := context.WithCancel(context.Background())
26+
t.Cleanup(cancel)
27+
28+
// First create a shared Postgres instance so we don't spawn a new
29+
// docker container for each test.
30+
pgFixture := db.NewTestPgFixture(
31+
t, db.DefaultPostgresFixtureLifetime, true,
32+
)
33+
t.Cleanup(func() {
34+
pgFixture.TearDown(t)
35+
})
36+
37+
makeSQLDB := func(t *testing.T, clock *clock.TestClock,
38+
sqlite bool) (*SQLStore, *db.TransactionExecutor[SQLQueries]) {
39+
40+
var store *SQLStore
41+
42+
if sqlite {
43+
store = NewSQLStore(db.NewTestSqliteDB(t).BaseDB, clock)
44+
} else {
45+
store = NewSQLStore(
46+
db.NewTestPostgresDB(t).BaseDB, clock,
47+
)
48+
}
49+
50+
baseDB := store.BaseDB
51+
52+
genericExecutor := db.NewTransactionExecutor(
53+
baseDB, func(tx *sql.Tx) SQLQueries {
54+
return baseDB.WithTx(tx)
55+
},
56+
)
57+
58+
return store, genericExecutor
59+
}
60+
61+
migrationTest := func(t *testing.T, kvStore *BoltStore,
62+
clock *clock.TestClock, sqlite bool) {
63+
64+
sqlStore, txEx := makeSQLDB(t, clock, sqlite)
65+
66+
var opts sqldb.MigrationTxOptions
67+
err := txEx.ExecTx(
68+
ctxc, &opts, func(tx SQLQueries) error {
69+
return MigrateAccountStoreToSQL(
70+
ctxc, kvStore, tx,
71+
)
72+
},
73+
)
74+
require.NoError(t, err)
75+
76+
// MigrateAccountStoreToSQL will check if the inserted accounts
77+
// and indices equals the migrated ones, but as a sanity check
78+
// we'll also fetch the accounts and indices from the store and
79+
// compare them to the original.
80+
// First we compare the migrated accounts to the original ones.
81+
kvAccounts, err := kvStore.Accounts(ctxc)
82+
require.NoError(t, err)
83+
numAccounts := len(kvAccounts)
84+
85+
sqlAccounts, err := sqlStore.Accounts(ctxc)
86+
require.NoError(t, err)
87+
require.Equal(t, numAccounts, len(sqlAccounts))
88+
89+
for i := 0; i < numAccounts; i++ {
90+
assertEqualAccounts(t, kvAccounts[i], sqlAccounts[i])
91+
}
92+
93+
// After that we compare the migrated indices.
94+
kvAddIndex, kvSettleIndex, err := kvStore.LastIndexes(ctxc)
95+
if errors.Is(err, ErrNoInvoiceIndexKnown) {
96+
// If the db doesn't have any indices, we can't compare
97+
// them.
98+
return
99+
} else {
100+
require.NoError(t, err)
101+
}
102+
103+
sqlAddIndex, sqlSettleIndex, err := sqlStore.LastIndexes(ctxc)
104+
require.NoError(t, err)
105+
106+
require.Equal(t, kvAddIndex, sqlAddIndex)
107+
require.Equal(t, kvSettleIndex, sqlSettleIndex)
108+
}
109+
110+
tests := []struct {
111+
name string
112+
populateDB func(t *testing.T, kvStore *BoltStore)
113+
}{
114+
{
115+
"empty",
116+
// Don't populate the DB.
117+
func(t *testing.T, kvStore *BoltStore) {},
118+
},
119+
{
120+
"account no expiry",
121+
func(t *testing.T, kvStore *BoltStore) {
122+
// Create an account that does not expire.
123+
acct1, err := kvStore.NewAccount(
124+
ctxc, 0, time.Time{}, "foo",
125+
)
126+
require.NoError(t, err)
127+
require.False(t, acct1.HasExpired())
128+
},
129+
},
130+
{
131+
"account with expiry",
132+
func(t *testing.T, kvStore *BoltStore) {
133+
// Create an account that does expire.
134+
acct1, err := kvStore.NewAccount(
135+
ctxc, 0, time.Now().Add(time.Hour),
136+
"foo",
137+
)
138+
require.NoError(t, err)
139+
require.False(t, acct1.HasExpired())
140+
},
141+
},
142+
{
143+
"account with updated expiry",
144+
func(t *testing.T, kvStore *BoltStore) {
145+
// Create an account that does expire.
146+
acct1, err := kvStore.NewAccount(
147+
ctxc, 0, time.Now().Add(time.Hour),
148+
"foo",
149+
)
150+
require.NoError(t, err)
151+
require.False(t, acct1.HasExpired())
152+
153+
err = kvStore.UpdateAccountBalanceAndExpiry(
154+
ctxc, acct1.ID, fn.None[int64](),
155+
fn.Some(time.Now().Add(time.Minute)),
156+
)
157+
require.NoError(t, err)
158+
},
159+
},
160+
{
161+
"account with balance",
162+
func(t *testing.T, kvStore *BoltStore) {
163+
// Create an account with balance
164+
acct1, err := kvStore.NewAccount(
165+
ctxc, 100000, time.Time{}, "foo",
166+
)
167+
require.NoError(t, err)
168+
require.False(t, acct1.HasExpired())
169+
},
170+
},
171+
{
172+
"account with updated balance",
173+
func(t *testing.T, kvStore *BoltStore) {
174+
// Create an account with balance
175+
acct1, err := kvStore.NewAccount(
176+
ctxc, 100000, time.Time{}, "foo",
177+
)
178+
require.NoError(t, err)
179+
require.False(t, acct1.HasExpired())
180+
181+
err = kvStore.CreditAccount(
182+
ctxc, acct1.ID, 10000,
183+
)
184+
require.NoError(t, err)
185+
186+
err = kvStore.DebitAccount(
187+
ctxc, acct1.ID, 20000,
188+
)
189+
require.NoError(t, err)
190+
},
191+
},
192+
{
193+
"account with invoices",
194+
func(t *testing.T, kvStore *BoltStore) {
195+
// Create an account with balance
196+
acct1, err := kvStore.NewAccount(
197+
ctxc, 0, time.Time{}, "foo",
198+
)
199+
require.NoError(t, err)
200+
require.False(t, acct1.HasExpired())
201+
202+
hash1 := lntypes.Hash{1, 2, 3, 4}
203+
err = kvStore.AddAccountInvoice(
204+
ctxc, acct1.ID, hash1,
205+
)
206+
require.NoError(t, err)
207+
208+
hash2 := lntypes.Hash{1, 2, 3, 4, 5}
209+
err = kvStore.AddAccountInvoice(
210+
ctxc, acct1.ID, hash2,
211+
)
212+
require.NoError(t, err)
213+
},
214+
},
215+
{
216+
"account with payments",
217+
func(t *testing.T, kvStore *BoltStore) {
218+
// Create an account with balance
219+
acct1, err := kvStore.NewAccount(
220+
ctxc, 0, time.Time{}, "foo",
221+
)
222+
require.NoError(t, err)
223+
require.False(t, acct1.HasExpired())
224+
225+
hash1 := lntypes.Hash{1, 1, 1, 1}
226+
known, err := kvStore.UpsertAccountPayment(
227+
ctxc, acct1.ID, hash1, 100,
228+
lnrpc.Payment_UNKNOWN,
229+
)
230+
require.NoError(t, err)
231+
require.False(t, known)
232+
233+
hash2 := lntypes.Hash{2, 2, 2, 2}
234+
known, err = kvStore.UpsertAccountPayment(
235+
ctxc, acct1.ID, hash2, 200,
236+
lnrpc.Payment_IN_FLIGHT,
237+
)
238+
require.NoError(t, err)
239+
require.False(t, known)
240+
241+
hash3 := lntypes.Hash{3, 3, 3, 3}
242+
known, err = kvStore.UpsertAccountPayment(
243+
ctxc, acct1.ID, hash3, 200,
244+
lnrpc.Payment_SUCCEEDED,
245+
)
246+
require.NoError(t, err)
247+
require.False(t, known)
248+
249+
hash4 := lntypes.Hash{4, 4, 4, 4}
250+
known, err = kvStore.UpsertAccountPayment(
251+
ctxc, acct1.ID, hash4, 200,
252+
lnrpc.Payment_FAILED,
253+
)
254+
require.NoError(t, err)
255+
require.False(t, known)
256+
},
257+
},
258+
{
259+
"multiple accounts",
260+
func(t *testing.T, kvStore *BoltStore) {
261+
// Create two accounts with balance and that
262+
// expires.
263+
acct1, err := kvStore.NewAccount(
264+
ctxc, 100000, time.Now().Add(time.Hour),
265+
"foo",
266+
)
267+
require.NoError(t, err)
268+
require.False(t, acct1.HasExpired())
269+
270+
acct2, err := kvStore.NewAccount(
271+
ctxc, 200000, time.Now().Add(time.Hour),
272+
"bar",
273+
)
274+
require.NoError(t, err)
275+
require.False(t, acct2.HasExpired())
276+
277+
// Create invoices for both accounts.
278+
hash1 := lntypes.Hash{1, 1, 1, 1}
279+
err = kvStore.AddAccountInvoice(
280+
ctxc, acct1.ID, hash1,
281+
)
282+
require.NoError(t, err)
283+
284+
hash2 := lntypes.Hash{2, 2, 2, 2}
285+
err = kvStore.AddAccountInvoice(
286+
ctxc, acct2.ID, hash2,
287+
)
288+
require.NoError(t, err)
289+
290+
// Create payments for both accounts.
291+
hash3 := lntypes.Hash{3, 3, 3, 3}
292+
known, err := kvStore.UpsertAccountPayment(
293+
ctxc, acct1.ID, hash3, 100,
294+
lnrpc.Payment_SUCCEEDED,
295+
)
296+
require.NoError(t, err)
297+
require.False(t, known)
298+
299+
hash4 := lntypes.Hash{4, 4, 4, 4}
300+
known, err = kvStore.UpsertAccountPayment(
301+
ctxc, acct2.ID, hash4, 200,
302+
lnrpc.Payment_IN_FLIGHT,
303+
)
304+
require.NoError(t, err)
305+
require.False(t, known)
306+
},
307+
},
308+
}
309+
310+
for _, test := range tests {
311+
tc := test
312+
313+
t.Run(tc.name, func(t *testing.T) {
314+
t.Parallel()
315+
316+
var kvStore *BoltStore
317+
testClock := clock.NewTestClock(time.Now())
318+
319+
kvStore, err := NewBoltStore(
320+
t.TempDir(), DBFilename, testClock,
321+
)
322+
require.NoError(t, err)
323+
324+
tc.populateDB(t, kvStore)
325+
326+
t.Cleanup(func() {
327+
require.NoError(t, kvStore.db.Close())
328+
})
329+
330+
t.Run("Postgres", func(t *testing.T) {
331+
migrationTest(t, kvStore, testClock, false)
332+
})
333+
334+
t.Run("SQLite", func(t *testing.T) {
335+
migrationTest(t, kvStore, testClock, true)
336+
})
337+
})
338+
}
339+
}

0 commit comments

Comments
 (0)