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 c6842ef6fb
commit 67c9c48940

View file

@ -26,7 +26,6 @@ import (
btcutil "github.com/lbryio/lbcutil"
"github.com/lbryio/lbcutil/hdkeychain"
"github.com/lbryio/lbcwallet/chain"
"github.com/lbryio/lbcwallet/internal/prompt"
"github.com/lbryio/lbcwallet/waddrmgr"
"github.com/lbryio/lbcwallet/wallet/txauthor"
"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
// so now.
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: "+
"%v", err)
}
@ -638,13 +637,12 @@ func locateBirthdayBlock(chainClient chainConn,
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
// would indicate resuming a recovery after a restart.
func (w *Wallet) recovery(chainClient chain.Interface,
birthdayBlock *waddrmgr.BlockStamp) error {
func (w *Wallet) Recovery(chainClient chain.Interface) error {
log.Infof("RECOVERY MODE ENABLED -- rescanning for used addresses "+
log.Infof("Recovery for used addresses "+
"with recovery_window=%d", w.recoveryWindow)
// 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
}
// Fetch the best height from the backend to determine when we should
// stop.
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return err
return nil
}
// Now we can begin scanning the chain from the wallet's current tip to
// ensure we properly handle restarts. Since the recovery process itself
// 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.
func (w *Wallet) RescanBlockchain(chainClient chain.Interface,
startHeight int32, stopHeight int32) (int32, int32, error) {
pass, err := prompt.Passphrase(false)
if err != nil {
return err
}
log.Infof("Rescanning blockchain from block %d to %d "+
"with recovery_window=%d", startHeight, stopHeight,
w.recoveryWindow)
err = w.Unlock(pass, nil)
if err != nil {
return err
defer log.Infof("Rescan blockchain done")
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
startHeight := w.Manager.SyncedTo().Height + 1
for height := startHeight; height <= bestHeight; height++ {
for height := startHeight; height <= stopHeight; height++ {
hash, err := chainClient.GetBlockHash(int64(height))
if err != nil {
return err
return startHeight, stopHeight, err
}
header, err := chainClient.GetBlockHeader(hash)
if err != nil {
return err
return startHeight, stopHeight, err
}
blocks = append(blocks, &waddrmgr.BlockStamp{
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
// if our birthday is after our reorg safe height, so we'll make
// sure to not add those to the batch.
if height >= birthdayBlock.Height {
if height >= startHeight {
recoveryMgr.AddToBlockBatch(
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
// state to disk.
recoveryBatch := recoveryMgr.BlockBatch()
if len(recoveryBatch) != recoveryBatchSize && height != bestHeight {
if len(recoveryBatch) != recoveryBatchSize && height != stopHeight {
continue
}
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
for _, block := range blocks {
err = w.Manager.SetSyncedTo(ns, block)
if err != nil {
return err
}
}
for scope, scopedMgr := range scopedMgrs {
scopeState := recoveryMgr.State().StateForScope(scope)
err = expandScopeHorizons(ns, scopedMgr, scopeState)
@ -755,7 +739,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
)
})
if err != nil {
return err
return startHeight, stopHeight, err
}
if len(recoveryBatch) > 0 {
@ -769,8 +753,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
blocks = blocks[:0]
recoveryMgr.ResetBlockBatch()
}
return nil
return startHeight, stopHeight, nil
}
// recoverScopedAddresses scans a range of blocks in attempts to recover any