diff --git a/channeldb/db.go b/channeldb/db.go index 3b50d7184..5e13f820e 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -663,12 +663,24 @@ func (d *DB) syncVersions(versions []version) error { } } - // If the current database version matches the latest version number, - // then we don't need to perform any migrations. latestVersion := getLatestDBVersion(versions) log.Infof("Checking for schema update: latest_version=%v, "+ "db_version=%v", latestVersion, meta.DbVersionNumber) - if meta.DbVersionNumber == latestVersion { + + switch { + + // If the database reports a higher version that we are aware of, the + // user is probably trying to revert to a prior version of lnd. We fail + // here to prevent reversions and unintended corruption. + case meta.DbVersionNumber > latestVersion: + log.Errorf("Refusing to revert from db_version=%d to "+ + "lower version=%d", meta.DbVersionNumber, + latestVersion) + return ErrDBReversion + + // If the current database version matches the latest version number, + // then we don't need to perform any migrations. + case meta.DbVersionNumber == latestVersion: return nil } diff --git a/channeldb/error.go b/channeldb/error.go index 6ee24a87e..5c8e20982 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -7,6 +7,10 @@ var ( // created. ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created") + // ErrDBReversion is returned when detecting an attempt to revert to a + // prior database version. + ErrDBReversion = fmt.Errorf("channel db cannot revert to prior version") + // ErrLinkNodesNotFound is returned when node info bucket hasn't been // created. ErrLinkNodesNotFound = fmt.Errorf("no link nodes exist") diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index 5890d6692..dbed0a2be 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "io/ioutil" "testing" "github.com/coreos/bbolt" @@ -380,3 +381,43 @@ func TestMigrationWithoutErrors(t *testing.T) { migrationWithoutErrors, false) } + +// TestMigrationReversion tests after performing a migration to a higher +// database version, opening the database with a lower latest db version returns +// ErrDBReversion. +func TestMigrationReversion(t *testing.T) { + t.Parallel() + + tempDirName, err := ioutil.TempDir("", "channeldb") + if err != nil { + t.Fatalf("unable to create temp dir: %v", err) + } + + cdb, err := Open(tempDirName) + if err != nil { + t.Fatalf("unable to open channeldb: %v", err) + } + + // Update the database metadata to point to one more than the highest + // known version. + err = cdb.Update(func(tx *bolt.Tx) error { + newMeta := &Meta{ + DbVersionNumber: getLatestDBVersion(dbVersions) + 1, + } + + return putMeta(newMeta, tx) + }) + + // Close the database. Even if we succeeded, our next step is to reopen. + cdb.Close() + + if err != nil { + t.Fatalf("unable to increase db version: %v", err) + } + + _, err = Open(tempDirName) + if err != ErrDBReversion { + t.Fatalf("unexpected error when opening channeldb, "+ + "want: %v, got: %v", ErrDBReversion, err) + } +}