From f174b60b9424cceecf8405b9fc3a814ae6b33ad7 Mon Sep 17 00:00:00 2001 From: ziggie Date: Sun, 11 Jan 2026 00:09:42 +0100 Subject: [PATCH] =?UTF-8?q?payments/migration1:=20wire=20KV=E2=86=92SQL=20?= =?UTF-8?q?migration=20in=20the=20main=20pkg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hook the payments KV→SQL migration into the SQL migration config. The migration is still only available when building with the build tag "test_native_sql". Moreover a tombstone protection similar to the invoice migration is added to prevent re-runningi with the KV backend once migration completes. --- config_builder.go | 58 ++++++++++++++++++++++++++++++ payments/db/kv_tombstone.go | 71 +++++++++++++++++++++++++++++++++++++ payments/db/log.go | 2 ++ sqldb/migrations_dev.go | 14 ++++++++ 4 files changed, 145 insertions(+) create mode 100644 payments/db/kv_tombstone.go diff --git a/config_builder.go b/config_builder.go index 74f8b3afb..729cb6017 100644 --- a/config_builder.go +++ b/config_builder.go @@ -51,6 +51,8 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/msgmux" paymentsdb "github.com/lightningnetwork/lnd/payments/db" + paymentsmig1 "github.com/lightningnetwork/lnd/payments/db/migration1" + paymentsmig1sqlc "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sqldb" @@ -76,6 +78,10 @@ const ( // graphMigration is the version number for the graph migration // that migrates the KV graph to the native SQL schema. graphMigration = 10 + + // paymentMigration is the version number for the payments migration + // that migrates KV payments to the native SQL schema. + paymentMigration = 14 ) // GrpcRegistrar is an interface that must be satisfied by an external subserver @@ -1153,6 +1159,31 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil } + paymentMig := func(tx *sqlc.Queries) error { + err := paymentsmig1.MigratePaymentsKVToSQL( + ctx, + dbs.ChanStateDB.Backend, + paymentsmig1sqlc.New(tx.GetTx()), + &paymentsmig1.SQLStoreConfig{ + QueryCfg: queryCfg, + }, + ) + if err != nil { + return fmt.Errorf("failed to migrate "+ + "payments to SQL: %w", err) + } + + // Set the payments bucket tombstone to + // indicate that the migration has been + // completed. + d.logger.Debugf("Setting payments bucket " + + "tombstone") + + return paymentsdb.SetPaymentsBucketTombstone( + dbs.ChanStateDB.Backend, + ) + } + // Make sure we attach the custom migration function to // the correct migration version. for i := 0; i < len(migrations); i++ { @@ -1162,11 +1193,17 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( migrations[i].MigrationFn = invoiceMig continue + case graphMigration: migrations[i].MigrationFn = graphMig continue + case paymentMigration: + migrations[i].MigrationFn = paymentMig + + continue + default: } @@ -1265,6 +1302,27 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil, nil, err } + // Check if the payments bucket tombstone is set. If it is, we + // need to return and ask the user switch back to using the + // native SQL store. + ripPayments, err := paymentsdb.GetPaymentsBucketTombstone( + dbs.ChanStateDB.Backend, + ) + if err != nil { + err = fmt.Errorf("unable to check payments bucket "+ + "tombstone: %w", err) + d.logger.Error(err) + + return nil, nil, err + } + if ripPayments { + err = fmt.Errorf("payments bucket tombstoned, please " + + "switch back to native SQL") + d.logger.Error(err) + + return nil, nil, err + } + dbs.InvoiceDB = dbs.ChanStateDB graphStore, err = graphdb.NewKVStore( diff --git a/payments/db/kv_tombstone.go b/payments/db/kv_tombstone.go new file mode 100644 index 000000000..7dc13b93b --- /dev/null +++ b/payments/db/kv_tombstone.go @@ -0,0 +1,71 @@ +package paymentsdb + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + // paymentsBucketTombstone is the key used to mark the payments bucket + // as permanently closed after a successful migration. + paymentsBucketTombstone = []byte("payments-tombstone") +) + +// SetPaymentsBucketTombstone sets the tombstone key in the payments bucket to +// mark the bucket as permanently closed. This prevents it from being reopened +// in the future. +func SetPaymentsBucketTombstone(db kvdb.Backend) error { + return kvdb.Update(db, func(tx kvdb.RwTx) error { + // Access the top-level payments bucket. + payments := tx.ReadWriteBucket(paymentsRootBucket) + + // In case the bucket doesn't exist, because we start + // immediately with the native SQL schema, we create it as well + // to make sure the user cannot switch back to the KV store. + if payments == nil { + var err error + payments, err = tx.CreateTopLevelBucket( + paymentsRootBucket, + ) + if err != nil { + return fmt.Errorf("failed to create payments "+ + "bucket: %w", err) + } + } + + // Add the tombstone key to the payments bucket. + err := payments.Put(paymentsBucketTombstone, []byte("1")) + if err != nil { + return fmt.Errorf("failed to set tombstone: %w", err) + } + + return nil + }, func() {}) +} + +// GetPaymentsBucketTombstone checks if the tombstone key exists in the payments +// bucket. It returns true if the tombstone is present and false otherwise. +func GetPaymentsBucketTombstone(db kvdb.Backend) (bool, error) { + var tombstoneExists bool + + err := kvdb.View(db, func(tx kvdb.RTx) error { + // Access the top-level payments bucket. + payments := tx.ReadBucket(paymentsRootBucket) + if payments == nil { + tombstoneExists = false + return nil + } + + // Check if the tombstone key exists. + tombstone := payments.Get(paymentsBucketTombstone) + tombstoneExists = tombstone != nil + + return nil + }, func() {}) + if err != nil { + return false, err + } + + return tombstoneExists, nil +} diff --git a/payments/db/log.go b/payments/db/log.go index 8a77dbcec..c8892341d 100644 --- a/payments/db/log.go +++ b/payments/db/log.go @@ -3,6 +3,7 @@ package paymentsdb import ( "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/build" + paymentsmig1 "github.com/lightningnetwork/lnd/payments/db/migration1" ) // log is a logger that is initialized with no output filters. This @@ -29,4 +30,5 @@ func DisableLog() { // using btclog. func UseLogger(logger btclog.Logger) { log = logger + paymentsmig1.UseLogger(logger) } diff --git a/sqldb/migrations_dev.go b/sqldb/migrations_dev.go index ca57cf0f1..7b02b151f 100644 --- a/sqldb/migrations_dev.go +++ b/sqldb/migrations_dev.go @@ -8,4 +8,18 @@ var migrationAdditions = []MigrationConfig{ Version: 12, SchemaVersion: 10, }, + { + Name: "000011_payment_duplicates", + Version: 13, + SchemaVersion: 11, + }, + { + Name: "kv_payments_migration", + Version: 14, + SchemaVersion: 11, + // A migration function may be attached to this + // migration to migrate KV payments to the native SQL + // schema. This is optional and can be disabled by the + // user if necessary. + }, }