mirror of
https://github.com/muun/recovery.git
synced 2025-11-11 22:40:16 -05:00
Release v0.1.0
This commit is contained in:
412
vendor/github.com/btcsuite/btcwallet/waddrmgr/migrations.go
generated
vendored
Normal file
412
vendor/github.com/btcsuite/btcwallet/waddrmgr/migrations.go
generated
vendored
Normal file
@@ -0,0 +1,412 @@
|
||||
package waddrmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/walletdb/migration"
|
||||
)
|
||||
|
||||
// versions is a list of the different database versions. The last entry should
|
||||
// reflect the latest database state. If the database happens to be at a version
|
||||
// number lower than the latest, migrations will be performed in order to catch
|
||||
// it up.
|
||||
var versions = []migration.Version{
|
||||
{
|
||||
Number: 2,
|
||||
Migration: upgradeToVersion2,
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Migration: upgradeToVersion5,
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Migration: populateBirthdayBlock,
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Migration: resetSyncedBlockToBirthday,
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Migration: storeMaxReorgDepth,
|
||||
},
|
||||
}
|
||||
|
||||
// getLatestVersion returns the version number of the latest database version.
|
||||
func getLatestVersion() uint32 {
|
||||
return versions[len(versions)-1].Number
|
||||
}
|
||||
|
||||
// MigrationManager is an implementation of the migration.Manager interface that
|
||||
// will be used to handle migrations for the address manager. It exposes the
|
||||
// necessary parameters required to successfully perform migrations.
|
||||
type MigrationManager struct {
|
||||
ns walletdb.ReadWriteBucket
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that MigrationManager implements the
|
||||
// migration.Manager interface.
|
||||
var _ migration.Manager = (*MigrationManager)(nil)
|
||||
|
||||
// NewMigrationManager creates a new migration manager for the address manager.
|
||||
// The given bucket should reflect the top-level bucket in which all of the
|
||||
// address manager's data is contained within.
|
||||
func NewMigrationManager(ns walletdb.ReadWriteBucket) *MigrationManager {
|
||||
return &MigrationManager{ns: ns}
|
||||
}
|
||||
|
||||
// Name returns the name of the service we'll be attempting to upgrade.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Name() string {
|
||||
return "wallet address manager"
|
||||
}
|
||||
|
||||
// Namespace returns the top-level bucket of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Namespace() walletdb.ReadWriteBucket {
|
||||
return m.ns
|
||||
}
|
||||
|
||||
// CurrentVersion returns the current version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) CurrentVersion(ns walletdb.ReadBucket) (uint32, error) {
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return fetchManagerVersion(ns)
|
||||
}
|
||||
|
||||
// SetVersion sets the version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) SetVersion(ns walletdb.ReadWriteBucket,
|
||||
version uint32) error {
|
||||
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return putManagerVersion(m.ns, version)
|
||||
}
|
||||
|
||||
// Versions returns all of the available database versions of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Versions() []migration.Version {
|
||||
return versions
|
||||
}
|
||||
|
||||
// upgradeToVersion2 upgrades the database from version 1 to version 2
|
||||
// 'usedAddrBucketName' a bucket for storing addrs flagged as marked is
|
||||
// initialized and it will be updated on the next rescan.
|
||||
func upgradeToVersion2(ns walletdb.ReadWriteBucket) error {
|
||||
currentMgrVersion := uint32(2)
|
||||
|
||||
_, err := ns.CreateBucketIfNotExists(usedAddrBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create used addresses bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
return putManagerVersion(ns, currentMgrVersion)
|
||||
}
|
||||
|
||||
// upgradeToVersion5 upgrades the database from version 4 to version 5. After
|
||||
// this update, the new ScopedKeyManager features cannot be used. This is due
|
||||
// to the fact that in version 5, we now store the encrypted master private
|
||||
// keys on disk. However, using the BIP0044 key scope, users will still be able
|
||||
// to create old p2pkh addresses.
|
||||
func upgradeToVersion5(ns walletdb.ReadWriteBucket) error {
|
||||
// First, we'll check if there are any existing segwit addresses, which
|
||||
// can't be upgraded to the new version. If so, we abort and warn the
|
||||
// user.
|
||||
err := ns.NestedReadBucket(addrBucketName).ForEach(
|
||||
func(k []byte, v []byte) error {
|
||||
row, err := deserializeAddressRow(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row.addrType > adtScript {
|
||||
return fmt.Errorf("segwit address exists in " +
|
||||
"wallet, can't upgrade from v4 to " +
|
||||
"v5: well, we tried ¯\\_(ツ)_/¯")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next, we'll write out the new database version.
|
||||
if err := putManagerVersion(ns, 5); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First, we'll need to create the new buckets that are used in the new
|
||||
// database version.
|
||||
scopeBucket, err := ns.CreateBucket(scopeBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create scope bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
scopeSchemas, err := ns.CreateBucket(scopeSchemaBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create scope schema bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
// With the buckets created, we can now create the default BIP0044
|
||||
// scope which will be the only scope usable in the database after this
|
||||
// update.
|
||||
scopeKey := scopeToBytes(&KeyScopeBIP0044)
|
||||
scopeSchema := ScopeAddrMap[KeyScopeBIP0044]
|
||||
schemaBytes := scopeSchemaToBytes(&scopeSchema)
|
||||
if err := scopeSchemas.Put(scopeKey[:], schemaBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := createScopedManagerNS(scopeBucket, &KeyScopeBIP0044); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bip44Bucket := scopeBucket.NestedReadWriteBucket(scopeKey[:])
|
||||
|
||||
// With the buckets created, we now need to port over *each* item in
|
||||
// the prior main bucket, into the new default scope.
|
||||
mainBucket := ns.NestedReadWriteBucket(mainBucketName)
|
||||
|
||||
// First, we'll move over the encrypted coin type private and public
|
||||
// keys to the new sub-bucket.
|
||||
encCoinPrivKeys := mainBucket.Get(coinTypePrivKeyName)
|
||||
encCoinPubKeys := mainBucket.Get(coinTypePubKeyName)
|
||||
|
||||
err = bip44Bucket.Put(coinTypePrivKeyName, encCoinPrivKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bip44Bucket.Put(coinTypePubKeyName, encCoinPubKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mainBucket.Delete(coinTypePrivKeyName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mainBucket.Delete(coinTypePubKeyName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next, we'll move over everything that was in the meta bucket to the
|
||||
// meta bucket within the new scope.
|
||||
metaBucket := ns.NestedReadWriteBucket(metaBucketName)
|
||||
lastAccount := metaBucket.Get(lastAccountName)
|
||||
if err := metaBucket.Delete(lastAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scopedMetaBucket := bip44Bucket.NestedReadWriteBucket(metaBucketName)
|
||||
err = scopedMetaBucket.Put(lastAccountName, lastAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, we'll recursively move over a set of keys which were
|
||||
// formerly under the main bucket, into the new scoped buckets. We'll
|
||||
// do so by obtaining a slice of all the keys that we need to modify
|
||||
// and then recursing through each of them, moving both nested buckets
|
||||
// and key/value pairs.
|
||||
keysToMigrate := [][]byte{
|
||||
acctBucketName, addrBucketName, usedAddrBucketName,
|
||||
addrAcctIdxBucketName, acctNameIdxBucketName, acctIDIdxBucketName,
|
||||
}
|
||||
|
||||
// Migrate each bucket recursively.
|
||||
for _, bucketKey := range keysToMigrate {
|
||||
err := migrateRecursively(ns, bip44Bucket, bucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateRecursively moves a nested bucket from one bucket to another,
|
||||
// recursing into nested buckets as required.
|
||||
func migrateRecursively(src, dst walletdb.ReadWriteBucket,
|
||||
bucketKey []byte) error {
|
||||
// Within this bucket key, we'll migrate over, then delete each key.
|
||||
bucketToMigrate := src.NestedReadWriteBucket(bucketKey)
|
||||
newBucket, err := dst.CreateBucketIfNotExists(bucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucketToMigrate.ForEach(func(k, v []byte) error {
|
||||
if nestedBucket := bucketToMigrate.
|
||||
NestedReadBucket(k); nestedBucket != nil {
|
||||
// We have a nested bucket, so recurse into it.
|
||||
return migrateRecursively(bucketToMigrate, newBucket, k)
|
||||
}
|
||||
|
||||
if err := newBucket.Put(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucketToMigrate.Delete(k)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Finally, we'll delete the bucket itself.
|
||||
if err := src.DeleteNestedBucket(bucketKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateBirthdayBlock is a migration that attempts to populate the birthday
|
||||
// block of the wallet. This is needed so that in the event that we need to
|
||||
// perform a rescan of the wallet, we can do so starting from this block, rather
|
||||
// than from the genesis block.
|
||||
//
|
||||
// NOTE: This migration cannot guarantee the correctness of the birthday block
|
||||
// being set as we do not store block timestamps, so a sanity check must be done
|
||||
// upon starting the wallet to ensure we do not potentially miss any relevant
|
||||
// events when rescanning.
|
||||
func populateBirthdayBlock(ns walletdb.ReadWriteBucket) error {
|
||||
// We'll need to jump through some hoops in order to determine the
|
||||
// corresponding block height for our birthday timestamp. Since we do
|
||||
// not store block timestamps, we'll need to estimate our height by
|
||||
// looking at the genesis timestamp and assuming a block occurs every 10
|
||||
// minutes. This can be unsafe, and cause us to actually miss on-chain
|
||||
// events, so a sanity check is done before the wallet attempts to sync
|
||||
// itself.
|
||||
//
|
||||
// We'll start by fetching our birthday timestamp.
|
||||
birthdayTimestamp, err := fetchBirthday(ns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch birthday timestamp: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Setting the wallet's birthday block from timestamp=%v",
|
||||
birthdayTimestamp)
|
||||
|
||||
// Now, we'll need to determine the timestamp of the genesis block for
|
||||
// the corresponding chain.
|
||||
genesisHash, err := fetchBlockHash(ns, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch genesis block hash: %v", err)
|
||||
}
|
||||
|
||||
var genesisTimestamp time.Time
|
||||
switch *genesisHash {
|
||||
case *chaincfg.MainNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.MainNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.TestNet3Params.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.TestNet3Params.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.RegressionNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.RegressionNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.SimNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.SimNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown genesis hash %v", genesisHash)
|
||||
}
|
||||
|
||||
// With the timestamps retrieved, we can estimate a block height by
|
||||
// taking the difference between them and dividing by the average block
|
||||
// time (10 minutes).
|
||||
birthdayHeight := int32((birthdayTimestamp.Sub(genesisTimestamp).Seconds() / 600))
|
||||
|
||||
// Now that we have the height estimate, we can fetch the corresponding
|
||||
// block and set it as our birthday block.
|
||||
birthdayHash, err := fetchBlockHash(ns, birthdayHeight)
|
||||
|
||||
// To ensure we record a height that is known to us from the chain,
|
||||
// we'll make sure this height estimate can be found. Otherwise, we'll
|
||||
// continue subtracting a day worth of blocks until we can find one.
|
||||
for IsError(err, ErrBlockNotFound) {
|
||||
birthdayHeight -= 144
|
||||
if birthdayHeight < 0 {
|
||||
birthdayHeight = 0
|
||||
}
|
||||
birthdayHash, err = fetchBlockHash(ns, birthdayHeight)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Estimated birthday block from timestamp=%v: height=%d, "+
|
||||
"hash=%v", birthdayTimestamp, birthdayHeight, birthdayHash)
|
||||
|
||||
// NOTE: The timestamp of the birthday block isn't set since we do not
|
||||
// store each block's timestamp.
|
||||
return PutBirthdayBlock(ns, BlockStamp{
|
||||
Height: birthdayHeight,
|
||||
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)
|
||||
}
|
||||
|
||||
// storeMaxReorgDepth is a migration responsible for allowing the wallet to only
|
||||
// maintain MaxReorgDepth block hashes stored in order to recover from long
|
||||
// reorgs.
|
||||
func storeMaxReorgDepth(ns walletdb.ReadWriteBucket) error {
|
||||
// Retrieve the current tip of the wallet. We'll use this to determine
|
||||
// the highest stale height we currently have stored within it.
|
||||
syncedTo, err := fetchSyncedTo(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxStaleHeight := staleHeight(syncedTo.Height)
|
||||
|
||||
// It's possible for this height to be non-sensical if we have less than
|
||||
// MaxReorgDepth blocks stored, so we can end the migration now.
|
||||
if maxStaleHeight < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Removing block hash entries beyond maximum reorg depth of "+
|
||||
"%v from current tip %v", MaxReorgDepth, syncedTo.Height)
|
||||
|
||||
// Otherwise, since we currently store all block hashes of the chain
|
||||
// before this migration, we'll remove all stale block hash entries
|
||||
// above the genesis block. This would leave us with only MaxReorgDepth
|
||||
// blocks stored.
|
||||
for height := maxStaleHeight; height > 0; height-- {
|
||||
if err := deleteBlockHash(ns, height); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user