diff --git a/waddrmgr/migrations.go b/waddrmgr/migrations.go index 826f2be..e9aa5f4 100644 --- a/waddrmgr/migrations.go +++ b/waddrmgr/migrations.go @@ -1,6 +1,7 @@ package waddrmgr import ( + "errors" "fmt" "time" @@ -26,6 +27,10 @@ var versions = []migration.Version{ Number: 6, Migration: populateBirthdayBlock, }, + { + Number: 7, + Migration: resetSyncedBlockToBirthday, + }, } // getLatestVersion returns the version number of the latest database version. @@ -350,3 +355,20 @@ func populateBirthdayBlock(ns walletdb.ReadWriteBucket) error { Hash: *birthdayHash, }) } + +// resetSyncedBlockToBirthday is a migration that resets the wallet's currently +// synced block to its birthday block. This essentially serves as a migration to +// force a rescan of the wallet. +func resetSyncedBlockToBirthday(ns walletdb.ReadWriteBucket) error { + syncBucket := ns.NestedReadWriteBucket(syncBucketName) + if syncBucket == nil { + return errors.New("sync bucket does not exist") + } + + birthdayBlock, err := fetchBirthdayBlock(ns) + if err != nil { + return err + } + + return putSyncedTo(ns, &birthdayBlock) +} diff --git a/waddrmgr/migrations_test.go b/waddrmgr/migrations_test.go index ea0408b..632fc02 100644 --- a/waddrmgr/migrations_test.go +++ b/waddrmgr/migrations_test.go @@ -215,3 +215,84 @@ func TestMigrationPopulateBirthdayBlockEstimateTooFar(t *testing.T) { false, ) } + +// TestMigrationResetSyncedBlockToBirthday ensures that the wallet properly sees +// its synced to block as the birthday block after resetting it. +func TestMigrationResetSyncedBlockToBirthday(t *testing.T) { + t.Parallel() + + var birthdayBlock BlockStamp + beforeMigration := func(ns walletdb.ReadWriteBucket) error { + // To test this migration, we'll assume we're synced to a chain + // of 100 blocks, with our birthday being the 50th block. + block := &BlockStamp{} + for i := int32(1); i < 100; i++ { + block.Height = i + blockHash := bytes.Repeat([]byte(string(i)), 32) + copy(block.Hash[:], blockHash) + if err := putSyncedTo(ns, block); err != nil { + return err + } + } + + const birthdayHeight = 50 + birthdayHash, err := fetchBlockHash(ns, birthdayHeight) + if err != nil { + return err + } + + birthdayBlock = BlockStamp{ + Hash: *birthdayHash, Height: birthdayHeight, + } + + return putBirthdayBlock(ns, birthdayBlock) + } + + afterMigration := func(ns walletdb.ReadWriteBucket) error { + // After the migration has succeeded, we should see that the + // database's synced block now reflects the birthday block. + syncedBlock, err := fetchSyncedTo(ns) + if err != nil { + return err + } + + if syncedBlock.Height != birthdayBlock.Height { + return fmt.Errorf("expected synced block height %d, "+ + "got %d", birthdayBlock.Height, + syncedBlock.Height) + } + if !syncedBlock.Hash.IsEqual(&birthdayBlock.Hash) { + return fmt.Errorf("expected synced block height %v, "+ + "got %v", birthdayBlock.Hash, syncedBlock.Hash) + } + + return nil + } + + // We can now apply the migration and expect it not to fail. + applyMigration( + t, beforeMigration, afterMigration, resetSyncedBlockToBirthday, + false, + ) +} + +// TestMigrationResetSyncedBlockToBirthdayWithNoBirthdayBlock ensures that we +// cannot reset our synced to block to our birthday block if one isn't +// available. +func TestMigrationResetSyncedBlockToBirthdayWithNoBirthdayBlock(t *testing.T) { + t.Parallel() + + // To replicate the scenario where the database is not aware of a + // birthday block, we won't set one. This should cause the migration to + // fail. + beforeMigration := func(walletdb.ReadWriteBucket) error { + return nil + } + afterMigration := func(walletdb.ReadWriteBucket) error { + return nil + } + applyMigration( + t, beforeMigration, afterMigration, resetSyncedBlockToBirthday, + true, + ) +}