711 lines
20 KiB
Go
711 lines
20 KiB
Go
|
//+build ignore
|
||
|
|
||
|
/*
|
||
|
* Copyright (c) 2013-2016 The btcsuite developers
|
||
|
* Copyright (c) 2015 The Decred developers
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
package wallet
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/btcsuite/btclog"
|
||
|
|
||
|
"github.com/decred/bitset"
|
||
|
"github.com/decred/dcrutil"
|
||
|
"github.com/decred/dcrwallet/chain"
|
||
|
"github.com/decred/dcrwallet/waddrmgr"
|
||
|
"github.com/decred/dcrwallet/walletdb"
|
||
|
)
|
||
|
|
||
|
// finalScanLength is the final length of accounts to scan for the
|
||
|
// function below.
|
||
|
var finalAcctScanLength = 50
|
||
|
|
||
|
// acctSeekWidth is the number of addresses for both internal and external
|
||
|
// branches to scan to determine whether or not an account exists and should
|
||
|
// be rescanned. This is the tolerance for account gaps as well.
|
||
|
var acctSeekWidth uint32 = 5
|
||
|
|
||
|
// accountIsUsed checks if an account has ever been used by scanning the
|
||
|
// first acctSeekWidth many addresses for usage.
|
||
|
func (w *Wallet) accountIsUsed(account uint32, chainClient *chain.RPCClient) bool {
|
||
|
// Search external branch then internal branch for a used
|
||
|
// address. We need to set the address function to use based
|
||
|
// on whether or not this is the initial sync. The function
|
||
|
// AddressDerivedFromCointype is able to see addresses that
|
||
|
// exists in accounts that have not yet been created, while
|
||
|
// AddressDerivedFromDbAcct can not.
|
||
|
addrFunc := w.Manager.AddressDerivedFromDbAcct
|
||
|
if w.initiallyUnlocked {
|
||
|
addrFunc = w.Manager.AddressDerivedFromCointype
|
||
|
}
|
||
|
|
||
|
for branch := uint32(0); branch < 2; branch++ {
|
||
|
for i := uint32(0); i < acctSeekWidth; i++ {
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = addrFunc(addrmgrNs, i, account, branch)
|
||
|
return err
|
||
|
})
|
||
|
if err != nil {
|
||
|
// Skip erroneous keys, which happen rarely.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
exists, err := chainClient.ExistsAddress(addr)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
if exists {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// bisectLastAcctIndex is a helper function for searching through accounts to
|
||
|
// find the last used account. It uses logarithmic scanning to determine if
|
||
|
// an account has been used.
|
||
|
func (w *Wallet) bisectLastAcctIndex(hi, low int) int {
|
||
|
chainClient, err := w.requireChainClient()
|
||
|
if err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
offset := low
|
||
|
for i := hi - low - 1; i > 0; i /= 2 {
|
||
|
if i+offset+int(acctSeekWidth) < waddrmgr.MaxAddressesPerAccount {
|
||
|
for j := i + offset + int(addrSeekWidth); j >= i+offset; j-- {
|
||
|
if w.accountIsUsed(uint32(j), chainClient) {
|
||
|
return i + offset
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if w.accountIsUsed(uint32(i+offset), chainClient) {
|
||
|
return i + offset
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// findAcctEnd is a helper function for searching for the last used account by
|
||
|
// logarithmic scanning of the account indexes.
|
||
|
func (w *Wallet) findAcctEnd(start, stop int) int {
|
||
|
indexStart := w.bisectLastAcctIndex(stop, start)
|
||
|
indexLast := 0
|
||
|
for {
|
||
|
indexLastStored := indexStart
|
||
|
low := indexLastStored
|
||
|
hi := indexLast + ((indexStart - indexLast) * 2) + 1
|
||
|
indexStart = w.bisectLastAcctIndex(hi, low)
|
||
|
indexLast = indexLastStored
|
||
|
|
||
|
if indexStart == 0 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return indexLast
|
||
|
}
|
||
|
|
||
|
// scanAccountIndex identifies the last used address in an HD keychain of public
|
||
|
// keys. It returns the index of the last used key, along with the address of
|
||
|
// this key.
|
||
|
func (w *Wallet) scanAccountIndex(start int, end int) (uint32, error) {
|
||
|
chainClient, err := w.requireChainClient()
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
// Find the last used account. Scan from it to the end in case there was a
|
||
|
// gap from that position, which is possible. Then, return the account
|
||
|
// in that position.
|
||
|
lastUsed := w.findAcctEnd(start, end)
|
||
|
if lastUsed != 0 {
|
||
|
for i := lastUsed + finalAcctScanLength; i >= lastUsed; i-- {
|
||
|
if w.accountIsUsed(uint32(i), chainClient) {
|
||
|
return uint32(i), nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We can't find any used addresses. The account is
|
||
|
// unused.
|
||
|
return 0, nil
|
||
|
}
|
||
|
|
||
|
// debugScanLength is the final length of keys to scan past the
|
||
|
// last index returned from the logarithmic scanning function
|
||
|
// when creating the debug string of used addresses.
|
||
|
var debugAddrScanLength = 3500
|
||
|
|
||
|
// addrSeekWidth is the number of new addresses to generate and add to the
|
||
|
// address manager when trying to sync up a wallet to the main chain. This
|
||
|
// is the maximum gap introduced by a resyncing as well, and should be less
|
||
|
// than finalScanLength above.
|
||
|
// TODO Optimize the scanning so that rather than overshooting the end address,
|
||
|
// you instead step through addresses incrementally until reaching idx so that
|
||
|
// you don't reach a gap. This can be done by keeping track of where the current
|
||
|
// cursor is and adding addresses in big chunks until you hit the end.
|
||
|
var addrSeekWidth uint32 = 20
|
||
|
|
||
|
// errDerivation is an error type signifying that the waddrmgr failed to
|
||
|
// derive a key.
|
||
|
var errDerivation = fmt.Errorf("failed to derive key")
|
||
|
|
||
|
// scanAddressRange scans backwards from end to start many addresses in the
|
||
|
// account branch, and return the first index that is found on the blockchain.
|
||
|
// If the address doesn't exist, false is returned as the first argument.
|
||
|
func (w *Wallet) scanAddressRange(account uint32, branch uint32, start int,
|
||
|
end int, chainClient *chain.RPCClient) (bool, int, error) {
|
||
|
|
||
|
var addresses []dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addresses, err = w.Manager.AddressesDerivedFromDbAcct(addrmgrNs,
|
||
|
uint32(start), uint32(end+1), account, branch)
|
||
|
if err != nil {
|
||
|
return errDerivation
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return false, 0, err
|
||
|
}
|
||
|
|
||
|
// Whether or not the addresses exist is encoded as a binary
|
||
|
// bitset.
|
||
|
exists, err := chainClient.ExistsAddresses(addresses)
|
||
|
if err != nil {
|
||
|
return false, 0, err
|
||
|
}
|
||
|
existsB, err := hex.DecodeString(exists)
|
||
|
if err != nil {
|
||
|
return false, 0, err
|
||
|
}
|
||
|
set := bitset.Bytes(existsB)
|
||
|
|
||
|
// Prevent a panic when an empty message is passed as a response.
|
||
|
if len(set) == 0 {
|
||
|
return false, 0, nil
|
||
|
}
|
||
|
|
||
|
// Scan backwards and return if we find an address exists.
|
||
|
idx := end
|
||
|
itr := len(addresses) - 1
|
||
|
for idx >= start {
|
||
|
// If the address exists in the mempool or blockchain according
|
||
|
// to the bit set returned, return this index.
|
||
|
if set.Get(itr) {
|
||
|
return true, idx, nil
|
||
|
}
|
||
|
|
||
|
itr--
|
||
|
idx--
|
||
|
}
|
||
|
|
||
|
return false, 0, nil
|
||
|
}
|
||
|
|
||
|
// bisectLastAddrIndex is a helper function for search through addresses.
|
||
|
func (w *Wallet) bisectLastAddrIndex(hi, low int, account uint32, branch uint32) int {
|
||
|
chainClient, err := w.requireChainClient()
|
||
|
if err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// Logarithmically scan address indexes to find the last used
|
||
|
// address index. Each time the algorithm receives an end point,
|
||
|
// scans a chunk of addresses at the end point, and if no
|
||
|
// addresses are found, divides the address index by two and
|
||
|
// repeats until it finds the last used index.
|
||
|
offset := low
|
||
|
for i := hi - low - 1; i > 0; i /= 2 {
|
||
|
if i+offset+int(addrSeekWidth) < waddrmgr.MaxAddressesPerAccount {
|
||
|
start := i + offset
|
||
|
end := i + offset + int(addrSeekWidth)
|
||
|
exists, idx, err := w.scanAddressRange(account, branch, start, end,
|
||
|
chainClient)
|
||
|
// Skip erroneous keys, which happen rarely. Don't skip
|
||
|
// other errors.
|
||
|
if err == errDerivation {
|
||
|
continue
|
||
|
}
|
||
|
if err != nil {
|
||
|
log.Warnf("unexpected error encountered during bisection "+
|
||
|
"scan of account %v, branch %v: %s", account, branch,
|
||
|
err.Error())
|
||
|
return 0
|
||
|
}
|
||
|
if exists {
|
||
|
return idx
|
||
|
}
|
||
|
} else {
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
|
||
|
uint32(i+offset), account, branch)
|
||
|
return err
|
||
|
})
|
||
|
// Skip erroneous keys, which happen rarely.
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
exists, err := chainClient.ExistsAddress(addr)
|
||
|
if err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
if exists {
|
||
|
return i + offset
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// findEnd is a helper function for searching for used addresses.
|
||
|
func (w *Wallet) findAddrEnd(start, stop int, account uint32, branch uint32) int {
|
||
|
indexStart := w.bisectLastAddrIndex(stop, start, account, branch)
|
||
|
indexLast := 0
|
||
|
for {
|
||
|
indexLastStored := indexStart
|
||
|
low := indexLastStored
|
||
|
hi := indexLast + ((indexStart - indexLast) * 2) + 1
|
||
|
indexStart = w.bisectLastAddrIndex(hi, low, account, branch)
|
||
|
indexLast = indexLastStored
|
||
|
|
||
|
if indexStart == 0 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return indexLast
|
||
|
}
|
||
|
|
||
|
// debugAccountAddrGapsString is a debug function that prints a graphical outlook
|
||
|
// of address usage to a string, from the perspective of the daemon.
|
||
|
func debugAccountAddrGapsString(scanBackFrom int, account uint32, branch uint32,
|
||
|
w *Wallet) (string, error) {
|
||
|
|
||
|
chainClient, err := w.requireChainClient()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
var buf bytes.Buffer
|
||
|
str := fmt.Sprintf("Begin debug address scan scanning backwards from "+
|
||
|
"idx %v, account %v, branch %v\n", scanBackFrom, account, branch)
|
||
|
buf.WriteString(str)
|
||
|
firstUsedIndex := 0
|
||
|
for i := scanBackFrom; i > 0; i-- {
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
|
||
|
uint32(i), account, branch)
|
||
|
return err
|
||
|
})
|
||
|
// Skip erroneous keys.
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
exists, err := chainClient.ExistsAddress(addr)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("failed to access chain server: %v",
|
||
|
err.Error())
|
||
|
}
|
||
|
|
||
|
if exists {
|
||
|
firstUsedIndex = i
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
str = fmt.Sprintf("Last used index found: %v\n", firstUsedIndex)
|
||
|
buf.WriteString(str)
|
||
|
|
||
|
batchSize := 50
|
||
|
batches := (firstUsedIndex / batchSize) + 1
|
||
|
lastBatchSize := 0
|
||
|
if firstUsedIndex%batchSize != 0 {
|
||
|
lastBatchSize = firstUsedIndex - ((batches - 1) * batchSize)
|
||
|
}
|
||
|
|
||
|
for i := 0; i < batches; i++ {
|
||
|
str = fmt.Sprintf("%8v", i*batchSize)
|
||
|
buf.WriteString(str)
|
||
|
|
||
|
start := i * batchSize
|
||
|
end := (i + 1) * batchSize
|
||
|
if i == batches-1 {
|
||
|
// Nothing to do because last batch empty.
|
||
|
if lastBatchSize == 0 {
|
||
|
break
|
||
|
}
|
||
|
end = (i*batchSize + lastBatchSize) + 1
|
||
|
}
|
||
|
|
||
|
for j := start; j < end; j++ {
|
||
|
if j%10 == 0 {
|
||
|
buf.WriteString(" ")
|
||
|
}
|
||
|
|
||
|
char := "_"
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
|
||
|
uint32(j), account, branch)
|
||
|
return err
|
||
|
})
|
||
|
if err != nil {
|
||
|
char = "X"
|
||
|
}
|
||
|
|
||
|
exists, err := chainClient.ExistsAddress(addr)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("failed to access chain server: %v",
|
||
|
err.Error())
|
||
|
}
|
||
|
if exists {
|
||
|
char = "#"
|
||
|
}
|
||
|
|
||
|
buf.WriteString(char)
|
||
|
}
|
||
|
|
||
|
buf.WriteString("\n")
|
||
|
}
|
||
|
|
||
|
return buf.String(), nil
|
||
|
}
|
||
|
|
||
|
// scanAddressIndex identifies the last used address in an HD keychain of public
|
||
|
// keys. It returns the index of the last used key, along with the address of
|
||
|
// this key.
|
||
|
func (w *Wallet) scanAddressIndex(start int, end int, account uint32,
|
||
|
branch uint32) (uint32, dcrutil.Address, error) {
|
||
|
chainClient, err := w.requireChainClient()
|
||
|
if err != nil {
|
||
|
return 0, nil, err
|
||
|
}
|
||
|
|
||
|
// Find the last used address. Scan from it to the end in case there was a
|
||
|
// gap from that position, which is possible. Then, return the address
|
||
|
// in that position.
|
||
|
lastUsed := w.findAddrEnd(start, end, account, branch)
|
||
|
|
||
|
// If debug is on, do an exhaustive check and a graphical printout
|
||
|
// of what the used addresses currently look like.
|
||
|
if log.Level() == btclog.DebugLvl || log.Level() == btclog.TraceLvl {
|
||
|
dbgStr, err := debugAccountAddrGapsString(lastUsed+debugAddrScanLength,
|
||
|
account, branch, w)
|
||
|
if err != nil {
|
||
|
log.Debugf("Failed to debug address gaps for account %v, "+
|
||
|
"branch %v: %v", account, branch, err)
|
||
|
} else {
|
||
|
log.Debugf("%v", dbgStr)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there was a last used index, do an exhaustive final scan that
|
||
|
// reexamines the last used addresses and ensures that the final index
|
||
|
// we have found is correct.
|
||
|
if lastUsed != 0 {
|
||
|
start := lastUsed
|
||
|
end := lastUsed + w.addrIdxScanLen
|
||
|
exists, idx, err := w.scanAddressRange(account, branch, start, end,
|
||
|
chainClient)
|
||
|
if err != nil {
|
||
|
return 0, nil, err
|
||
|
}
|
||
|
|
||
|
if exists {
|
||
|
lastUsed = idx
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
|
||
|
uint32(lastUsed), account, branch)
|
||
|
return err
|
||
|
})
|
||
|
if err != nil {
|
||
|
return 0, nil, err
|
||
|
}
|
||
|
return uint32(lastUsed), addr, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// In the case that 0 was returned as the last used address,
|
||
|
// make sure the the 0th address was not used. If it was,
|
||
|
// return this address to let the caller know that this
|
||
|
// 0th address was used.
|
||
|
if lastUsed == 0 {
|
||
|
var addr dcrutil.Address
|
||
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
||
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||
|
var err error
|
||
|
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs, 0,
|
||
|
account, branch)
|
||
|
return err
|
||
|
})
|
||
|
// Skip erroneous keys.
|
||
|
if err != nil {
|
||
|
return 0, nil, err
|
||
|
}
|
||
|
|
||
|
exists, err := chainClient.ExistsAddress(addr)
|
||
|
if err != nil {
|
||
|
return 0, nil, fmt.Errorf("failed to access chain server: %v",
|
||
|
err.Error())
|
||
|
}
|
||
|
|
||
|
if exists {
|
||
|
return 0, addr, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We can't find any used addresses for this account's
|
||
|
// branch.
|
||
|
return 0, nil, nil
|
||
|
}
|
||
|
|
||
|
// rescanActiveAddresses accesses the daemon to discover all the addresses that
|
||
|
// have been used by an HD keychain stemming from this wallet in the default
|
||
|
// account.
|
||
|
func (w *Wallet) rescanActiveAddresses() error {
|
||
|
log.Infof("Beginning a rescan of active addresses using the daemon. " +
|
||
|
"This may take a while.")
|
||
|
|
||
|
// Start by rescanning the accounts and determining what the
|
||
|
// current account index is. This scan should only ever be
|
||
|
// performed if we're restoring our wallet from seed.
|
||
|
lastAcct := uint32(0)
|
||
|
var err error
|
||
|
if w.initiallyUnlocked {
|
||
|
min := 0
|
||
|
max := waddrmgr.MaxAccountNum
|
||
|
lastAcct, err = w.scanAccountIndex(min, max)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||
|
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||
|
lastAcctMgr, err := w.Manager.LastAccount(addrmgrNs)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// The address manager is not synced (wallet has been restored
|
||
|
// from seed?). In this case, spawn the accounts in the address
|
||
|
// manager first. The accounts are named by their respective
|
||
|
// index number, as strings.
|
||
|
if lastAcctMgr < lastAcct {
|
||
|
for i := lastAcctMgr + 1; i <= lastAcct; i++ {
|
||
|
_, err := w.Manager.NewAccount(
|
||
|
addrmgrNs, strconv.Itoa(int(i)))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The account manager has a greater index than the rescan.
|
||
|
// It is likely that the end user created a new account but
|
||
|
// did not use it yet. Rescan it anyway so that the address
|
||
|
// pool is created.
|
||
|
if lastAcctMgr > lastAcct {
|
||
|
lastAcct = lastAcctMgr
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
log.Infof("The last used account was %v. Beginning a rescan for "+
|
||
|
"all active addresses in known accounts.", lastAcct)
|
||
|
|
||
|
// Rescan addresses for the both the internal and external
|
||
|
// branches of the account. Insert a new address pool for
|
||
|
// the respective account and initialize it.
|
||
|
for acct := uint32(0); acct <= lastAcct; acct++ {
|
||
|
var extIdx, intIdx uint32
|
||
|
min := 0
|
||
|
max := waddrmgr.MaxAddressesPerAccount
|
||
|
|
||
|
// Do this for both external (0) and internal (1) branches.
|
||
|
for branch := uint32(0); branch < 2; branch++ {
|
||
|
idx, lastAddr, err := w.scanAddressIndex(min, max, acct, branch)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||
|
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||
|
|
||
|
// If the account is unused, buffer the initial address pool
|
||
|
// by syncing the address manager upstream.
|
||
|
unusedAcct := (lastAddr == nil)
|
||
|
if unusedAcct {
|
||
|
_, err := w.Manager.SyncAccountToAddrIndex(
|
||
|
addrmgrNs, acct, addressPoolBuffer, branch)
|
||
|
if err != nil {
|
||
|
// A ErrSyncToIndex error indicates that we're already
|
||
|
// synced to beyond the end of the account in the
|
||
|
// waddrmgr.
|
||
|
errWaddrmgr, ok := err.(waddrmgr.ManagerError)
|
||
|
if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex {
|
||
|
return fmt.Errorf("failed to create initial waddrmgr "+
|
||
|
"address buffer for the address pool, "+
|
||
|
"account %v, branch %v: %s", acct, branch,
|
||
|
err.Error())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
branchString := "external"
|
||
|
if branch == waddrmgr.InternalBranch {
|
||
|
branchString = "internal"
|
||
|
}
|
||
|
|
||
|
// Fetch the address pool index for this account and
|
||
|
// branch from the database meta bucket.
|
||
|
isInternal := branch == waddrmgr.InternalBranch
|
||
|
oldIdx, err := w.Manager.NextToUseAddrPoolIndex(
|
||
|
addrmgrNs, isInternal, acct)
|
||
|
unexpectedError := false
|
||
|
if err != nil {
|
||
|
mErr, ok := err.(waddrmgr.ManagerError)
|
||
|
if !ok {
|
||
|
unexpectedError = true
|
||
|
} else {
|
||
|
// Skip errors where the account's address index
|
||
|
// has not been store. For this case, oldIdx will
|
||
|
// be the special case 0 which will always be
|
||
|
// skipped in the initialization step below.
|
||
|
if mErr.ErrorCode != waddrmgr.ErrMetaPoolIdxNoExist {
|
||
|
unexpectedError = true
|
||
|
}
|
||
|
}
|
||
|
if unexpectedError {
|
||
|
return fmt.Errorf("got unexpected error trying to "+
|
||
|
"retrieve last known addr index for acct %v, "+
|
||
|
"%s branch: %v", acct, branchString, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the stored index is further along than the sync-to
|
||
|
// index determined by the contents of daemon's addrindex,
|
||
|
// use it to initialize the address pool instead.
|
||
|
nextToUseIdx := idx
|
||
|
if !unusedAcct {
|
||
|
nextToUseIdx++
|
||
|
}
|
||
|
if oldIdx > nextToUseIdx {
|
||
|
nextToUseIdx = oldIdx
|
||
|
}
|
||
|
nextToUseAddr, err := w.Manager.AddressDerivedFromDbAcct(
|
||
|
addrmgrNs, nextToUseIdx, acct, branch)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to derive next address for "+
|
||
|
"account %v, branch %v: %s", acct, branch,
|
||
|
err.Error())
|
||
|
}
|
||
|
|
||
|
// Save these for the address pool startup later.
|
||
|
if isInternal {
|
||
|
intIdx = nextToUseIdx
|
||
|
} else {
|
||
|
extIdx = nextToUseIdx
|
||
|
}
|
||
|
|
||
|
// Synchronize the account manager to our address index plus
|
||
|
// an extra chunk of addresses that are used as a buffer
|
||
|
// in the address pool.
|
||
|
_, err = w.Manager.SyncAccountToAddrIndex(addrmgrNs,
|
||
|
acct, nextToUseIdx+addressPoolBuffer, branch)
|
||
|
if err != nil {
|
||
|
// A ErrSyncToIndex error indicates that we're already
|
||
|
// synced to beyond the end of the account in the
|
||
|
// waddrmgr.
|
||
|
errWaddrmgr, ok := err.(waddrmgr.ManagerError)
|
||
|
if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex {
|
||
|
return fmt.Errorf("couldn't sync %s addresses in "+
|
||
|
"address manager: %v", branchString, err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set the next address in the waddrmgr database so that the
|
||
|
// address pool can synchronize properly after.
|
||
|
err = w.Manager.StoreNextToUseAddress(
|
||
|
addrmgrNs, isInternal, acct, nextToUseIdx)
|
||
|
if err != nil {
|
||
|
log.Errorf("Failed to store next to use pool idx for "+
|
||
|
"%s pool in the manager on init sync: %v",
|
||
|
branchString, err.Error())
|
||
|
}
|
||
|
|
||
|
log.Infof("Successfully synchronized the address manager to "+
|
||
|
"%s address %v (key index %v) for account %v",
|
||
|
branchString,
|
||
|
nextToUseAddr.String(),
|
||
|
nextToUseIdx,
|
||
|
acct)
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pool, err := newAddressPools(acct, intIdx, extIdx, w)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
w.addrPoolsMtx.Lock()
|
||
|
w.addrPools[acct] = pool
|
||
|
w.addrPoolsMtx.Unlock()
|
||
|
}
|
||
|
|
||
|
log.Infof("Successfully synchronized wallet accounts to account "+
|
||
|
"number %v.", lastAcct)
|
||
|
|
||
|
return nil
|
||
|
}
|