Skip to content

Commit d2148ce

Browse files
authored
Merge pull request #973 from Roasbeef/prevent-db-downgrade
tapdb: implement basic migration downgrade protection
2 parents 838206d + cae788c commit d2148ce

File tree

9 files changed

+128
-16
lines changed

9 files changed

+128
-16
lines changed

.github/workflows/main.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ jobs:
8181
- name: Run test vector creation check
8282
run: make test-vector-check
8383

84+
migration-version-check:
85+
name: migration version check
86+
runs-on: ubuntu-latest
87+
steps:
88+
- name: git checkout
89+
uses: actions/checkout@v3
90+
91+
- name: Run migration version check
92+
run: make migration-version-check
93+
8494
########################
8595
# Compilation check.
8696
########################

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ test-vector-check: gen-deterministic-test-vectors
323323
@$(call print, "Checking deterministic test vectors.")
324324
if test -n "$$(git status | grep -e ".json")"; then echo "Test vectors not updated"; git status; git diff; exit 1; fi
325325

326+
migration-version-check:
327+
@$(call print, "Checking migration version.")
328+
./scripts/check-migration-latest-version.sh
329+
326330
clean:
327331
@$(call print, "Cleaning source.$(NC)")
328332
$(RM) coverage.txt

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ require (
4949
google.golang.org/protobuf v1.33.0
5050
gopkg.in/macaroon-bakery.v2 v2.1.0
5151
gopkg.in/macaroon.v2 v2.1.0
52-
modernc.org/sqlite v1.29.8
52+
modernc.org/sqlite v1.30.0
5353
)
5454

5555
require (
@@ -195,7 +195,7 @@ require (
195195
gopkg.in/yaml.v2 v2.4.0 // indirect
196196
gopkg.in/yaml.v3 v3.0.1 // indirect
197197
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
198-
modernc.org/libc v1.49.3 // indirect
198+
modernc.org/libc v1.50.9 // indirect
199199
modernc.org/mathutil v1.6.0 // indirect
200200
modernc.org/memory v1.8.0 // indirect
201201
modernc.org/strutil v1.2.0 // indirect

go.sum

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,6 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
525525
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
526526
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
527527
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
528-
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
529-
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
530528
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
531529
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
532530
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -1143,18 +1141,18 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
11431141
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
11441142
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
11451143
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
1146-
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
1147-
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
1148-
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
1149-
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
1144+
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
1145+
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
1146+
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
1147+
modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=
11501148
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
11511149
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
11521150
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
11531151
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
11541152
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
11551153
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
1156-
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
1157-
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
1154+
modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=
1155+
modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
11581156
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
11591157
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
11601158
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -1163,8 +1161,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
11631161
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
11641162
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
11651163
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
1166-
modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8=
1167-
modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
1164+
modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM=
1165+
modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw=
11681166
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
11691167
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
11701168
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Get the latest version number from the migration file names.
6+
migrations_path="tapdb/sqlc/migrations"
7+
latest_file_version=$(ls -r $migrations_path | grep .up.sql | head -1 | cut -d_ -f1)
8+
9+
# Force base 10 interpretation, getting rid of the leading zeroes.
10+
latest_file_version=$((10#$latest_file_version))
11+
12+
# Check the value in migrations.go.
13+
file_path="tapdb/migrations.go"
14+
latest_code_version=$(grep -oP 'LatestMigrationVersion\s*=\s*\K\d+' "$file_path")
15+
16+
if [ "$latest_file_version" -ne "$latest_code_version" ]; then
17+
echo "Latest migration version in file names: $latest_file_version"
18+
echo "Latest migration version in code: $latest_code_version"
19+
exit 1
20+
fi
21+
22+
echo "Latest migration version in file names and code match: $latest_file_version"

tapdb/migrations.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tapdb
33
import (
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
@@ -34,6 +45,36 @@ var (
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.
3980
type 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.
74115
func 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.

tapdb/migrations_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,15 @@ func TestMigration15(t *testing.T) {
118118
t, wire.TxWitness{{0xbb}}, assets[0].PrevWitnesses[1].TxWitness,
119119
)
120120
}
121+
122+
// TestMigrationDowngrade tests that downgrading the database is prevented.
123+
func TestMigrationDowngrade(t *testing.T) {
124+
// For this test, with the current hard coded latest version.
125+
db := NewTestDBWithVersion(t, LatestMigrationVersion)
126+
127+
// We'll now attempt to execute migrations, targeting the latest
128+
// version. But we'll have the DB think the latest version is actually
129+
// less than the current version. This simulates downgrading.
130+
err := db.ExecuteMigrations(TargetLatest, WithLatestVersion(1))
131+
require.ErrorIs(t, err, ErrMigrationDowngrade)
132+
}

tapdb/postgres.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,14 @@ func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) {
141141

142142
// ExecuteMigrations runs migrations for the Postgres database, depending on the
143143
// target given, either all migrations or up to a given version.
144-
func (s *PostgresStore) ExecuteMigrations(target MigrationTarget) error {
144+
func (s *PostgresStore) ExecuteMigrations(target MigrationTarget,
145+
optFuncs ...MigrateOpt) error {
146+
147+
opts := defaultMigrateOptions()
148+
for _, optFunc := range optFuncs {
149+
optFunc(opts)
150+
}
151+
145152
driver, err := postgres_migrate.WithInstance(
146153
s.DB, &postgres_migrate.Config{},
147154
)
@@ -152,6 +159,7 @@ func (s *PostgresStore) ExecuteMigrations(target MigrationTarget) error {
152159
postgresFS := newReplacerFS(sqlSchemas, postgresSchemaReplacements)
153160
return applyMigrations(
154161
postgresFS, driver, "sqlc/migrations", s.cfg.DBName, target,
162+
opts,
155163
)
156164
}
157165

tapdb/sqlite.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ func NewSqliteStore(cfg *SqliteConfig) (*SqliteStore, error) {
151151

152152
// ExecuteMigrations runs migrations for the sqlite database, depending on the
153153
// target given, either all migrations or up to a given version.
154-
func (s *SqliteStore) ExecuteMigrations(target MigrationTarget) error {
154+
func (s *SqliteStore) ExecuteMigrations(target MigrationTarget,
155+
optFuncs ...MigrateOpt) error {
156+
157+
opts := defaultMigrateOptions()
158+
for _, optFunc := range optFuncs {
159+
optFunc(opts)
160+
}
161+
155162
driver, err := sqlite_migrate.WithInstance(
156163
s.DB, &sqlite_migrate.Config{},
157164
)
@@ -161,7 +168,7 @@ func (s *SqliteStore) ExecuteMigrations(target MigrationTarget) error {
161168

162169
sqliteFS := newReplacerFS(sqlSchemas, sqliteSchemaReplacements)
163170
return applyMigrations(
164-
sqliteFS, driver, "sqlc/migrations", "sqlite", target,
171+
sqliteFS, driver, "sqlc/migrations", "sqlite", target, opts,
165172
)
166173
}
167174

0 commit comments

Comments
 (0)