wallet/wallet: add new syncToBirthday method

In this commit, we add a new syncToBirthday method to the wallet. This
method intends to sync the wallet's point of the view of the chain until
finding its birthday. Most of the logic found within it is heavily
borrowed from the existing syncWithChain method. This method is
currently unused, but it will end up replacing some of the existing sync
logic in a later commit.

Co-authored-by: Roei Erez <roeierez@gmail.com>
This commit is contained in:
Wilmer Paulino 2019-01-07 18:40:30 -08:00
parent 9ad115360b
commit 089cc747db
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F

View file

@ -697,6 +697,164 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
return w.rescanWithTarget(addrs, unspent, nil)
}
// scanChain is a helper method that scans the chain from the starting height
// until the tip of the chain. The onBlock callback can be used to perform
// certain operations for every block that we process as we scan the chain.
func (w *Wallet) scanChain(startHeight int32,
onBlock func(int32, *chainhash.Hash, *wire.BlockHeader) error) error {
chainClient, err := w.requireChainClient()
if err != nil {
return err
}
// isCurrent is a helper function that we'll use to determine if the
// chain backend is currently synced. When running with a btcd or
// bitcoind backend, It will use the height of the latest checkpoint as
// its lower bound.
var latestCheckptHeight int32
if len(w.chainParams.Checkpoints) > 0 {
latestCheckptHeight = w.chainParams.
Checkpoints[len(w.chainParams.Checkpoints)-1].Height
}
isCurrent := func(bestHeight int32) bool {
switch c := chainClient.(type) {
case *chain.NeutrinoClient:
return c.CS.IsCurrent()
}
return bestHeight >= latestCheckptHeight
}
// Determine the latest height known to the chain backend and begin
// scanning the chain from the start height up until this point.
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return err
}
for height := startHeight; height <= bestHeight; height++ {
hash, err := chainClient.GetBlockHash(int64(height))
if err != nil {
return err
}
header, err := chainClient.GetBlockHeader(hash)
if err != nil {
return err
}
if err := onBlock(height, hash, header); err != nil {
return err
}
// If we've reached our best height and we're not current, we'll
// wait for blocks at tip to ensure we go through all existent
// blocks.
for height == bestHeight && !isCurrent(bestHeight) {
time.Sleep(100 * time.Millisecond)
_, bestHeight, err = chainClient.GetBestBlock()
if err != nil {
return err
}
}
}
return nil
}
// syncToBirthday attempts to sync the wallet's point of view of the chain until
// it finds the first block whose timestamp is above the wallet's birthday. The
// wallet's birthday is already two days in the past of its actual birthday, so
// this is relatively safe to do.
func (w *Wallet) syncToBirthday() (*waddrmgr.BlockStamp, error) {
var birthdayStamp *waddrmgr.BlockStamp
birthday := w.Manager.Birthday()
tx, err := w.db.BeginReadWriteTx()
if err != nil {
return nil, err
}
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
// We'll begin scanning the chain from our last sync point until finding
// the first block with a timestamp greater than our birthday. We'll use
// this block to represent our birthday stamp. errDone is an error we'll
// use to signal that we've found it and no longer need to keep scanning
// the chain.
errDone := errors.New("done")
err = w.scanChain(w.Manager.SyncedTo().Height, func(height int32,
hash *chainhash.Hash, header *wire.BlockHeader) error {
if header.Timestamp.After(birthday) {
log.Debugf("Found birthday block: height=%d, hash=%v",
height, hash)
birthdayStamp = &waddrmgr.BlockStamp{
Hash: *hash,
Height: height,
Timestamp: header.Timestamp,
}
err := w.Manager.SetBirthdayBlock(
ns, *birthdayStamp, true,
)
if err != nil {
return err
}
}
err = w.Manager.SetSyncedTo(ns, &waddrmgr.BlockStamp{
Hash: *hash,
Height: height,
Timestamp: header.Timestamp,
})
if err != nil {
return err
}
// Checkpoint our state every 10K blocks.
if height%10000 == 0 {
if err := tx.Commit(); err != nil {
return err
}
log.Infof("Caught up to height %d", height)
tx, err = w.db.BeginReadWriteTx()
if err != nil {
return err
}
ns = tx.ReadWriteBucket(waddrmgrNamespaceKey)
}
// If we've found our birthday, we can return errDone to signal
// that we should stop scanning the chain and persist our state.
if birthdayStamp != nil {
return errDone
}
return nil
})
if err != nil && err != errDone {
tx.Rollback()
return nil, err
}
// If a birthday stamp has yet to be found, we'll return an error
// indicating so.
if birthdayStamp == nil {
tx.Rollback()
return nil, fmt.Errorf("did not find a suitable birthday "+
"block with a timestamp greater than %v", birthday)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return birthdayStamp, nil
}
// defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the
// default set of key scopes.
func (w *Wallet) defaultScopeManagers() (