@@ -3,6 +3,7 @@ package tapdb
33import (
44 "bytes"
55 "errors"
6+ "fmt"
67 "io"
78 "io/fs"
89 "net/http"
@@ -12,6 +13,16 @@ import (
1213 "github.com/golang-migrate/migrate/v4"
1314 "github.com/golang-migrate/migrate/v4/database"
1415 "github.com/golang-migrate/migrate/v4/source/httpfs"
16+ "github.com/lightninglabs/taproot-assets/fn"
17+ )
18+
19+ const (
20+ // LatestMigrationVersion is the latest migration version of the
21+ // database. This is used to implement downgrade protection for the
22+ // daemon.
23+ //
24+ // NOTE: This MUST be updated when a new migration is added.
25+ LatestMigrationVersion = 21
1526)
1627
1728// MigrationTarget is a functional option that can be passed to applyMigrations
3445 }
3546)
3647
48+ var (
49+ // ErrMigrationDowngrade is returned when a database downgrade is
50+ // detected.
51+ ErrMigrationDowngrade = errors .New ("database downgrade detected" )
52+ )
53+
54+ // migrationOption is a functional option that can be passed to migrate related
55+ // methods to modify their behavior.
56+ type migrateOptions struct {
57+ latestVersion fn.Option [uint ]
58+ }
59+
60+ // defaultMigrateOptions returns a new migrateOptions instance with default
61+ // settings.
62+ func defaultMigrateOptions () * migrateOptions {
63+ return & migrateOptions {}
64+ }
65+
66+ // MigrateOpt is a functional option that can be passed to migrate related
67+ // methods to modify behavior.
68+ type MigrateOpt func (* migrateOptions )
69+
70+ // WithLatestVersion allows callers to override the default latest version
71+ // setting.
72+ func WithLatestVersion (version uint ) MigrateOpt {
73+ return func (o * migrateOptions ) {
74+ o .latestVersion = fn .Some (version )
75+ }
76+ }
77+
3778// migrationLogger is a logger that wraps the passed btclog.Logger so it can be
3879// used to log migrations.
3980type migrationLogger struct {
@@ -72,7 +113,7 @@ func (m *migrationLogger) Verbose() bool {
72113// system under the given path, using the passed database driver and database
73114// name, up to or down to the given target version.
74115func applyMigrations (fs fs.FS , driver database.Driver , path , dbName string ,
75- targetVersion MigrationTarget ) error {
116+ targetVersion MigrationTarget , opts * migrateOptions ) error {
76117
77118 // With the migrate instance open, we'll create a new migration source
78119 // using the embedded file system stored in sqlSchemas. The library
@@ -95,6 +136,16 @@ func applyMigrations(fs fs.FS, driver database.Driver, path, dbName string,
95136
96137 migrationVersion , _ , _ := sqlMigrate .Version ()
97138
139+ // As the down migrations may end up *dropping* data, we want to
140+ // prevent that without explicit accounting.
141+ latestVersion := opts .latestVersion .UnwrapOr (LatestMigrationVersion )
142+ if migrationVersion > latestVersion {
143+ return fmt .Errorf ("%w: database version is newer than the " +
144+ "latest migration version, preventing downgrade: " +
145+ "db_version=%v, latest_migration_version=%v" ,
146+ ErrMigrationDowngrade , migrationVersion , latestVersion )
147+ }
148+
98149 log .Infof ("Applying migrations from version=%v" , migrationVersion )
99150
100151 // Apply our local logger to the migration instance.
0 commit comments