diff --git a/config_builder.go b/config_builder.go index b5e19e5dc..45a923e05 100644 --- a/config_builder.go +++ b/config_builder.go @@ -51,6 +51,7 @@ import ( "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sqldb" + "github.com/lightningnetwork/lnd/sqldb/sqlc" "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower" @@ -60,6 +61,16 @@ import ( "gopkg.in/macaroon-bakery.v2/bakery" ) +const ( + // invoiceMigrationBatchSize is the number of invoices that will be + // migrated in a single batch. + invoiceMigrationBatchSize = 1000 + + // invoiceMigration is the version of the migration that will be used to + // migrate invoices from the kvdb to the sql database. + invoiceMigration = 7 +) + // GrpcRegistrar is an interface that must be satisfied by an external subserver // that wants to be able to register its own gRPC server onto lnd's main // grpc.Server instance. @@ -1038,7 +1049,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( if err != nil { cleanUp() - err := fmt.Errorf("unable to open graph DB: %w", err) + err = fmt.Errorf("unable to open graph DB: %w", err) d.logger.Error(err) return nil, nil, err @@ -1072,65 +1083,69 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( case err != nil: cleanUp() - err := fmt.Errorf("unable to open graph DB: %w", err) + err = fmt.Errorf("unable to open graph DB: %w", err) d.logger.Error(err) return nil, nil, err } - // Instantiate a native SQL invoice store if the flag is set. + // Instantiate a native SQL store if the flag is set. if d.cfg.DB.UseNativeSQL { + migrations := sqldb.GetMigrations() + + // If the user has not explicitly disabled the SQL invoice + // migration, attach the custom migration function to invoice + // migration (version 7). Even if this custom migration is + // disabled, the regular native SQL store migrations will still + // run. If the database version is already above this custom + // migration's version (7), it will be skipped permanently, + // regardless of the flag. + if !d.cfg.DB.SkipSQLInvoiceMigration { + migrationFn := func(tx *sqlc.Queries) error { + return invoices.MigrateInvoicesToSQL( + ctx, dbs.ChanStateDB.Backend, + dbs.ChanStateDB, tx, + invoiceMigrationBatchSize, + ) + } + + // Make sure we attach the custom migration function to + // the correct migration version. + for i := 0; i < len(migrations); i++ { + if migrations[i].Version != invoiceMigration { + continue + } + + migrations[i].MigrationFn = migrationFn + } + } + // We need to apply all migrations to the native SQL store // before we can use it. - err := dbs.NativeSQLStore.ApplyAllMigrations( - ctx, sqldb.GetMigrations(), - ) + err = dbs.NativeSQLStore.ApplyAllMigrations(ctx, migrations) if err != nil { cleanUp() - err := fmt.Errorf("unable to apply migrations: %w", err) - d.logger.Error(err) - - return nil, nil, err - } - - // KV invoice db resides in the same database as the channel - // state DB. Let's query the database to see if we have any - // invoices there. If we do, we won't allow the user to start - // lnd with native SQL enabled, as we don't currently migrate - // the invoices to the new database schema. - invoiceSlice, err := dbs.ChanStateDB.QueryInvoices( - ctx, invoices.InvoiceQuery{ - NumMaxInvoices: 1, - }, - ) - if err != nil { - cleanUp() - d.logger.Errorf("Unable to query KV invoice DB: %v", - err) - - return nil, nil, err - } - - if len(invoiceSlice.Invoices) > 0 { - cleanUp() - err := fmt.Errorf("found invoices in the KV invoice " + - "DB, migration to native SQL is not yet " + - "supported") + err = fmt.Errorf("faild to run migrations for the "+ + "native SQL store: %w", err) d.logger.Error(err) return nil, nil, err } + // With the DB ready and migrations applied, we can now create + // the base DB and transaction executor for the native SQL + // invoice store. baseDB := dbs.NativeSQLStore.GetBaseDB() executor := sqldb.NewTransactionExecutor( - baseDB, - func(tx *sql.Tx) invoices.SQLInvoiceQueries { + baseDB, func(tx *sql.Tx) invoices.SQLInvoiceQueries { return baseDB.WithTx(tx) }, ) - dbs.InvoiceDB = invoices.NewSQLStore( + sqlInvoiceDB := invoices.NewSQLStore( executor, clock.NewDefaultClock(), ) + + dbs.InvoiceDB = sqlInvoiceDB } else { dbs.InvoiceDB = dbs.ChanStateDB } @@ -1143,7 +1158,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( if err != nil { cleanUp() - err := fmt.Errorf("unable to open %s database: %w", + err = fmt.Errorf("unable to open %s database: %w", lncfg.NSTowerClientDB, err) d.logger.Error(err) return nil, nil, err @@ -1158,7 +1173,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( if err != nil { cleanUp() - err := fmt.Errorf("unable to open %s database: %w", + err = fmt.Errorf("unable to open %s database: %w", lncfg.NSTowerServerDB, err) d.logger.Error(err) return nil, nil, err diff --git a/invoices/testdata/channel.db b/invoices/testdata/channel.db index 78741eae9..69397f529 100644 Binary files a/invoices/testdata/channel.db and b/invoices/testdata/channel.db differ diff --git a/lncfg/db.go b/lncfg/db.go index 040b3e8d1..bdeadb933 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -87,6 +87,8 @@ type DB struct { UseNativeSQL bool `long:"use-native-sql" description:"Use native SQL for tables that already support it."` + SkipSQLInvoiceMigration bool `long:"skip-sql-invoice-migration" description:"Do not migrate invoices stored in our key-value database to native SQL."` + NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."` PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."` @@ -115,7 +117,8 @@ func DefaultDB() *DB { MaxConnections: defaultSqliteMaxConnections, BusyTimeout: defaultSqliteBusyTimeout, }, - UseNativeSQL: false, + UseNativeSQL: false, + SkipSQLInvoiceMigration: false, } } diff --git a/sample-lnd.conf b/sample-lnd.conf index 86ea82485..5390ccd77 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1472,6 +1472,9 @@ ; own risk. ; db.use-native-sql=false +; If set to true, native SQL invoice migration will be skipped. Note that this +; option is intended for users who experience non-resolvable migration errors. +; db.skip-sql-invoice-migration=false [etcd] diff --git a/sqldb/migrations.go b/sqldb/migrations.go index 7722fda4f..f81436733 100644 --- a/sqldb/migrations.go +++ b/sqldb/migrations.go @@ -61,6 +61,15 @@ var ( Version: 6, SchemaVersion: 6, }, + { + Name: "kv_invoice_migration", + Version: 7, + SchemaVersion: 6, + // A migration function is may be attached to this + // migration to migrate KV invoices to the native SQL + // schema. This is optional and can be disabled by the + // user if necessary. + }, } )