From 089cc747db3c573b07fdae8e16b311979bb4d896 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 7 Jan 2019 18:40:30 -0800 Subject: [PATCH] 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 --- wallet/wallet.go | 158 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/wallet/wallet.go b/wallet/wallet.go index a50dbcf..6afb508 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -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() (