multi-account: support BIP44 account discovery
This commit is contained in:
parent
0410b7ce01
commit
169abd446c
4 changed files with 255 additions and 252 deletions
|
@ -42,6 +42,9 @@ const (
|
|||
// ImportedAddrAccountName is the name of the imported account.
|
||||
ImportedAddrAccountName = "imported"
|
||||
|
||||
// AccountGapLimit is used for account discovery defined in BIP0044
|
||||
AccountGapLimit = 20
|
||||
|
||||
// DefaultAccountNum is the number of the default account.
|
||||
DefaultAccountNum = 0
|
||||
|
||||
|
@ -1527,7 +1530,7 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket,
|
|||
|
||||
// Derive the account key for the first account according our
|
||||
// BIP0044-like derivation.
|
||||
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0)
|
||||
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, DefaultAccountNum)
|
||||
if err != nil {
|
||||
// The seed is unusable if the any of the children in the
|
||||
// required hierarchy can't be derived due to invalid child.
|
||||
|
|
|
@ -116,11 +116,15 @@ type KeyScope struct {
|
|||
// identify a particular child key, when the account and branch can be inferred
|
||||
// from context.
|
||||
type ScopedIndex struct {
|
||||
// Scope is the BIP44 account' used to derive the child key.
|
||||
Scope KeyScope
|
||||
Scope KeyScope
|
||||
Account uint32
|
||||
Branch uint32
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Index is the BIP44 address_index used to derive the child key.
|
||||
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
|
||||
|
@ -625,6 +629,14 @@ func (s *ScopedKeyManager) DeriveFromKeyPathCache(
|
|||
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
|
||||
// scheme) from a given key path. If key derivation isn't possible, then an
|
||||
// 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.
|
||||
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
|
||||
// already been created.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -1472,10 +1501,42 @@ func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket,
|
|||
return err
|
||||
}
|
||||
|
||||
lastAccount, err := fetchLastAccount(ns, &s.scope)
|
||||
if account < lastAccount {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save last account metadata
|
||||
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
|
||||
// account number with the given name. If an account with the same name
|
||||
// already exists, ErrDuplicateAccount will be returned.
|
||||
|
|
|
@ -63,66 +63,45 @@ func (rm *RecoveryManager) Resurrect(ns walletdb.ReadBucket,
|
|||
// First, for each scope that we are recovering, rederive all of the
|
||||
// addresses up to the last found address known to each branch.
|
||||
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)
|
||||
acctProperties, err := scopedMgr.AccountProperties(
|
||||
ns, waddrmgr.DefaultAccountNum,
|
||||
)
|
||||
|
||||
lastAccount, err := scopedMgr.LastAccount(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the external key count, which bounds the indexes we
|
||||
// will need to rederive.
|
||||
externalCount := acctProperties.ExternalKeyCount
|
||||
|
||||
// Walk through all indexes through the last external key,
|
||||
// deriving each address and adding it to the external branch
|
||||
// recovery state's set of addresses to look for.
|
||||
for i := uint32(0); i < externalCount; i++ {
|
||||
keyPath := externalKeyPath(i)
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
|
||||
if err != nil && err != hdkeychain.ErrInvalidChild {
|
||||
for accountIndex, accountState := range scopeState[:lastAccount+1] {
|
||||
log.Infof("Resurrecting addresses for key scope %v, account %v", keyScope, accountIndex)
|
||||
acctProperties, err := scopedMgr.AccountProperties(ns,
|
||||
uint32(accountIndex))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err == hdkeychain.ErrInvalidChild {
|
||||
scopeState.ExternalBranch.MarkInvalidChild(i)
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
// Fetch the key count, which bounds the indexes we
|
||||
// will need to rederive.
|
||||
counts := []uint32{
|
||||
acctProperties.ExternalKeyCount,
|
||||
acctProperties.InternalKeyCount,
|
||||
}
|
||||
|
||||
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)
|
||||
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.
|
||||
for addrIndex := uint32(0); addrIndex < counts[branchIndex]; addrIndex++ {
|
||||
keyPath := keyPath(uint32(accountIndex), uint32(branchIndex), addrIndex)
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
|
||||
if err != nil && err != hdkeychain.ErrInvalidChild {
|
||||
return err
|
||||
} else if err == hdkeychain.ErrInvalidChild {
|
||||
branchState.MarkInvalidChild(addrIndex)
|
||||
continue
|
||||
}
|
||||
branchState.AddAddr(addrIndex, addr.Address())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +181,7 @@ type RecoveryState struct {
|
|||
|
||||
// scopes maintains a map of each requested key scope to its active
|
||||
// RecoveryState.
|
||||
scopes map[waddrmgr.KeyScope]*ScopeRecoveryState
|
||||
scopes map[waddrmgr.KeyScope]ScopeRecoveryState
|
||||
|
||||
// watchedOutPoints contains the set of all outpoints known to the
|
||||
// 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
|
||||
// particular key scope will receive the same recoveryWindow.
|
||||
func NewRecoveryState(recoveryWindow uint32) *RecoveryState {
|
||||
scopes := make(map[waddrmgr.KeyScope]*ScopeRecoveryState)
|
||||
scopes := make(map[waddrmgr.KeyScope]ScopeRecoveryState)
|
||||
|
||||
return &RecoveryState{
|
||||
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
|
||||
// recoveryWindow.
|
||||
func (rs *RecoveryState) StateForScope(
|
||||
keyScope waddrmgr.KeyScope) *ScopeRecoveryState {
|
||||
keyScope waddrmgr.KeyScope) ScopeRecoveryState {
|
||||
|
||||
// If the account recovery state already exists, return it.
|
||||
if scopeState, ok := rs.scopes[keyScope]; ok {
|
||||
return scopeState
|
||||
scopeState, ok := rs.scopes[keyScope]
|
||||
if !ok {
|
||||
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
|
||||
// chosen recovery window.
|
||||
rs.scopes[keyScope] = NewScopeRecoveryState(rs.recoveryWindow)
|
||||
|
||||
return rs.scopes[keyScope]
|
||||
return scopeState
|
||||
}
|
||||
|
||||
// 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
|
||||
// under a particular BIP32 account. Each account tracks both an external and
|
||||
// internal branch recovery state, both of which use the same recovery window.
|
||||
type ScopeRecoveryState struct {
|
||||
// ExternalBranch is the recovery state of addresses generated for
|
||||
// external use, i.e. receiving addresses.
|
||||
AccountBranches [][2]*BranchRecoveryState
|
||||
}
|
||||
// under a BIP32 accounts. Each account tracks both an external and internal
|
||||
// branch recovery state, both of which use the same recovery window.
|
||||
type ScopeRecoveryState []AccountRecoveryState
|
||||
|
||||
// NewScopeRecoveryState initializes an ScopeRecoveryState with the chosen
|
||||
// recovery window.
|
||||
func NewScopeRecoveryState(recoveryWindow uint32) *ScopeRecoveryState {
|
||||
return &ScopeRecoveryState{
|
||||
ExternalBranch: NewBranchRecoveryState(recoveryWindow),
|
||||
InternalBranch: NewBranchRecoveryState(recoveryWindow),
|
||||
}
|
||||
}
|
||||
type AccountRecoveryState []*BranchRecoveryState
|
||||
|
||||
// BranchRecoveryState maintains the required state in-order to properly
|
||||
// recover addresses derived from a particular account's internal or external
|
||||
|
|
308
wallet/wallet.go
308
wallet/wallet.go
|
@ -26,6 +26,7 @@ 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"
|
||||
|
@ -666,16 +667,13 @@ func (w *Wallet) recovery(chainClient chain.Interface,
|
|||
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)
|
||||
for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() {
|
||||
scopedMgrs[scopedMgr.Scope()] = scopedMgr
|
||||
}
|
||||
|
||||
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||||
var credits []wtxmgr.Credit
|
||||
txMgrNS := tx.ReadBucket(wtxmgrNamespaceKey)
|
||||
credits, err := w.TxStore.UnspentOutputs(txMgrNS)
|
||||
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
|
||||
// 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.ProvidePrivPassphrase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Unlock(pass, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Lock()
|
||||
|
||||
var blocks []*waddrmgr.BlockStamp
|
||||
startHeight := w.Manager.SyncedTo().Height + 1
|
||||
for height := startHeight; height <= bestHeight; height++ {
|
||||
|
@ -735,35 +745,43 @@ 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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
return w.recoverScopedAddresses(
|
||||
chainClient, tx, ns, recoveryBatch,
|
||||
recoveryMgr.State(), scopedMgrs,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(recoveryBatch) > 0 {
|
||||
log.Infof("Recovered addresses from blocks "+
|
||||
"%d-%d", recoveryBatch[0].Height,
|
||||
recoveryBatch[len(recoveryBatch)-1].Height)
|
||||
}
|
||||
|
||||
// Clear the batch of all processed blocks to reuse the
|
||||
// same memory for future batches.
|
||||
blocks = blocks[:0]
|
||||
recoveryMgr.ResetBlockBatch()
|
||||
if len(recoveryBatch) != recoveryBatchSize && height != bestHeight {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.recoverScopedAddresses(chainClient, tx, ns,
|
||||
recoveryBatch, recoveryMgr.State(), scopedMgrs,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(recoveryBatch) > 0 {
|
||||
log.Infof("Recovered addresses from blocks "+
|
||||
"%d-%d", recoveryBatch[0].Height,
|
||||
recoveryBatch[len(recoveryBatch)-1].Height)
|
||||
}
|
||||
|
||||
// Clear the batch of all processed blocks to reuse the
|
||||
// same memory for future batches.
|
||||
blocks = blocks[:0]
|
||||
recoveryMgr.ResetBlockBatch()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -795,16 +813,8 @@ func (w *Wallet) recoverScopedAddresses(
|
|||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Scanning %d blocks for recoverable addresses", len(batch))
|
||||
|
||||
expandHorizons:
|
||||
for scope, scopedMgr := range scopedMgrs {
|
||||
scopeState := recoveryState.StateForScope(scope)
|
||||
err := expandScopeHorizons(ns, scopedMgr, scopeState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Infof("Scanning %d blocks for recoverable addresses", len(batch))
|
||||
|
||||
// With the internal and external horizons properly expanded, we now
|
||||
// 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
|
||||
// horizon will be properly extended such that our lookahead always includes the
|
||||
// proper number of valid child keys.
|
||||
func expandScopeHorizons(ns walletdb.ReadWriteBucket,
|
||||
func expandScopeHorizons(
|
||||
ns walletdb.ReadWriteBucket,
|
||||
scopedMgr *waddrmgr.ScopedKeyManager,
|
||||
scopeState *ScopeRecoveryState) error {
|
||||
scopeState ScopeRecoveryState) error {
|
||||
|
||||
// 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 {
|
||||
keyPath := externalKeyPath(childIndex)
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
|
||||
switch {
|
||||
case err == hdkeychain.ErrInvalidChild:
|
||||
// Record the existence of an invalid child with the
|
||||
// external branch's recovery state. This also
|
||||
// increments the branch's horizon so that it accounts
|
||||
// for this skipped child index.
|
||||
scopeState.ExternalBranch.MarkInvalidChild(childIndex)
|
||||
childIndex++
|
||||
continue
|
||||
|
||||
case err != nil:
|
||||
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
|
||||
|
||||
// Register the newly generated external address and child index
|
||||
// with the external branch recovery state.
|
||||
scopeState.ExternalBranch.AddAddr(childIndex, addr.Address())
|
||||
branchKey, err := acctKey.Derive(uint32(branchIndex))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
childIndex++
|
||||
count++
|
||||
}
|
||||
for count < exWindow {
|
||||
kp := keyPath(uint32(accountIndex), uint32(branchIndex), addrIndex)
|
||||
indexKey, err := branchKey.Derive(addrIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addrType := waddrmgr.ScopeAddrMap[scopedMgr.Scope()].ExternalAddrType
|
||||
addr, err := scopedMgr.DeriveFromExtKeys(kp, indexKey, addrType)
|
||||
switch {
|
||||
case err == hdkeychain.ErrInvalidChild:
|
||||
branchState.MarkInvalidChild(addrIndex)
|
||||
addrIndex++
|
||||
continue
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
case err != nil:
|
||||
return err
|
||||
branchState.AddAddr(addrIndex, addr.Address())
|
||||
|
||||
addrIndex++
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return waddrmgr.DerivationPath{
|
||||
InternalAccount: account,
|
||||
|
@ -976,23 +967,22 @@ func newFilterBlocksRequest(batch []wtxmgr.BlockMeta,
|
|||
WatchedOutPoints: recoveryState.WatchedOutPoints(),
|
||||
}
|
||||
|
||||
// Populate the external and internal addresses by merging the addresses
|
||||
// sets belong to all currently tracked scopes.
|
||||
// Populate the addresses by merging the addresses sets belong to all
|
||||
// currently tracked scopes.
|
||||
for scope := range scopedMgrs {
|
||||
scopeState := recoveryState.StateForScope(scope)
|
||||
for index, addr := range scopeState.ExternalBranch.Addrs() {
|
||||
scopedIndex := waddrmgr.ScopedIndex{
|
||||
Scope: scope,
|
||||
Index: index,
|
||||
for accountIndex, accountState := range scopeState {
|
||||
for branchIndex, branchState := range accountState {
|
||||
for addrIndex, addr := range branchState.Addrs() {
|
||||
scopedIndex := waddrmgr.ScopedIndex{
|
||||
Scope: scope,
|
||||
Account: uint32(accountIndex),
|
||||
Branch: uint32(branchIndex),
|
||||
Index: addrIndex,
|
||||
}
|
||||
filterReq.Addresses[scopedIndex] = addr
|
||||
}
|
||||
}
|
||||
filterReq.ExternalAddrs[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,
|
||||
recoveryState *RecoveryState) error {
|
||||
|
||||
// Mark all recovered external addresses as used. This will be done only
|
||||
// for scopes that reported a non-zero number of external addresses in
|
||||
// this block.
|
||||
for scope, indexes := range filterResp.FoundExternalAddrs {
|
||||
// First, report all external child indexes found for this
|
||||
// scope. This ensures that the external last-found index will
|
||||
// be updated to include the maximum child index seen thus far.
|
||||
scopeState := recoveryState.StateForScope(scope)
|
||||
for index := range indexes {
|
||||
scopeState.ExternalBranch.ReportFound(index)
|
||||
}
|
||||
|
||||
scopedMgr := scopedMgrs[scope]
|
||||
|
||||
// Mark all recovered addresses as used. This will be done only for
|
||||
// scopes that reported a non-zero number of addresses in this block.
|
||||
for index := range filterResp.FoundAddresses {
|
||||
scopedMgr := scopedMgrs[index.Scope]
|
||||
// First, report all child indexes found for this scope. This
|
||||
// ensures that the last-found index will be updated to include
|
||||
// the maximum child index seen thus far.
|
||||
scopeState := recoveryState.StateForScope(index.Scope)
|
||||
branchState := scopeState[index.Account][index.Branch]
|
||||
branchState.ReportFound(index.Index)
|
||||
// Now, with all found addresses reported, derive and extend all
|
||||
// external addresses up to and including the current last found
|
||||
// index for this scope.
|
||||
exNextUnfound := scopeState.ExternalBranch.NextUnfound()
|
||||
nextFound := branchState.NextUnfound()
|
||||
|
||||
exLastFound := exNextUnfound
|
||||
if exLastFound > 0 {
|
||||
exLastFound--
|
||||
lastFound := nextFound
|
||||
if lastFound > 0 {
|
||||
lastFound--
|
||||
}
|
||||
|
||||
err := scopedMgr.ExtendExternalAddresses(
|
||||
ns, waddrmgr.DefaultAccountNum, exLastFound,
|
||||
err := scopedMgr.ExtendAddresses(
|
||||
ns, index.Account, index.Branch, lastFound,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, with the scope's addresses extended, we mark used
|
||||
// the external addresses that were found in the block and
|
||||
// belong to this scope.
|
||||
for index := range indexes {
|
||||
addr := scopeState.ExternalBranch.GetAddr(index)
|
||||
err := scopedMgr.MarkUsed(ns, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// the addresses that were found in the block and belong to
|
||||
// this scope.
|
||||
addr := branchState.GetAddr(index.Index)
|
||||
err = scopedMgr.MarkUsed(ns, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all recovered internal addresses as used. This will be done only
|
||||
// for scopes that reported a non-zero number of internal addresses in
|
||||
// this block.
|
||||
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,
|
||||
)
|
||||
var lastAccount uint32
|
||||
for _, scopedMgr := range scopedMgrs {
|
||||
account, err := scopedMgr.LastAccount(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lastAccount < account {
|
||||
lastAccount = account
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, with the scope's addresses extended, we mark used
|
||||
// the internal addresses that were found in the blockand belong
|
||||
// to this scope.
|
||||
for index := range indexes {
|
||||
addr := scopeState.InternalBranch.GetAddr(index)
|
||||
err := scopedMgr.MarkUsed(ns, addr)
|
||||
// Make sure all scopes are extended to the same account.
|
||||
for _, s := range scopedMgrs {
|
||||
for gapAccount := lastAccount; gapAccount >= 0; gapAccount-- {
|
||||
_, err := s.AccountProperties(ns, gapAccount)
|
||||
// If the account exists, we can stop extending.
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
err = s.NewRawAccount(ns, gapAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue