Skip to content

Commit 0346e31

Browse files
committed
tapd+sqlc: separate SQL migrations from code migs
Previously, SQL migrations and code migrations have been designed to be a single migration version, where the SQL migration was run first, followed by a post step migration callback that executed the code migration. Due to a re-definition of the migration design, which separates SQL and code migrations, we need to update tapd to become compatible with the new migration design before updating to the new design. This PR therefore separates the code migrations from migration `33` & `37`, into separate migration files, and adds those two new migrations to the `tapdb/sqlc/migrations` directory. Additionally, since users that have already run the code migrations during migration `33` & `37` will have those migrations re-executed once more after updating to this PR, we test that re-execution of those code migrations will be a no-op for those users, and won't add or change any data in the database.
1 parent 18db746 commit 0346e31

File tree

4 files changed

+192
-7
lines changed

4 files changed

+192
-7
lines changed

tapdb/migrations_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"regexp"
11+
"sort"
1112
"strings"
1213
"testing"
1314

@@ -573,6 +574,10 @@ func TestMigration33(t *testing.T) {
573574

574575
// And now that we have test data inserted, we can migrate to the latest
575576
// version.
577+
// NOTE: the post-migration check was originally run at migration
578+
// version 33, but was later moved to version 48. Targeting the latest
579+
// migration, will execute post-migration check, but at version 48
580+
// instead of 33.
576581
err := db.ExecuteMigrations(TargetLatest, WithPostStepCallbacks(
577582
makePostStepCallbacks(db, postMigrationChecks),
578583
))
@@ -672,6 +677,115 @@ func TestMigration33(t *testing.T) {
672677
)
673678
}
674679

