From 54c5d4e01a14eec9f8fc547e7d1cafb8b1340487 Mon Sep 17 00:00:00 2001 From: 0xfandom Date: Fri, 1 May 2026 15:49:09 +0530 Subject: [PATCH] migrate-db: skip missing source DBs by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running migrate-db on a fresh node (no bbolt DBs yet) currently aborts with an unrecoverable "database does not exist" error on the first mandatory prefix. LND happily creates the destination SQL DB itself on first start, so a missing source is not actually a failure — it just means there is nothing to migrate. Treat ErrDbDoesNotExist on a mandatory prefix the same as on an optional one (log and continue), and gate the previous strict behavior behind a new opt-in flag: --error-if-no-bbolt-db-exists The flag is off by default so the no-op case becomes friendly to container/k8s init wrappers that always run migrate-db, and on for operators who want a hard failure when their bbolt files have gone missing unexpectedly. Fixes #77 --- cmd_migrate_db.go | 37 ++++++++++++++++++++++++----- cmd_migrate_db_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++ docs/data-migration.md | 2 ++ 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/cmd_migrate_db.go b/cmd_migrate_db.go index 9ec30a8..0a18b74 100644 --- a/cmd_migrate_db.go +++ b/cmd_migrate_db.go @@ -99,6 +99,7 @@ type migrateDBCommand struct { ForceNewMigration bool `long:"force-new-migration" description:"Force a new migration from the beginning of the source DB so the resume state will be discarded"` ForceVerifyDB bool `long:"force-verify-db" description:"Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the content of the destination db"` ChunkSize uint64 `long:"chunk-size" description:"Chunk size for the migration in bytes"` + ErrorIfNoBboltDB bool `long:"error-if-no-bbolt-db-exists" description:"Return an error when a mandatory source bbolt DB is not found. By default missing source DBs are logged and skipped so a fresh node (no bbolt DBs yet) is treated as a successful no-op."` } func newMigrateDBCommand() *migrateDBCommand { @@ -232,13 +233,26 @@ func (x *migrateDBCommand) Execute(_ []string) error { // Open and check the database version. srcDb, err := openSourceDb(x.Source, prefix, x.Network, true) if err == kvdb.ErrDbDoesNotExist { - // Only skip if it's an optional because it's not - // required to run a wtclient or wtserver for example. - if optionalDBs[prefix] { + switch { + // Optional DBs (e.g. wtclient, wtserver, neutrino) are + // always safe to skip when missing. + case optionalDBs[prefix]: logger.Warnf("Skipping checking db version "+ "of optional DB %s: not found", prefix) continue + + // A mandatory bbolt DB is missing. Treat this as a + // fresh node by default (LND will create the SQL DB + // itself on first start). Only fail if the user + // explicitly opted in via --error-if-no-bbolt-db-exists. + case !x.ErrorIfNoBboltDB: + logger.Warnf("Skipping checking db version "+ + "of mandatory DB %s: not found "+ + "(pass --error-if-no-bbolt-db-exists "+ + "to fail instead)", prefix) + + continue } } if err != nil { @@ -281,12 +295,23 @@ func (x *migrateDBCommand) Execute(_ []string) error { x.Source, prefix, x.Network, true, ) if err == kvdb.ErrDbDoesNotExist { - // Only skip if it's an optional because it's not - // required to run a wtclient or wtserver for example. - if optionalDBs[prefix] { + switch { + // Optional DBs (e.g. wtclient, wtserver, neutrino) are + // always safe to skip when missing. + case optionalDBs[prefix]: logger.Warnf("Skipping optional DB %s: not "+ "found", prefix) continue + + // A mandatory bbolt DB is missing. Treat this as a + // fresh node by default (LND will create the SQL DB + // itself on first start). Only fail if the user + // explicitly opted in via --error-if-no-bbolt-db-exists. + case !x.ErrorIfNoBboltDB: + logger.Warnf("Skipping mandatory DB %s: not "+ + "found (pass --error-if-no-bbolt-db-"+ + "exists to fail instead)", prefix) + continue } } if err != nil { diff --git a/cmd_migrate_db_test.go b/cmd_migrate_db_test.go index bc8c3a0..98c1f53 100644 --- a/cmd_migrate_db_test.go +++ b/cmd_migrate_db_test.go @@ -3,6 +3,9 @@ package main import ( "testing" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/kvdb/sqlite" + "github.com/lightningnetwork/lnd/lncfg" "github.com/stretchr/testify/require" ) @@ -20,3 +23,54 @@ func setupTestData(t *testing.T) string { return tempDir } + +// TestMigrateDBNoBboltDBExists verifies that running migrate-db against a data +// directory that contains no bbolt source DBs is a successful no-op by default +// and only fails when --error-if-no-bbolt-db-exists is set. +func TestMigrateDBNoBboltDBExists(t *testing.T) { + t.Parallel() + + makeCmd := func(dataDir string, errIfMissing bool) *migrateDBCommand { + return &migrateDBCommand{ + Source: &SourceDB{ + Backend: lncfg.BoltBackend, + Bolt: &Bolt{ + DBTimeout: kvdb.DefaultDBTimeout, + DataDir: dataDir, + TowerDir: dataDir, + }, + }, + Dest: &DestDB{ + Backend: lncfg.SqliteBackend, + Sqlite: &Sqlite{ + DataDir: dataDir, + TowerDir: dataDir, + Config: &sqlite.Config{}, + }, + }, + Network: "regtest", + ChunkSize: 1024, + ErrorIfNoBboltDB: errIfMissing, + } + } + + t.Run("default skips missing DBs", func(t *testing.T) { + t.Parallel() + + // An empty temp dir contains no bbolt DBs at all. With the + // default flag value the migration should succeed without + // touching the destination. + err := makeCmd(t.TempDir(), false).Execute(nil) + require.NoError(t, err) + }) + + t.Run("flag forces error when missing", func(t *testing.T) { + t.Parallel() + + err := makeCmd(t.TempDir(), true).Execute(nil) + require.Error(t, err) + require.Contains( + t, err.Error(), "failed to open source db", + ) + }) +} diff --git a/docs/data-migration.md b/docs/data-migration.md index 3ea3cfb..843915b 100644 --- a/docs/data-migration.md +++ b/docs/data-migration.md @@ -238,6 +238,8 @@ Help Options: --force-verify-db Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the content of the destination db --chunk-size= Chunk size for the migration in bytes + --error-if-no-bbolt-db-exists Return an error when a mandatory source bbolt DB is not found. By default missing source DBs are logged and skipped so a fresh node (no bbolt + DBs yet) is treated as a successful no-op. source: --source.backend=[bolt] The source database backend. (default: bolt)