multi-account: support BIP44 account discovery

This commit is contained in:
Roy Lee 2022-09-16 20:10:47 -07:00
parent 0410b7ce01
commit 169abd446c
4 changed files with 255 additions and 252 deletions

View file

@ -42,6 +42,9 @@ const (
// ImportedAddrAccountName is the name of the imported account. // ImportedAddrAccountName is the name of the imported account.
ImportedAddrAccountName = "imported" ImportedAddrAccountName = "imported"
// AccountGapLimit is used for account discovery defined in BIP0044
AccountGapLimit = 20
// DefaultAccountNum is the number of the default account. // DefaultAccountNum is the number of the default account.
DefaultAccountNum = 0 DefaultAccountNum = 0
@ -1527,7 +1530,7 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket,
// Derive the account key for the first account according our // Derive the account key for the first account according our
// BIP0044-like derivation. // BIP0044-like derivation.
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0) acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, DefaultAccountNum)
if err != nil { if err != nil {
// The seed is unusable if the any of the children in the // The seed is unusable if the any of the children in the
// required hierarchy can't be derived due to invalid child. // required hierarchy can't be derived due to invalid child.

View file

@ -116,13 +116,17 @@ type KeyScope struct {
// identify a particular child key, when the account and branch can be inferred // identify a particular child key, when the account and branch can be inferred
// from context. // from context.
type ScopedIndex struct { type ScopedIndex struct {
// Scope is the BIP44 account' used to derive the child key.
Scope KeyScope Scope KeyScope
Account uint32
// Index is the BIP44 address_index used to derive the child key. Branch uint32
Index uint32 Index uint32
} }
func (i ScopedIndex) String() string {
return fmt.Sprintf("%s/%d'/%d/%d",
i.Scope, i.Account, i.Branch, i.Index)
}
// String returns a human readable version describing the keypath encapsulated // String returns a human readable version describing the keypath encapsulated
// by the target key scope. // by the target key scope.
func (k KeyScope) String() string { func (k KeyScope) String() string {
@ -625,6 +629,14 @@ func (s *ScopedKeyManager) DeriveFromKeyPathCache(
return privKey, nil return privKey, nil
} }
func (s *ScopedKeyManager) DeriveFromExtKeys(kp DerivationPath,
derivedKey *hdkeychain.ExtendedKey,
addrType AddressType) (ManagedAddress, error) {
return newManagedAddressFromExtKey(
s, kp, derivedKey, addrType,
)
}
// DeriveFromKeyPath attempts to derive a maximal child key (under the BIP0044 // DeriveFromKeyPath attempts to derive a maximal child key (under the BIP0044
// scheme) from a given key path. If key derivation isn't possible, then an // scheme) from a given key path. If key derivation isn't possible, then an
// error will be returned. // error will be returned.
@ -1105,11 +1117,28 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
// //
// This function MUST be called with the manager lock held for writes. // This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket, func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
account uint32, lastIndex uint32, internal bool) error { account uint32, branch uint32, lastIndex uint32) error {
// The next address can only be generated for accounts that have // The next address can only be generated for accounts that have
// already been created. // already been created.
acctInfo, err := s.loadAccountInfo(ns, account) acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
err = s.newAccount(ns, account, fmt.Sprintf("act:%v", account))
if err != nil {
return err
}
for gapAccount := account - 1; gapAccount >= 0; gapAccount-- {
_, err = s.loadAccountInfo(ns, gapAccount)
if err == nil {
break
}
err = s.newAccount(ns, gapAccount, fmt.Sprintf("act:%v", gapAccount))
if err != nil {
return err
}
}
}
acctInfo, err = s.loadAccountInfo(ns, account)
if err != nil { if err != nil {
return err return err
} }
@ -1472,10 +1501,42 @@ func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket,
return err return err
} }
lastAccount, err := fetchLastAccount(ns, &s.scope)
if account < lastAccount {
return nil
}
// Save last account metadata // Save last account metadata
return putLastAccount(ns, &s.scope, account) return putLastAccount(ns, &s.scope, account)
} }
func (s *ScopedKeyManager) DeriveAccountKey(ns walletdb.ReadWriteBucket,
account uint32) (*hdkeychain.ExtendedKey, error) {
_, coinTypePrivEnc, err := fetchCoinTypeKeys(ns, &s.scope)
if err != nil {
return nil, err
}
// Decrypt the cointype key.
serializedKeyPriv, err := s.rootManager.cryptoKeyPriv.Decrypt(coinTypePrivEnc)
if err != nil {
str := fmt.Sprintf("failed to decrypt cointype serialized private key")
return nil, managerError(ErrLocked, str, err)
}
defer zero.Bytes(serializedKeyPriv)
coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv))
if err != nil {
str := fmt.Sprintf("failed to create cointype extended private key")
return nil, managerError(ErrKeyChain, str, err)
}
defer coinTypeKeyPriv.Zero()
// Derive the account key using the cointype key
return deriveAccountKey(coinTypeKeyPriv, account)
}
// RenameAccount renames an account stored in the manager based on the given // RenameAccount renames an account stored in the manager based on the given
// account number with the given name. If an account with the same name // account number with the given name. If an account with the same name
// already exists, ErrDuplicateAccount will be returned. // already exists, ErrDuplicateAccount will be returned.

