wallet: perform recovery on all registered key scopes

In similar fashion to the previous commit, due to a no longer existing
bug within the wallet, it was possible for change addresses to be
created outside of their intended key scope (the default), so wallets
affected by this now need to ensure upon recovery that they scan the
chain for _all_ existing key scopes, rather than just the default ones,
to reflect their proper balance. Through manual testing, it was shown
that the impact of recovering the additional key scopes is negligible in
most cases for both full nodes and light clients.
This commit is contained in:
Wilmer Paulino 2020-03-30 15:33:23 -07:00
parent 1285049923
commit 31c027e19f
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F

View file

@ -637,13 +637,15 @@ func (w *Wallet) recovery(chainClient chain.Interface,
)
// In the event that this recovery is being resumed, we will need to
// repopulate all found addresses from the database. For basic recovery,
// we will only do so for the default scopes.
scopedMgrs, err := w.defaultScopeManagers()
if err != nil {
return err
// repopulate all found addresses from the database. Ideally, for basic
// recovery, we would only do so for the default scopes, but due to a
// bug in which the wallet would create change addresses outside of the
// default scopes, it's necessary to attempt all registered key scopes.
scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() {
scopedMgrs[scopedMgr.Scope()] = scopedMgr
}
err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
txMgrNS := tx.ReadBucket(wtxmgrNamespaceKey)
credits, err := w.TxStore.UnspentOutputs(txMgrNS)
if err != nil {
@ -712,9 +714,9 @@ func (w *Wallet) recovery(chainClient chain.Interface,
return err
}
}
return w.recoverDefaultScopes(
return w.recoverScopedAddresses(
chainClient, tx, ns, recoveryBatch,
recoveryMgr.State(),
recoveryMgr.State(), scopedMgrs,
)
})
if err != nil {
@ -737,48 +739,10 @@ func (w *Wallet) recovery(chainClient chain.Interface,
return nil
}
// defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the
// default set of key scopes.
func (w *Wallet) defaultScopeManagers() (
map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager, error) {
scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
for _, scope := range waddrmgr.DefaultKeyScopes {
scopedMgr, err := w.Manager.FetchScopedKeyManager(scope)
if err != nil {
return nil, err
}
scopedMgrs[scope] = scopedMgr
}
return scopedMgrs, nil
}
// recoverDefaultScopes attempts to recover any addresses belonging to any
// active scoped key managers known to the wallet. Recovery of each scope's
// default account will be done iteratively against the same batch of blocks.
// TODO(conner): parallelize/pipeline/cache intermediate network requests
func (w *Wallet) recoverDefaultScopes(
chainClient chain.Interface,
tx walletdb.ReadWriteTx,
ns walletdb.ReadWriteBucket,
batch []wtxmgr.BlockMeta,
recoveryState *RecoveryState) error {
scopedMgrs, err := w.defaultScopeManagers()
if err != nil {
return err
}
return w.recoverScopedAddresses(
chainClient, tx, ns, batch, recoveryState, scopedMgrs,
)
}
// recoverAccountAddresses scans a range of blocks in attempts to recover any
// recoverScopedAddresses scans a range of blocks in attempts to recover any
// previously used addresses for a particular account derivation path. At a high
// level, the algorithm works as follows:
//
// 1) Ensure internal and external branch horizons are fully expanded.
// 2) Filter the entire range of blocks, stopping if a non-zero number of
// address are contained in a particular block.
@ -786,6 +750,8 @@ func (w *Wallet) recoverDefaultScopes(
// 4) Record any outpoints found in the block that should be watched for spends
// 5) Trim the range of blocks up to and including the one reporting the addrs.
// 6) Repeat from (1) if there are still more blocks in the range.
//
// TODO(conner): parallelize/pipeline/cache intermediate network requests
func (w *Wallet) recoverScopedAddresses(
chainClient chain.Interface,
tx walletdb.ReadWriteTx,