waddrmgr/migrations: add migration to force rescan from birthday block

In this commit, we add a migration to force a rescan of users' wallets
starting from their birthday block to ensure that their balance is
reflected correctly as it is on-chain. This was inspired by the recent
bug discovered where the wallet would not watch for the confirmation of
a relevant transaction.
This commit is contained in:
Wilmer Paulino 2018-11-07 17:48:04 -08:00
parent ae31984630
commit 0424fd22ec
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
2 changed files with 103 additions and 0 deletions

View file

@ -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)
}

View file

@ -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,
)
}