Skip to content

Commit 444524a

Browse files
committed
channeldb+lnd: set invoice bucket tombstone after migration
This commit introduces the functionality to set a tombstone key in the invoice bucket after the migration to the native SQL database. The tombstone prevents the user from switching back to the KV invoice database, ensuring data consistency and avoiding potential issues like lingering invoices or partial state in KV tables. The tombstone is checked on startup to block any manual overrides that attempt to revert the migration.
1 parent 6cabc74 commit 444524a

File tree

4 files changed

+122
-1
lines changed

4 files changed

+122
-1
lines changed

channeldb/invoice_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,28 @@ func TestEncodeDecodeAmpInvoiceState(t *testing.T) {
103103
// The two states should match.
104104
require.Equal(t, ampState, ampState2)
105105
}
106+
107+
// TestInvoiceBucketTombstone tests the behavior of setting and checking the
108+
// invoice bucket tombstone. It verifies that the tombstone can be set correctly
109+
// and detected when present in the database.
110+
func TestInvoiceBucketTombstone(t *testing.T) {
111+
t.Parallel()
112+
113+
// Initialize a test database.
114+
db, err := MakeTestDB(t)
115+
require.NoError(t, err, "unable to initialize db")
116+
117+
// Ensure the tombstone doesn't exist initially.
118+
tombstoneExists, err := db.GetInvoiceBucketTombstone()
119+
require.NoError(t, err)
120+
require.False(t, tombstoneExists)
121+
122+
// Set the tombstone.
123+
err = db.SetInvoiceBucketTombstone()
124+
require.NoError(t, err)
125+
126+
// Verify that the tombstone exists after setting it.
127+
tombstoneExists, err = db.GetInvoiceBucketTombstone()
128+
require.NoError(t, err)
129+
require.True(t, tombstoneExists)
130+
}

channeldb/invoices.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ var (
8080
//
8181
// settleIndexNo => invoiceKey
8282
settleIndexBucket = []byte("invoice-settle-index")
83+
84+
// invoiceBucketTombstone is a special key that indicates the invoice
85+
// bucket has been permanently closed. Its purpose is to prevent the
86+
// invoice bucket from being reopened in the future. A key use case for
87+
// the tombstone is to ensure users cannot switch back to the KV invoice
88+
// database after migrating to the native SQL database.
89+
invoiceBucketTombstone = []byte("invoice-tombstone")
8390
)
8491

8592
const (
@@ -2402,3 +2409,49 @@ func (d *DB) DeleteInvoice(_ context.Context,
24022409

24032410
return err
24042411
}
2412+
2413+
// SetInvoiceBucketTombstone sets the tombstone key in the invoice bucket to
2414+
// mark the bucket as permanently closed. This prevents it from being reopened
2415+
// in the future.
2416+
func (d *DB) SetInvoiceBucketTombstone() error {
2417+
return kvdb.Update(d, func(tx kvdb.RwTx) error {
2418+
// Access the top-level invoice bucket.
2419+
invoices := tx.ReadWriteBucket(invoiceBucket)
2420+
if invoices == nil {
2421+
return fmt.Errorf("invoice bucket does not exist")
2422+
}
2423+
2424+
// Add the tombstone key to the invoice bucket.
2425+
err := invoices.Put(invoiceBucketTombstone, []byte("1"))
2426+
if err != nil {
2427+
return fmt.Errorf("failed to set tombstone: %w", err)
2428+
}
2429+
2430+
return nil
2431+
}, func() {})
2432+
}
2433+
2434+
// GetInvoiceBucketTombstone checks if the tombstone key exists in the invoice
2435+
// bucket. It returns true if the tombstone is present and false otherwise.
2436+
func (d *DB) GetInvoiceBucketTombstone() (bool, error) {
2437+
var tombstoneExists bool
2438+
2439+
err := kvdb.View(d, func(tx kvdb.RTx) error {
2440+
// Access the top-level invoice bucket.
2441+
invoices := tx.ReadBucket(invoiceBucket)
2442+
if invoices == nil {
2443+
return fmt.Errorf("invoice bucket does not exist")
2444+
}
2445+
2446+
// Check if the tombstone key exists.
2447+
tombstone := invoices.Get(invoiceBucketTombstone)
2448+
tombstoneExists = tombstone != nil
2449+
2450+
return nil
2451+
}, func() {})
2452+
if err != nil {
2453+
return false, err
2454+
}
2455+
2456+
return tombstoneExists, nil
2457+
}

config_builder.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1101,11 +1101,22 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
11011101
// regardless of the flag.
11021102
if !d.cfg.DB.SkipSQLInvoiceMigration {
11031103
migrationFn := func(tx *sqlc.Queries) error {
1104-
return invoices.MigrateInvoicesToSQL(
1104+
err := invoices.MigrateInvoicesToSQL(
11051105
ctx, dbs.ChanStateDB.Backend,
11061106
dbs.ChanStateDB, tx,
11071107
invoiceMigrationBatchSize,
11081108
)
1109+
if err != nil {
1110+
return fmt.Errorf("failed to migrate "+
1111+
"invoices to SQL: %w", err)
1112+
}
1113+
1114+
// Set the invoice bucket tombstone to indicate
1115+
// that the migration has been completed.
1116+
d.logger.Debugf("Setting invoice bucket " +
1117+
"tombstone")
1118+
1119+
return dbs.ChanStateDB.SetInvoiceBucketTombstone() //nolint:ll
11091120
}
11101121

11111122
// Make sure we attach the custom migration function to
@@ -1147,6 +1158,28 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
11471158

11481159
dbs.InvoiceDB = sqlInvoiceDB
11491160
} else {
1161+
// Check if the invoice bucket tombstone is set. If it is, we
1162+
// need to return and ask the user switch back to using the
1163+
// native SQL store.
1164+
ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
1165+
d.logger.Debugf("Invoice bucket tombstone set to: %v",
1166+
ripInvoices)
1167+
1168+
if err != nil {
1169+
err = fmt.Errorf("unable to check invoice bucket "+
1170+
"tombstone: %w", err)
1171+
d.logger.Error(err)
1172+
1173+
return nil, nil, err
1174+
}
1175+
if ripInvoices {
1176+
err = fmt.Errorf("invoices bucket tombstoned, please " +
1177+
"switch back to native SQL")
1178+
d.logger.Error(err)
1179+
1180+
return nil, nil, err
1181+
}
1182+
11501183
dbs.InvoiceDB = dbs.ChanStateDB
11511184
}
11521185

itest/lnd_invoice_migration_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,16 @@ func testInvoiceMigration(ht *lntest.HarnessTest) {
298298
}
299299
}
300300

301+
// Now restart Bob without the --db.use-native-sql flag so we can check
302+
// that the KV tombstone was set and that Bob will fail to start.
303+
require.NoError(ht, bob.Stop())
304+
bob.SetExtraArgs(nil)
305+
306+
// Bob should now fail to start due to the tombstone being set.
307+
require.NoError(ht, bob.StartLndCmd(ht.Context()))
308+
require.Error(ht, bob.WaitForProcessExit())
309+
301310
// Start Bob again so the test can complete.
311+
bob.SetExtraArgs([]string{"--db.use-native-sql"})
302312
require.NoError(ht, bob.Start(ht.Context()))
303313
}

0 commit comments

Comments
 (0)