wallet: break recovery() to recovery() and rescanblockchain()

Now the recovery, which runs at startup, only scans for known
addresses that were generated and recorded by this wallet.

The coming rescanblockchain RPC implementation, which requires the
wallet to be unlocked, does account discovery.
This commit is contained in:
Roy Lee 2022-10-27 00:46:13 -07:00
parent e03ce4c6d5
commit fe6f28d469

View file

@ -26,7 +26,6 @@ import (
btcutil "github.com/lbryio/lbcutil" btcutil "github.com/lbryio/lbcutil"
"github.com/lbryio/lbcutil/hdkeychain" "github.com/lbryio/lbcutil/hdkeychain"
"github.com/lbryio/lbcwallet/chain" "github.com/lbryio/lbcwallet/chain"
"github.com/lbryio/lbcwallet/internal/prompt"
"github.com/lbryio/lbcwallet/waddrmgr" "github.com/lbryio/lbcwallet/waddrmgr"
"github.com/lbryio/lbcwallet/wallet/txauthor" "github.com/lbryio/lbcwallet/wallet/txauthor"
"github.com/lbryio/lbcwallet/wallet/txrules" "github.com/lbryio/lbcwallet/wallet/txrules"
@ -407,7 +406,7 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
// If the wallet requested an on-chain recovery of its funds, we'll do // If the wallet requested an on-chain recovery of its funds, we'll do
// so now. // so now.
if w.recoveryWindow > 0 { if w.recoveryWindow > 0 {
if err := w.recovery(chainClient, birthdayStamp); err != nil { if err := w.Recovery(chainClient); err != nil {
return fmt.Errorf("unable to perform wallet recovery: "+ return fmt.Errorf("unable to perform wallet recovery: "+
"%v", err) "%v", err)
} }
@ -638,13 +637,12 @@ func locateBirthdayBlock(chainClient chainConn,
return birthdayBlock, nil return birthdayBlock, nil
} }
// recovery attempts to recover any unspent outputs that pay to any of our // Recovery attempts to recover any unspent outputs that pay to any of our
// addresses starting from our birthday, or the wallet's tip (if higher), which // addresses starting from our birthday, or the wallet's tip (if higher), which
// would indicate resuming a recovery after a restart. // would indicate resuming a recovery after a restart.
func (w *Wallet) recovery(chainClient chain.Interface, func (w *Wallet) Recovery(chainClient chain.Interface) error {
birthdayBlock *waddrmgr.BlockStamp) error {
log.Infof("RECOVERY MODE ENABLED -- rescanning for used addresses "+ log.Infof("Recovery for used addresses "+
"with recovery_window=%d", w.recoveryWindow) "with recovery_window=%d", w.recoveryWindow)
// We'll initialize the recovery manager with a default batch size of // We'll initialize the recovery manager with a default batch size of
@ -672,44 +670,36 @@ func (w *Wallet) recovery(chainClient chain.Interface,
return err return err
} }
// Fetch the best height from the backend to determine when we should return nil
// stop.
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return err
} }
// Now we can begin scanning the chain from the wallet's current tip to func (w *Wallet) RescanBlockchain(chainClient chain.Interface,
// ensure we properly handle restarts. Since the recovery process itself startHeight int32, stopHeight int32) (int32, int32, error) {
// acts as rescan, we'll also update our wallet's synced state along the
// way to reflect the blocks we process and prevent rescanning them
// later on.
//
// NOTE: We purposefully don't update our best height since we assume
// that a wallet rescan will be performed from the wallet's tip, which
// will be of bestHeight after completing the recovery process.
pass, err := prompt.Passphrase(false) log.Infof("Rescanning blockchain from block %d to %d "+
if err != nil { "with recovery_window=%d", startHeight, stopHeight,
return err w.recoveryWindow)
}
err = w.Unlock(pass, nil) defer log.Infof("Rescan blockchain done")
if err != nil {
return err recoveryMgr := NewRecoveryManager(
w.recoveryWindow, recoveryBatchSize, w.chainParams,
)
scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() {
scopedMgrs[scopedMgr.Scope()] = scopedMgr
} }
defer w.Lock()
var blocks []*waddrmgr.BlockStamp var blocks []*waddrmgr.BlockStamp
startHeight := w.Manager.SyncedTo().Height + 1 for height := startHeight; height <= stopHeight; height++ {
for height := startHeight; height <= bestHeight; height++ {
hash, err := chainClient.GetBlockHash(int64(height)) hash, err := chainClient.GetBlockHash(int64(height))
if err != nil { if err != nil {
return err return startHeight, stopHeight, err
} }
header, err := chainClient.GetBlockHeader(hash) header, err := chainClient.GetBlockHeader(hash)
if err != nil { if err != nil {
return err return startHeight, stopHeight, err
} }
blocks = append(blocks, &waddrmgr.BlockStamp{ blocks = append(blocks, &waddrmgr.BlockStamp{
Hash: *hash, Hash: *hash,
@ -720,7 +710,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
// It's possible for us to run into blocks before our birthday // It's possible for us to run into blocks before our birthday
// if our birthday is after our reorg safe height, so we'll make // if our birthday is after our reorg safe height, so we'll make
// sure to not add those to the batch. // sure to not add those to the batch.
if height >= birthdayBlock.Height { if height >= startHeight {
recoveryMgr.AddToBlockBatch( recoveryMgr.AddToBlockBatch(
hash, height, header.Timestamp, hash, height, header.Timestamp,
) )
@ -731,18 +721,12 @@ func (w *Wallet) recovery(chainClient chain.Interface,
// the recovery batch size, so we can proceed to commit our // the recovery batch size, so we can proceed to commit our
// state to disk. // state to disk.
recoveryBatch := recoveryMgr.BlockBatch() recoveryBatch := recoveryMgr.BlockBatch()
if len(recoveryBatch) != recoveryBatchSize && height != bestHeight { if len(recoveryBatch) != recoveryBatchSize && height != stopHeight {
continue continue
} }
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
for _, block := range blocks {
err = w.Manager.SetSyncedTo(ns, block)
if err != nil {
return err
}
}
for scope, scopedMgr := range scopedMgrs { for scope, scopedMgr := range scopedMgrs {
scopeState := recoveryMgr.State().StateForScope(scope) scopeState := recoveryMgr.State().StateForScope(scope)
err = expandScopeHorizons(ns, scopedMgr, scopeState) err = expandScopeHorizons(ns, scopedMgr, scopeState)
@ -755,7 +739,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
) )
}) })
if err != nil { if err != nil {
return err return startHeight, stopHeight, err
} }
if len(recoveryBatch) > 0 { if len(recoveryBatch) > 0 {
@ -769,8 +753,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
blocks = blocks[:0] blocks = blocks[:0]
recoveryMgr.ResetBlockBatch() recoveryMgr.ResetBlockBatch()
} }
return startHeight, stopHeight, nil
return nil
} }
// recoverScopedAddresses scans a range of blocks in attempts to recover any // recoverScopedAddresses scans a range of blocks in attempts to recover any