680+
// TestMigration48ScriptKeyTypeReplay makes sure that if the script key type
681+
// backfill already ran for a user (originally at migration 33), the replay at
682+
// migration 48 is a no-op and doesn't rewrite any data.
683+
func TestMigration48ScriptKeyTypeReplay(t *testing.T) {
684+
ctx := context.Background()
685+
686+
db := NewTestDBWithVersion(t, 32)
687+
688+
InsertTestdata(t, db.BaseDB, "migrations_test_00033_dummy_data.sql")
689+
690+
// We simulate that a user previously ran migration 33, which at the
691+
// time also contained the code migration which has now been separated
692+
// to migration 48.
693+
err := db.ExecuteMigrations(TargetVersion(33), WithPostStepCallbacks(
694+
makePostStepCallbacks(db, map[uint]postMigrationCheck{
695+
33: determineAndAssignScriptKeyType,
696+
}),
697+
))
698+
require.NoError(t, err)
699+
700+
const (
701+
key1 = "039c571fffcac1a1a7cd3372bd202ad8562f28e48b90f8a4eb714" +
702+
"eca062f576ee6"
703+
key2 = "029c571fffcac1a1a7cd3372bd202ad8562f28e48b90f8a4eb714" +
704+
"eca062f576ee6"
705+
key3 = "03f9cdf1ff7c9fbb0ea3c8533cd7048994f41ea20a79764469c22" +
706+
"aa18aa6696169"
707+
key4 = "027c79b9b26e463895eef5679d8558942c86c4ad2233adef01bc3" +
708+
"e6d540b3653fe"
709+
key5 = "0350aaeb166f4234650d84a2d8a130987aeaf6950206e0905401e" +
710+
"e74ff3f8d18e6"
711+
key6 = "02248bca7dbb12dcf0b490263a1d521691691aa2541842b7472c8" +
712+
"3acac0e88443b"
713+
)
714+
715+
expectedKeyTypes := map[string]asset.ScriptKeyType{
716+
key1: asset.ScriptKeyUnknown,
717+
key2: asset.ScriptKeyBip86,
718+
key3: asset.ScriptKeyScriptPathExternal,
719+
key4: asset.ScriptKeyTombstone,
720+
key5: asset.ScriptKeyScriptPathChannel,
721+
key6: asset.ScriptKeyBurn,
722+
}
723+
724+
// fetchTypes returns the script key types currently persisted in the
725+
// database keyed by their tweaked hex representation.
726+
fetchTypes := func() map[string]asset.ScriptKeyType {
727+
currentTypes := make(map[string]asset.ScriptKeyType)
728+
729+
for keyHex := range expectedKeyTypes {
730+
keyBytes, err := hex.DecodeString(keyHex)
731+
require.NoError(t, err)
732+
733+
dbKey, err := db.BaseDB.FetchScriptKeyByTweakedKey(
734+
ctx, keyBytes,
735+
)
736+
require.NoError(t, err)
737+
738+
currentTypes[keyHex] =
739+
extractSqlInt16[asset.ScriptKeyType](
740+
dbKey.ScriptKey.KeyType,
741+
)
742+
}
743+
744+
return currentTypes
745+
}
746+
747+
// Verify that the database contains the expected script keys after the
748+
// first migration has been run.
749+
require.Equal(t, expectedKeyTypes, fetchTypes())
750+
751+
// Now let's change the ScriptKey type for one of the entries, to an
752+
// incorrect value. When the code migration is rerun, this value should
753+
// not be changed despite being incorrect, as the replay of the code
754+
// migration won't act on values which have already been assigned.
755+
keyBytes, err := hex.DecodeString(key5)
756+
require.NoError(t, err)
757+
758+
dbKey, err := db.BaseDB.FetchScriptKeyByTweakedKey(
759+
ctx, keyBytes,
760+
)
761+
762+
_, err = db.BaseDB.UpsertScriptKey(ctx, NewScriptKey{
763+
InternalKeyID: dbKey.InternalKey.KeyID,
764+
TweakedScriptKey: dbKey.ScriptKey.TweakedScriptKey,
765+
Tweak: dbKey.ScriptKey.Tweak,
766+
KeyType: sqlInt16(asset.ScriptKeyBip86),
767+
})
768+
require.NoError(t, err)
769+
770+
// Executing the code migration again (now at migration 48) should not
771+
// change or add any new values than the values assigned when the code
772+
// migration was run for migration version 33.
773+
err = db.ExecuteMigrations(TargetLatest, WithPostStepCallbacks(
774+
makePostStepCallbacks(db, postMigrationChecks),
775+
))
776+
require.NoError(t, err)
777+
778+
// As we changed the value for key5 to an incorrect value, we expect
779+
// that the db still contains that, hence not being equal to the
780+
// expectedKeyTypes.
781+
require.NotEqual(t, expectedKeyTypes, fetchTypes())
782+
783+
// If we change the expectedKeyTypes to contain the incorrect value,
784+
// they should however be equal.
785+
expectedKeyTypes[key5] = asset.ScriptKeyBip86
786+
require.Equal(t, expectedKeyTypes, fetchTypes())
787+
}
788+
675789
// TestMigration37 tests that the Golang based post-migration check for the
676790
// asset burn insertion works as expected.
677791
func TestMigration37(t *testing.T) {
@@ -685,6 +799,10 @@ func TestMigration37(t *testing.T) {
685799

686800
// And now that we have test data inserted, we can migrate to the latest
687801
// version.
802+
// NOTE: the post-migration check was originally run at migration
803+
// version 37, but was later moved to version 49. Targeting the latest
804+
// migration, will execute post-migration check, but at version 49
805+
// instead of 37.
688806
err := db.ExecuteMigrations(TargetLatest, WithPostStepCallbacks(
689807
makePostStepCallbacks(db, postMigrationChecks),
690808
))
@@ -696,6 +814,71 @@ func TestMigration37(t *testing.T) {
696814
require.Len(t, burns, 5)
697815
}
698816

817+
// TestMigration49BurnReplay makes sure that if the asset burn code migration
818+
// already ran for a user (originally at migration 37), the replay at migration
819+
// 49 is a no-op and doesn't insert duplicate burns.
820+
func TestMigration49BurnReplay(t *testing.T) {
821+
ctx := context.Background()
822+
823+
db := NewTestDBWithVersion(t, 36)
824+
825+
InsertTestdata(t, db.BaseDB, "migrations_test_00037_dummy_data.sql")
826+
827+
// The test data inserts 3 burns into the database before the migration
828+
// is run. After the migration is run, 2 more entries will be added.
829+
burnsBefore, err := db.QueryBurns(ctx, QueryBurnsFilters{})
830+
require.NoError(t, err)
831+
require.Len(t, burnsBefore, 3)
832+
833+
// We simulate that a user previously ran migration 37, which at the
834+
// time also contained the code migration which has now been separated
835+
// to migration 49.
836+
err = db.ExecuteMigrations(TargetVersion(37), WithPostStepCallbacks(
837+
makePostStepCallbacks(db, map[uint]postMigrationCheck{
838+
37: insertAssetBurns,
839+
}),
840+
))
841+
require.NoError(t, err)
842+
843+
// Since the migration was insertAssetBurns code migration was run for
844+
// version 37, the database should now contain 5 entries.
845+
burnsAfterFirstMigration, err := db.QueryBurns(ctx, QueryBurnsFilters{})
846+
require.NoError(t, err)
847+
require.Len(t, burnsAfterFirstMigration, 5)
848+
849+
normalizeBurns := func(burns []sqlc.QueryBurnsRow) []string {
850+
result := make([]string, 0, len(burns))
851+
852+
for _, burn := range burns {
853+
result = append(result, fmt.Sprintf("%x:%x:%x:%d",
854+
burn.AnchorTxid, burn.AssetID, burn.GroupKey,
855+
burn.Amount))
856+
}
857+
858+
sort.Strings(result)
859+
860+
return result
861+
}
862+
863+
// Execute the rest of the migrations, which will trigger the migration
864+
// version 49 code migration.
865+
err = db.ExecuteMigrations(TargetLatest, WithPostStepCallbacks(
866+
makePostStepCallbacks(db, postMigrationChecks),
867+
))
868+
require.NoError(t, err)
869+
870+
burnsAfter, err := db.QueryBurns(ctx, QueryBurnsFilters{})
871+
require.NoError(t, err)
872+
873+
// Despite that the code migration in migration version 49 was executed
874+
// once more, the asset burns persisted in the database should not have
875+
// changed.
876+
require.Equal(
877+
t, normalizeBurns(burnsAfterFirstMigration),
878+
normalizeBurns(burnsAfter),
879+
)
880+
}
881+
699882
// TestDirtySqliteVersion tests that if a migration fails and leaves an Sqlite
700883
// database backend in a dirty state, any attempts of re-executing migrations on
701884
// the db (i.e. restart tapd), will fail with an error indicating that the

tapdb/post_migration_checks.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ import (
1818
)
1919

2020
const (
21-
// Migration33ScriptKeyType is the version of the migration that
22-
// introduces the script key type.
23-
Migration33ScriptKeyType = 33
21+
// Migration48ScriptKeyType is the version of the code migration that
22+
// runs the script key type detection/backfill.
23+
Migration48ScriptKeyType = 48
2424

25-
// Migration37InsertAssetBurns is the version of the migration that
25+
// Migration49InsertAssetBurns is the version of the code migration that
2626
// inserts the asset burns into the specific asset burns table by
2727
// querying all assets and detecting burns from their witnesses.
28-
Migration37InsertAssetBurns = 37
28+
Migration49InsertAssetBurns = 49
2929
)
3030

3131
// postMigrationCheck is a function type for a function that performs a
@@ -38,8 +38,8 @@ var (
3838
// applied. These functions are used to perform additional checks on the
3939
// database state that are not fully expressible in SQL.
4040
postMigrationChecks = map[uint]postMigrationCheck{
41-
Migration33ScriptKeyType: determineAndAssignScriptKeyType,
42-
Migration37InsertAssetBurns: insertAssetBurns,
41+
Migration48ScriptKeyType: determineAndAssignScriptKeyType,
42+
Migration49InsertAssetBurns: insertAssetBurns,
4343
}
4444
)
4545

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- The file intentionally only contains this comment to ensure the file created and picked up in the migration stream.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- The file intentionally only contains this comment to ensure the file created and picked up in the migration stream.

0 commit comments

Comments
 (0)