Merge pull request #536 from cfromknecht/birthday-block-rescan

wallet: start initial rescan from birthday block
This commit is contained in:
Olaoluwa Osuntokun 2018-09-03 18:05:40 -07:00 committed by GitHub
commit 8ae4afc701
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 14 deletions

View file

@ -245,6 +245,14 @@ out:
// current best block in the main chain, and is considered an initial sync
// rescan.
func (w *Wallet) Rescan(addrs []btcutil.Address, unspent []wtxmgr.Credit) error {
return w.rescanWithTarget(addrs, unspent, nil)
}
// rescanWithTarget performs a rescan starting at the optional startStamp. If
// none is provided, the rescan will begin from the manager's sync tip.
func (w *Wallet) rescanWithTarget(addrs []btcutil.Address,
unspent []wtxmgr.Credit, startStamp *waddrmgr.BlockStamp) error {
outpoints := make(map[wire.OutPoint]btcutil.Address, len(unspent))
for _, output := range unspent {
_, outputAddrs, _, err := txscript.ExtractPkScriptAddrs(
@ -257,11 +265,18 @@ func (w *Wallet) Rescan(addrs []btcutil.Address, unspent []wtxmgr.Credit) error
outpoints[output.OutPoint] = outputAddrs[0]
}
// If a start block stamp was provided, we will use that as the initial
// starting point for the rescan.
if startStamp == nil {
startStamp = &waddrmgr.BlockStamp{}
*startStamp = w.Manager.SyncedTo()
}
job := &RescanJob{
InitialSync: true,
Addrs: addrs,
OutPoints: outpoints,
BlockStamp: w.Manager.SyncedTo(),
BlockStamp: *startStamp,
}
// Submit merged job and block until rescan completes.

View file

@ -345,6 +345,13 @@ func (w *Wallet) syncWithChain() error {
isRecovery := w.recoveryWindow > 0
isInitialSync := len(addrs) == 0 && len(unspent) == 0 &&
startHeight == 0
birthday := w.Manager.Birthday()
// If an initial sync is attempted, we will try and find the block stamp
// of the first block past our birthday. This will be fed into the
// rescan to ensure we catch transactions that are sent while performing
// the initial sync.
var birthdayStamp *waddrmgr.BlockStamp
// TODO(jrick): How should this handle a synced height earlier than
// the chain server best block?
@ -470,14 +477,36 @@ func (w *Wallet) syncWithChain() error {
}
// Check to see if this header's timestamp has surpassed
// our birthday. If we are in recovery mode and the
// check passes, we will add this block to our list of
// blocks to scan for recovered addresses.
// our birthday or if we've surpassed one previously.
timestamp := header.Timestamp
if isRecovery && timestamp.After(w.Manager.Birthday()) {
recoveryMgr.AddToBlockBatch(
hash, height, timestamp,
)
if timestamp.After(birthday) || birthdayStamp != nil {
// If this is the first block past our birthday,
// record the block stamp so that we can use
// this as the starting point for the rescan.
// This will ensure we don't miss transactions
// that are sent to the wallet during an initial
// sync.
//
// NOTE: The birthday persisted by the wallet is
// two days before the actual wallet birthday,
// to deal with potentially inaccurate header
// timestamps.
if birthdayStamp == nil {
birthdayStamp = &waddrmgr.BlockStamp{
Height: height,
Hash: *hash,
Timestamp: timestamp,
}
}
// If we are in recovery mode and the check
// passes, we will add this block to our list of
// blocks to scan for recovered addresses.
if isRecovery {
recoveryMgr.AddToBlockBatch(
hash, height, timestamp,
)
}
}
err = w.Manager.SetSyncedTo(ns, &waddrmgr.BlockStamp{
@ -566,11 +595,11 @@ func (w *Wallet) syncWithChain() error {
// Compare previously-seen blocks against the chain server. If any of
// these blocks no longer exist, rollback all of the missing blocks
// before catching up with the rescan.
rollback := false
rollbackStamp := w.Manager.SyncedTo()
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
rollback := false
rollbackStamp := w.Manager.SyncedTo()
for height := rollbackStamp.Height; true; height-- {
hash, err := w.Manager.BlockHash(addrmgrNs, height)
if err != nil {
@ -594,14 +623,15 @@ func (w *Wallet) syncWithChain() error {
}
rollback = true
}
if rollback {
err := w.Manager.SetSyncedTo(addrmgrNs, &rollbackStamp)
if err != nil {
return err
}
// Rollback unconfirms transactions at and beyond the passed
// height, so add one to the new synced-to height to prevent
// unconfirming txs from the synced-to block.
// Rollback unconfirms transactions at and beyond the
// passed height, so add one to the new synced-to height
// to prevent unconfirming txs from the synced-to block.
err = w.TxStore.Rollback(txmgrNs, rollbackStamp.Height+1)
if err != nil {
return err
@ -613,6 +643,13 @@ func (w *Wallet) syncWithChain() error {
return err
}
// If a birthday stamp was found during the initial sync and the
// rollback causes us to revert it, update the birthday stamp so that it
// points at the new tip.
if birthdayStamp != nil && rollbackStamp.Height <= birthdayStamp.Height {
birthdayStamp = &rollbackStamp
}
// Request notifications for connected and disconnected blocks.
//
// TODO(jrick): Either request this notification only once, or when
@ -626,7 +663,7 @@ func (w *Wallet) syncWithChain() error {
return err
}
return w.Rescan(addrs, unspent)
return w.rescanWithTarget(addrs, unspent, birthdayStamp)
}
// defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the