Skip to content

Commit d593098

Browse files
authored
Merge pull request #1084 from ViktorTigerstrom/2025-06-update-accounts-migration
[sql-42] accounts: Update SQL migration to iterate over buckets + ensure proper closure of SQL test stores
2 parents f4a5883 + 12343cf commit d593098

File tree

5 files changed

+114
-14
lines changed

5 files changed

+114
-14
lines changed

accounts/sql_migration.go

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package accounts
22

33
import (
4+
"bytes"
45
"context"
56
"database/sql"
67
"errors"
@@ -11,6 +12,7 @@ import (
1112

1213
"github.com/davecgh/go-spew/spew"
1314
"github.com/lightninglabs/lightning-terminal/db/sqlc"
15+
"github.com/lightningnetwork/lnd/kvdb"
1416
"github.com/pmezard/go-difflib/difflib"
1517
)
1618

@@ -24,7 +26,7 @@ var (
2426
// MigrateAccountStoreToSQL runs the migration of all accounts and indices from
2527
// the KV database to the SQL database. The migration is done in a single
2628
// transaction to ensure that all accounts are migrated or none at all.
27-
func MigrateAccountStoreToSQL(ctx context.Context, kvStore *BoltStore,
29+
func MigrateAccountStoreToSQL(ctx context.Context, kvStore kvdb.Backend,
2830
tx SQLQueries) error {
2931

3032
log.Infof("Starting migration of the KV accounts store to SQL")
@@ -47,12 +49,12 @@ func MigrateAccountStoreToSQL(ctx context.Context, kvStore *BoltStore,
4749
// migrateAccountsToSQL runs the migration of all accounts from the KV database
4850
// to the SQL database. The migration is done in a single transaction to ensure
4951
// that all accounts are migrated or none at all.
50-
func migrateAccountsToSQL(ctx context.Context, kvStore *BoltStore,
52+
func migrateAccountsToSQL(ctx context.Context, kvStore kvdb.Backend,
5153
tx SQLQueries) error {
5254

5355
log.Infof("Starting migration of accounts from KV to SQL")
5456

55-
kvAccounts, err := kvStore.Accounts(ctx)
57+
kvAccounts, err := getBBoltAccounts(kvStore)
5658
if err != nil {
5759
return err
5860
}
@@ -104,6 +106,51 @@ func migrateAccountsToSQL(ctx context.Context, kvStore *BoltStore,
104106
return nil
105107
}
106108

109+
// getBBoltAccounts is a helper function that fetches all accounts from the
110+
// Bbolt store, by iterating directly over the buckets, without needing to
111+
// use any public functions of the BoltStore struct.
112+
func getBBoltAccounts(db kvdb.Backend) ([]*OffChainBalanceAccount, error) {
113+
var accounts []*OffChainBalanceAccount
114+
err := db.View(func(tx kvdb.RTx) error {
115+
// This function will be called in the ForEach and receive
116+
// the key and value of each account in the DB. The key, which
117+
// is also the ID is not used because it is also marshaled into
118+
// the value.
119+
readFn := func(k, v []byte) error {
120+
// Skip the two special purpose keys.
121+
if bytes.Equal(k, lastAddIndexKey) ||
122+
bytes.Equal(k, lastSettleIndexKey) {
123+
124+
return nil
125+
}
126+
127+
// There should be no sub-buckets.
128+
if v == nil {
129+
return fmt.Errorf("invalid bucket structure")
130+
}
131+
132+
account, err := deserializeAccount(v)
133+
if err != nil {
134+
return err
135+
}
136+
137+
accounts = append(accounts, account)
138+
return nil
139+
}
140+
141+
// We know the bucket should exist since it's created when
142+
// the account storage is initialized.
143+
return tx.ReadBucket(accountBucketName).ForEach(readFn)
144+
}, func() {
145+
accounts = nil
146+
})
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
return accounts, nil
152+
}
153+
107154
// migrateSingleAccountToSQL runs the migration for a single account from the
108155
// KV database to the SQL database.
109156
func migrateSingleAccountToSQL(ctx context.Context,
@@ -163,12 +210,12 @@ func migrateSingleAccountToSQL(ctx context.Context,
163210

164211
// migrateAccountsIndicesToSQL runs the migration for the account indices from
165212
// the KV database to the SQL database.
166-
func migrateAccountsIndicesToSQL(ctx context.Context, kvStore *BoltStore,
213+
func migrateAccountsIndicesToSQL(ctx context.Context, kvStore kvdb.Backend,
167214
tx SQLQueries) error {
168215

169216
log.Infof("Starting migration of accounts indices from KV to SQL")
170217

171-
addIndex, settleIndex, err := kvStore.LastIndexes(ctx)
218+
addIndex, settleIndex, err := getBBoltIndices(kvStore)
172219
if errors.Is(err, ErrNoInvoiceIndexKnown) {
173220
log.Infof("No indices found in KV store, skipping migration")
174221
return nil
@@ -211,6 +258,40 @@ func migrateAccountsIndicesToSQL(ctx context.Context, kvStore *BoltStore,
211258
return nil
212259
}
213260

261+
// getBBoltIndices is a helper function that fetches the índices from the
262+
// Bbolt store, by iterating directly over the buckets, without needing to
263+
// use any public functions of the BoltStore struct.
264+
func getBBoltIndices(db kvdb.Backend) (uint64, uint64, error) {
265+
var (
266+
addValue, settleValue []byte
267+
)
268+
err := db.View(func(tx kvdb.RTx) error {
269+
bucket := tx.ReadBucket(accountBucketName)
270+
if bucket == nil {
271+
return ErrAccountBucketNotFound
272+
}
273+
274+
addValue = bucket.Get(lastAddIndexKey)
275+
if len(addValue) == 0 {
276+
return ErrNoInvoiceIndexKnown
277+
}
278+
279+
settleValue = bucket.Get(lastSettleIndexKey)
280+
if len(settleValue) == 0 {
281+
return ErrNoInvoiceIndexKnown
282+
}
283+
284+
return nil
285+
}, func() {
286+
addValue, settleValue = nil, nil
287+
})
288+
if err != nil {
289+
return 0, 0, err
290+
}
291+
292+
return byteOrder.Uint64(addValue), byteOrder.Uint64(settleValue), nil
293+
}
294+
214295
// overrideAccountTimeZone overrides the time zone of the account to the local
215296
// time zone and chops off the nanosecond part for comparison. This is needed
216297
// because KV database stores times as-is which as an unwanted side effect would

accounts/sql_migration_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ func TestAccountStoreMigration(t *testing.T) {
3939
*db.TransactionExecutor[SQLQueries]) {
4040

4141
testDBStore := NewTestDB(t, clock)
42-
t.Cleanup(func() {
43-
require.NoError(t, testDBStore.Close())
44-
})
4542

4643
store, ok := testDBStore.(*SQLStore)
4744
require.True(t, ok)
@@ -344,7 +341,7 @@ func TestAccountStoreMigration(t *testing.T) {
344341
err = txEx.ExecTx(ctx, &opts,
345342
func(tx SQLQueries) error {
346343
return MigrateAccountStoreToSQL(
347-
ctx, kvStore, tx,
344+
ctx, kvStore.db, tx,
348345
)
349346
},
350347
)

accounts/test_postgres.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ var ErrDBClosed = errors.New("database is closed")
1616

1717
// NewTestDB is a helper function that creates an SQLStore database for testing.
1818
func NewTestDB(t *testing.T, clock clock.Clock) Store {
19-
return NewSQLStore(db.NewTestPostgresDB(t).BaseDB, clock)
19+
return createStore(t, db.NewTestPostgresDB(t).BaseDB, clock)
2020
}
2121

2222
// NewTestDBFromPath is a helper function that creates a new SQLStore with a
2323
// connection to an existing postgres database for testing.
2424
func NewTestDBFromPath(t *testing.T, dbPath string,
2525
clock clock.Clock) Store {
2626

27-
return NewSQLStore(db.NewTestPostgresDB(t).BaseDB, clock)
27+
return createStore(t, db.NewTestPostgresDB(t).BaseDB, clock)
2828
}

accounts/test_sql.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//go:build test_db_postgres || test_db_sqlite
2+
3+
package accounts
4+
5+
import (
6+
"testing"
7+
8+
"github.com/lightninglabs/lightning-terminal/db"
9+
"github.com/lightningnetwork/lnd/clock"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// createStore is a helper function that creates a new SQLStore and ensure that
14+
// it is closed when during the test cleanup.
15+
func createStore(t *testing.T, sqlDB *db.BaseDB, clock clock.Clock) *SQLStore {
16+
store := NewSQLStore(sqlDB, clock)
17+
t.Cleanup(func() {
18+
require.NoError(t, store.Close())
19+
})
20+
21+
return store
22+
}

accounts/test_sqlite.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ var ErrDBClosed = errors.New("database is closed")
1616

1717
// NewTestDB is a helper function that creates an SQLStore database for testing.
1818
func NewTestDB(t *testing.T, clock clock.Clock) Store {
19-
return NewSQLStore(db.NewTestSqliteDB(t).BaseDB, clock)
19+
return createStore(t, db.NewTestSqliteDB(t).BaseDB, clock)
2020
}
2121

2222
// NewTestDBFromPath is a helper function that creates a new SQLStore with a
2323
// connection to an existing SQL database for testing.
2424
func NewTestDBFromPath(t *testing.T, dbPath string,
2525
clock clock.Clock) Store {
2626

27-
return NewSQLStore(
28-
db.NewTestSqliteDbHandleFromPath(t, dbPath).BaseDB, clock,
27+
return createStore(
28+
t, db.NewTestSqliteDbHandleFromPath(t, dbPath).BaseDB, clock,
2929
)
3030
}

0 commit comments

Comments
 (0)