View file

@ -63,66 +63,45 @@ func (rm *RecoveryManager) Resurrect(ns walletdb.ReadBucket,
// First, for each scope that we are recovering, rederive all of the // First, for each scope that we are recovering, rederive all of the
// addresses up to the last found address known to each branch. // addresses up to the last found address known to each branch.
for keyScope, scopedMgr := range scopedMgrs { for keyScope, scopedMgr := range scopedMgrs {
// Load the current account properties for this scope, using the
// the default account number.
// TODO(conner): rescan for all created accounts if we allow
// users to use non-default address
scopeState := rm.state.StateForScope(keyScope) scopeState := rm.state.StateForScope(keyScope)
acctProperties, err := scopedMgr.AccountProperties(
ns, waddrmgr.DefaultAccountNum, lastAccount, err := scopedMgr.LastAccount(ns)
)
if err != nil { if err != nil {
return err return err
} }
// Fetch the external key count, which bounds the indexes we for accountIndex, accountState := range scopeState[:lastAccount+1] {
// will need to rederive. log.Infof("Resurrecting addresses for key scope %v, account %v", keyScope, accountIndex)
externalCount := acctProperties.ExternalKeyCount acctProperties, err := scopedMgr.AccountProperties(ns,
uint32(accountIndex))
if err != nil {
return err
}
// Walk through all indexes through the last external key, // Fetch the key count, which bounds the indexes we
// deriving each address and adding it to the external branch // will need to rederive.
counts := []uint32{
acctProperties.ExternalKeyCount,
acctProperties.InternalKeyCount,
}
for branchIndex, branchState := range accountState {
// Walk through all indexes through the last key,
// deriving each address and adding it to the branch
// recovery state's set of addresses to look for. // recovery state's set of addresses to look for.
for i := uint32(0); i < externalCount; i++ { for addrIndex := uint32(0); addrIndex < counts[branchIndex]; addrIndex++ {
keyPath := externalKeyPath(i) keyPath := keyPath(uint32(accountIndex), uint32(branchIndex), addrIndex)
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath) addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
if err != nil && err != hdkeychain.ErrInvalidChild { if err != nil && err != hdkeychain.ErrInvalidChild {
return err return err
} else if err == hdkeychain.ErrInvalidChild { } else if err == hdkeychain.ErrInvalidChild {
scopeState.ExternalBranch.MarkInvalidChild(i) branchState.MarkInvalidChild(addrIndex)
continue continue
} }
branchState.AddAddr(addrIndex, addr.Address())
scopeState.ExternalBranch.AddAddr(i, addr.Address())
} }
// Fetch the internal key count, which bounds the indexes we
// will need to rederive.
internalCount := acctProperties.InternalKeyCount
// Walk through all indexes through the last internal key,
// deriving each address and adding it to the internal branch
// recovery state's set of addresses to look for.
for i := uint32(0); i < internalCount; i++ {
keyPath := internalKeyPath(i)
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
if err != nil && err != hdkeychain.ErrInvalidChild {
return err
} else if err == hdkeychain.ErrInvalidChild {
scopeState.InternalBranch.MarkInvalidChild(i)
continue
} }
scopeState.InternalBranch.AddAddr(i, addr.Address())
}
// The key counts will point to the next key that can be
// derived, so we subtract one to point to last known key. If
// the key count is zero, then no addresses have been found.
if externalCount > 0 {
scopeState.ExternalBranch.ReportFound(externalCount - 1)
}
if internalCount > 0 {
scopeState.InternalBranch.ReportFound(internalCount - 1)
} }
} }
@ -202,7 +181,7 @@ type RecoveryState struct {
// scopes maintains a map of each requested key scope to its active // scopes maintains a map of each requested key scope to its active
// RecoveryState. // RecoveryState.
scopes map[waddrmgr.KeyScope]*ScopeRecoveryState scopes map[waddrmgr.KeyScope]ScopeRecoveryState
// watchedOutPoints contains the set of all outpoints known to the // watchedOutPoints contains the set of all outpoints known to the
// wallet. This is updated iteratively as new outpoints are found during // wallet. This is updated iteratively as new outpoints are found during
@ -214,7 +193,7 @@ type RecoveryState struct {
// recoveryWindow. Each RecoveryState that is subsequently initialized for a // recoveryWindow. Each RecoveryState that is subsequently initialized for a
// particular key scope will receive the same recoveryWindow. // particular key scope will receive the same recoveryWindow.
func NewRecoveryState(recoveryWindow uint32) *RecoveryState { func NewRecoveryState(recoveryWindow uint32) *RecoveryState {
scopes := make(map[waddrmgr.KeyScope]*ScopeRecoveryState) scopes := make(map[waddrmgr.KeyScope]ScopeRecoveryState)
return &RecoveryState{ return &RecoveryState{
recoveryWindow: recoveryWindow, recoveryWindow: recoveryWindow,
@ -227,18 +206,21 @@ func NewRecoveryState(recoveryWindow uint32) *RecoveryState {
// does not already exist, a new one will be generated with the RecoveryState's // does not already exist, a new one will be generated with the RecoveryState's
// recoveryWindow. // recoveryWindow.
func (rs *RecoveryState) StateForScope( func (rs *RecoveryState) StateForScope(
keyScope waddrmgr.KeyScope) *ScopeRecoveryState { keyScope waddrmgr.KeyScope) ScopeRecoveryState {
// If the account recovery state already exists, return it. scopeState, ok := rs.scopes[keyScope]
if scopeState, ok := rs.scopes[keyScope]; ok { if !ok {
return scopeState for i := 0; i < waddrmgr.AccountGapLimit; i++ {
accountState := []*BranchRecoveryState{
NewBranchRecoveryState(rs.recoveryWindow),
NewBranchRecoveryState(rs.recoveryWindow),
}
scopeState = append(scopeState, accountState)
}
rs.scopes[keyScope] = scopeState
} }
// Otherwise, initialize the recovery state for this scope with the return scopeState
// chosen recovery window.
rs.scopes[keyScope] = NewScopeRecoveryState(rs.recoveryWindow)
return rs.scopes[keyScope]
} }
// WatchedOutPoints returns the global set of outpoints that are known to belong // WatchedOutPoints returns the global set of outpoints that are known to belong
@ -256,22 +238,11 @@ func (rs *RecoveryState) AddWatchedOutPoint(outPoint *wire.OutPoint,
} }
// ScopeRecoveryState is used to manage the recovery of addresses generated // ScopeRecoveryState is used to manage the recovery of addresses generated
// under a particular BIP32 account. Each account tracks both an external and // under a BIP32 accounts. Each account tracks both an external and internal
// internal branch recovery state, both of which use the same recovery window. // branch recovery state, both of which use the same recovery window.
type ScopeRecoveryState struct { type ScopeRecoveryState []AccountRecoveryState
// ExternalBranch is the recovery state of addresses generated for
// external use, i.e. receiving addresses.
AccountBranches [][2]*BranchRecoveryState
}
// NewScopeRecoveryState initializes an ScopeRecoveryState with the chosen type AccountRecoveryState []*BranchRecoveryState
// recovery window.
func NewScopeRecoveryState(recoveryWindow uint32) *ScopeRecoveryState {
return &ScopeRecoveryState{
ExternalBranch: NewBranchRecoveryState(recoveryWindow),
InternalBranch: NewBranchRecoveryState(recoveryWindow),
}
}
// BranchRecoveryState maintains the required state in-order to properly // BranchRecoveryState maintains the required state in-order to properly
// recover addresses derived from a particular account's internal or external // recover addresses derived from a particular account's internal or external

View file

@ -26,6 +26,7 @@ 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"
@ -666,16 +667,13 @@ func (w *Wallet) recovery(chainClient chain.Interface,
w.recoveryWindow, recoveryBatchSize, w.chainParams, w.recoveryWindow, recoveryBatchSize, w.chainParams,
) )
// In the event that this recovery is being resumed, we will need to
// 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) scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() { for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() {
scopedMgrs[scopedMgr.Scope()] = scopedMgr scopedMgrs[scopedMgr.Scope()] = scopedMgr
} }
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
var credits []wtxmgr.Credit
txMgrNS := tx.ReadBucket(wtxmgrNamespaceKey) txMgrNS := tx.ReadBucket(wtxmgrNamespaceKey)
credits, err := w.TxStore.UnspentOutputs(txMgrNS) credits, err := w.TxStore.UnspentOutputs(txMgrNS)
if err != nil { if err != nil {
@ -704,6 +702,18 @@ func (w *Wallet) recovery(chainClient chain.Interface,
// NOTE: We purposefully don't update our best height since we assume // 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 // that a wallet rescan will be performed from the wallet's tip, which
// will be of bestHeight after completing the recovery process. // will be of bestHeight after completing the recovery process.
pass, err := prompt.ProvidePrivPassphrase()
if err != nil {
return err
}
err = w.Unlock(pass, nil)
if err != nil {
return err
}
defer w.Lock()
var blocks []*waddrmgr.BlockStamp var blocks []*waddrmgr.BlockStamp
startHeight := w.Manager.SyncedTo().Height + 1 startHeight := w.Manager.SyncedTo().Height + 1
for height := startHeight; height <= bestHeight; height++ { for height := startHeight; height <= bestHeight; height++ {
@ -735,18 +745,27 @@ 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 != bestHeight {
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { continue
}
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
for _, block := range blocks { for _, block := range blocks {
err := w.Manager.SetSyncedTo(ns, block) err = w.Manager.SetSyncedTo(ns, block)
if err != nil { if err != nil {
return err return err
} }
} }
return w.recoverScopedAddresses( for scope, scopedMgr := range scopedMgrs {
chainClient, tx, ns, recoveryBatch, scopeState := recoveryMgr.State().StateForScope(scope)
recoveryMgr.State(), scopedMgrs, err = expandScopeHorizons(ns, scopedMgr, scopeState)
if err != nil {
return err
}
}
return w.recoverScopedAddresses(chainClient, tx, ns,
recoveryBatch, recoveryMgr.State(), scopedMgrs,
) )
}) })
if err != nil { if err != nil {
@ -764,7 +783,6 @@ func (w *Wallet) recovery(chainClient chain.Interface,
blocks = blocks[:0] blocks = blocks[:0]
recoveryMgr.ResetBlockBatch() recoveryMgr.ResetBlockBatch()
} }
}
return nil return nil
} }
@ -795,16 +813,8 @@ func (w *Wallet) recoverScopedAddresses(
return nil return nil
} }
log.Infof("Scanning %d blocks for recoverable addresses", len(batch))
expandHorizons: expandHorizons:
for scope, scopedMgr := range scopedMgrs { log.Infof("Scanning %d blocks for recoverable addresses", len(batch))
scopeState := recoveryState.StateForScope(scope)
err := expandScopeHorizons(ns, scopedMgr, scopeState)
if err != nil {
return err
}
}
// With the internal and external horizons properly expanded, we now // With the internal and external horizons properly expanded, we now
// construct the filter blocks request. The request includes the range // construct the filter blocks request. The request includes the range
@ -887,74 +897,55 @@ expandHorizons:
// persistent state of the wallet. If any invalid child keys are detected, the // persistent state of the wallet. If any invalid child keys are detected, the
// horizon will be properly extended such that our lookahead always includes the // horizon will be properly extended such that our lookahead always includes the
// proper number of valid child keys. // proper number of valid child keys.
func expandScopeHorizons(ns walletdb.ReadWriteBucket, func expandScopeHorizons(
ns walletdb.ReadWriteBucket,
scopedMgr *waddrmgr.ScopedKeyManager, scopedMgr *waddrmgr.ScopedKeyManager,
scopeState *ScopeRecoveryState) error { scopeState ScopeRecoveryState) error {
for accountIndex, accountState := range scopeState {
acctKey, err := scopedMgr.DeriveAccountKey(ns, uint32(accountIndex))
if err != nil {
return err
}
for branchIndex, branchState := range accountState {
exHorizon, exWindow := branchState.ExtendHorizon()
count, addrIndex := uint32(0), exHorizon
branchKey, err := acctKey.Derive(uint32(branchIndex))
if err != nil {
return err
}
// Compute the current external horizon and the number of addresses we
// must derive to ensure we maintain a sufficient recovery window for
// the external branch.
exHorizon, exWindow := scopeState.ExternalBranch.ExtendHorizon()
count, childIndex := uint32(0), exHorizon
for count < exWindow { for count < exWindow {
keyPath := externalKeyPath(childIndex) kp := keyPath(uint32(accountIndex), uint32(branchIndex), addrIndex)
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath) indexKey, err := branchKey.Derive(addrIndex)
if err != nil {
return err
}
addrType := waddrmgr.ScopeAddrMap[scopedMgr.Scope()].ExternalAddrType
addr, err := scopedMgr.DeriveFromExtKeys(kp, indexKey, addrType)
switch { switch {
case err == hdkeychain.ErrInvalidChild: case err == hdkeychain.ErrInvalidChild:
// Record the existence of an invalid child with the branchState.MarkInvalidChild(addrIndex)
// external branch's recovery state. This also addrIndex++
// increments the branch's horizon so that it accounts
// for this skipped child index.
scopeState.ExternalBranch.MarkInvalidChild(childIndex)
childIndex++
continue continue
case err != nil: case err != nil:
return err return err
} }
// Register the newly generated external address and child index branchState.AddAddr(addrIndex, addr.Address())
// with the external branch recovery state.
scopeState.ExternalBranch.AddAddr(childIndex, addr.Address())
childIndex++ addrIndex++
count++ count++
} }
// Compute the current internal horizon and the number of addresses we
// must derive to ensure we maintain a sufficient recovery window for
// the internal branch.
inHorizon, inWindow := scopeState.InternalBranch.ExtendHorizon()
count, childIndex = 0, inHorizon
for count < inWindow {
keyPath := internalKeyPath(childIndex)
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
switch {
case err == hdkeychain.ErrInvalidChild:
// Record the existence of an invalid child with the
// internal branch's recovery state. This also
// increments the branch's horizon so that it accounts
// for this skipped child index.
scopeState.InternalBranch.MarkInvalidChild(childIndex)
childIndex++
continue
case err != nil:
return err
} }
// Register the newly generated internal address and child index
// with the internal branch recovery state.
scopeState.InternalBranch.AddAddr(childIndex, addr.Address())
childIndex++
count++
} }
return nil return nil
} }
// keyPath returns the relative external derivation path /account/branch/index. // keyPath returns the relative derivation path /account/branch/index.
func keyPath(account, branch, index uint32) waddrmgr.DerivationPath { func keyPath(account, branch, index uint32) waddrmgr.DerivationPath {
return waddrmgr.DerivationPath{ return waddrmgr.DerivationPath{
InternalAccount: account, InternalAccount: account,
@ -976,23 +967,22 @@ func newFilterBlocksRequest(batch []wtxmgr.BlockMeta,
WatchedOutPoints: recoveryState.WatchedOutPoints(), WatchedOutPoints: recoveryState.WatchedOutPoints(),
} }
// Populate the external and internal addresses by merging the addresses // Populate the addresses by merging the addresses sets belong to all
// sets belong to all currently tracked scopes. // currently tracked scopes.
for scope := range scopedMgrs { for scope := range scopedMgrs {
scopeState := recoveryState.StateForScope(scope) scopeState := recoveryState.StateForScope(scope)
for index, addr := range scopeState.ExternalBranch.Addrs() { for accountIndex, accountState := range scopeState {
for branchIndex, branchState := range accountState {
for addrIndex, addr := range branchState.Addrs() {
scopedIndex := waddrmgr.ScopedIndex{ scopedIndex := waddrmgr.ScopedIndex{
Scope: scope, Scope: scope,
Index: index, Account: uint32(accountIndex),
Branch: uint32(branchIndex),
Index: addrIndex,
} }
filterReq.ExternalAddrs[scopedIndex] = addr filterReq.Addresses[scopedIndex] = addr
} }
for index, addr := range scopeState.InternalBranch.Addrs() {
scopedIndex := waddrmgr.ScopedIndex{
Scope: scope,
Index: index,
} }
filterReq.InternalAddrs[scopedIndex] = addr
} }
} }
@ -1007,85 +997,63 @@ func extendFoundAddresses(ns walletdb.ReadWriteBucket,
scopedMgrs map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager, scopedMgrs map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager,
recoveryState *RecoveryState) error { recoveryState *RecoveryState) error {
// Mark all recovered external addresses as used. This will be done only // Mark all recovered addresses as used. This will be done only for
// for scopes that reported a non-zero number of external addresses in // scopes that reported a non-zero number of addresses in this block.
// this block. for index := range filterResp.FoundAddresses {
for scope, indexes := range filterResp.FoundExternalAddrs { scopedMgr := scopedMgrs[index.Scope]
// First, report all external child indexes found for this // First, report all child indexes found for this scope. This
// scope. This ensures that the external last-found index will // ensures that the last-found index will be updated to include
// be updated to include the maximum child index seen thus far. // the maximum child index seen thus far.
scopeState := recoveryState.StateForScope(scope) scopeState := recoveryState.StateForScope(index.Scope)
for index := range indexes { branchState := scopeState[index.Account][index.Branch]
scopeState.ExternalBranch.ReportFound(index) branchState.ReportFound(index.Index)
}
scopedMgr := scopedMgrs[scope]
// Now, with all found addresses reported, derive and extend all // Now, with all found addresses reported, derive and extend all
// external addresses up to and including the current last found // external addresses up to and including the current last found
// index for this scope. // index for this scope.
exNextUnfound := scopeState.ExternalBranch.NextUnfound() nextFound := branchState.NextUnfound()
exLastFound := exNextUnfound lastFound := nextFound
if exLastFound > 0 { if lastFound > 0 {
exLastFound-- lastFound--
} }
err := scopedMgr.ExtendExternalAddresses( err := scopedMgr.ExtendAddresses(
ns, waddrmgr.DefaultAccountNum, exLastFound, ns, index.Account, index.Branch, lastFound,
) )
if err != nil { if err != nil {
return err return err
} }
// Finally, with the scope's addresses extended, we mark used // Finally, with the scope's addresses extended, we mark used
// the external addresses that were found in the block and // the addresses that were found in the block and belong to
// belong to this scope. // this scope.
for index := range indexes { addr := branchState.GetAddr(index.Index)
addr := scopeState.ExternalBranch.GetAddr(index) err = scopedMgr.MarkUsed(ns, addr)
err := scopedMgr.MarkUsed(ns, addr)
if err != nil { if err != nil {
return err return err
} }
} }
}
// Mark all recovered internal addresses as used. This will be done only var lastAccount uint32
// for scopes that reported a non-zero number of internal addresses in for _, scopedMgr := range scopedMgrs {
// this block. account, err := scopedMgr.LastAccount(ns)
for scope, indexes := range filterResp.FoundInternalAddrs {
// First, report all internal child indexes found for this
// scope. This ensures that the internal last-found index will
// be updated to include the maximum child index seen thus far.
scopeState := recoveryState.StateForScope(scope)
for index := range indexes {
scopeState.InternalBranch.ReportFound(index)
}
scopedMgr := scopedMgrs[scope]
// Now, with all found addresses reported, derive and extend all
// internal addresses up to and including the current last found
// index for this scope.
inNextUnfound := scopeState.InternalBranch.NextUnfound()
inLastFound := inNextUnfound
if inLastFound > 0 {
inLastFound--
}
err := scopedMgr.ExtendInternalAddresses(
ns, waddrmgr.DefaultAccountNum, inLastFound,
)
if err != nil { if err != nil {
return err return err
} }
if lastAccount < account {
lastAccount = account
}
}
// Finally, with the scope's addresses extended, we mark used // Make sure all scopes are extended to the same account.
// the internal addresses that were found in the blockand belong for _, s := range scopedMgrs {
// to this scope. for gapAccount := lastAccount; gapAccount >= 0; gapAccount-- {
for index := range indexes { _, err := s.AccountProperties(ns, gapAccount)
addr := scopeState.InternalBranch.GetAddr(index) // If the account exists, we can stop extending.
err := scopedMgr.MarkUsed(ns, addr) if err == nil {
break
}
err = s.NewRawAccount(ns, gapAccount)
if err != nil { if err != nil {
return err return err
} }