Merge pull request #575 from wpaulino/syncedto-rescan

wallet/wallet: prevent always rescanning from birthday block
This commit is contained in:
Olaoluwa Osuntokun 2018-11-15 19:00:35 -08:00 committed by GitHub
commit 55c7c63993
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 28 deletions

View file

@ -257,6 +257,7 @@ var (
startBlockName = []byte("startblock") startBlockName = []byte("startblock")
birthdayName = []byte("birthday") birthdayName = []byte("birthday")
birthdayBlockName = []byte("birthdayblock") birthdayBlockName = []byte("birthdayblock")
birthdayBlockVerifiedName = []byte("birthdayblockverified")
) )
// uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in // uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in
@ -2012,6 +2013,47 @@ func putBirthdayBlock(ns walletdb.ReadWriteBucket, block BlockStamp) error {
return nil return nil
} }
// fetchBirthdayBlockVerification retrieves the bit that determines whether the
// wallet has verified that its birthday block is correct.
func fetchBirthdayBlockVerification(ns walletdb.ReadBucket) bool {
bucket := ns.NestedReadBucket(syncBucketName)
verifiedValue := bucket.Get(birthdayBlockVerifiedName)
// If there is no verification status, we can assume it has not been
// verified yet.
if verifiedValue == nil {
return false
}
// Otherwise, we'll determine if it's verified by the value stored.
verified := binary.BigEndian.Uint16(verifiedValue[:])
return verified != 0
}
// putBirthdayBlockVerification stores a bit that determines whether the
// birthday block has been verified by the wallet to be correct.
func putBirthdayBlockVerification(ns walletdb.ReadWriteBucket, verified bool) error {
// Convert the boolean to an integer in its binary representation as
// there is no way to insert a boolean directly as a value of a
// key/value pair.
verifiedValue := uint16(0)
if verified {
verifiedValue = 1
}
var verifiedBytes [2]byte
binary.BigEndian.PutUint16(verifiedBytes[:], verifiedValue)
bucket := ns.NestedReadWriteBucket(syncBucketName)
err := bucket.Put(birthdayBlockVerifiedName, verifiedBytes[:])
if err != nil {
str := "failed to store birthday block verification"
return managerError(ErrDatabase, str, err)
}
return nil
}
// managerExists returns whether or not the manager has already been created // managerExists returns whether or not the manager has already been created
// in the given database namespace. // in the given database namespace.
func managerExists(ns walletdb.ReadBucket) bool { func managerExists(ns walletdb.ReadBucket) bool {

View file

@ -112,15 +112,26 @@ func (m *Manager) SetBirthday(ns walletdb.ReadWriteBucket,
} }
// BirthdayBlock returns the birthday block, or earliest block a key could have // BirthdayBlock returns the birthday block, or earliest block a key could have
// been used, for the manager. // been used, for the manager. A boolean is also returned to indicate whether
func (m *Manager) BirthdayBlock(ns walletdb.ReadBucket) (BlockStamp, error) { // the birthday block has been verified as correct.
return fetchBirthdayBlock(ns) func (m *Manager) BirthdayBlock(ns walletdb.ReadBucket) (BlockStamp, bool, error) {
birthdayBlock, err := fetchBirthdayBlock(ns)
if err != nil {
return BlockStamp{}, false, err
}
return birthdayBlock, fetchBirthdayBlockVerification(ns), nil
} }
// SetBirthdayBlock sets the birthday block, or earliest time a key could have // SetBirthdayBlock sets the birthday block, or earliest time a key could have
// been used, for the manager. // been used, for the manager. The verified boolean can be used to specify
// whether this birthday block should be sanity checked to determine if there
// exists a better candidate to prevent less block fetching.
func (m *Manager) SetBirthdayBlock(ns walletdb.ReadWriteBucket, func (m *Manager) SetBirthdayBlock(ns walletdb.ReadWriteBucket,
block BlockStamp) error { block BlockStamp, verified bool) error {
return putBirthdayBlock(ns, block) if err := putBirthdayBlock(ns, block); err != nil {
return err
}
return putBirthdayBlockVerification(ns, verified)
} }

View file

@ -360,12 +360,15 @@ func (w *Wallet) birthdaySanityCheck() (*waddrmgr.BlockStamp, error) {
} }
// We'll then fetch our wallet's birthday timestamp and block. // We'll then fetch our wallet's birthday timestamp and block.
birthdayTimestamp := w.Manager.Birthday() var (
var birthdayBlock waddrmgr.BlockStamp birthdayTimestamp = w.Manager.Birthday()
birthdayBlock waddrmgr.BlockStamp
birthdayBlockVerified bool
)
err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
var err error var err error
ns := tx.ReadBucket(waddrmgrNamespaceKey) ns := tx.ReadBucket(waddrmgrNamespaceKey)
birthdayBlock, err = w.Manager.BirthdayBlock(ns) birthdayBlock, birthdayBlockVerified, err = w.Manager.BirthdayBlock(ns)
return err return err
}) })
@ -380,6 +383,17 @@ func (w *Wallet) birthdaySanityCheck() (*waddrmgr.BlockStamp, error) {
return nil, err return nil, err
} }
// If the birthday block has already been verified to be correct, we can
// exit our sanity check to prevent potentially fetching a better
// candidate.
if birthdayBlockVerified {
log.Debugf("Birthday block has already been verified: "+
"height=%d, hash=%v", birthdayBlock.Height,
birthdayBlock.Hash)
return &birthdayBlock, nil
}
log.Debugf("Starting sanity check for the wallet's birthday block "+ log.Debugf("Starting sanity check for the wallet's birthday block "+
"from: height=%d, hash=%v", birthdayBlock.Height, "from: height=%d, hash=%v", birthdayBlock.Height,
birthdayBlock.Hash) birthdayBlock.Hash)
@ -505,21 +519,15 @@ func (w *Wallet) birthdaySanityCheck() (*waddrmgr.BlockStamp, error) {
timestampDelta = birthdayTimestamp.Sub(header.Timestamp) timestampDelta = birthdayTimestamp.Sub(header.Timestamp)
} }
// At this point, we've found a valid candidate that satisfies our // At this point, we've found a new, better candidate, so we'll write it
// conditions above. If this is our current birthday block, then we can // to disk.
// exit to avoid the additional database transaction.
if candidate.Hash.IsEqual(&birthdayBlock.Hash) {
return &candidate, nil
}
// Otherwise, we have a new, better candidate, so we'll write it to
// disk.
log.Debugf("Found a new valid wallet birthday block: height=%d, hash=%v", log.Debugf("Found a new valid wallet birthday block: height=%d, hash=%v",
candidate.Height, candidate.Hash) candidate.Height, candidate.Hash)
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
if err := w.Manager.SetBirthdayBlock(ns, candidate); err != nil { err := w.Manager.SetBirthdayBlock(ns, candidate, true)
if err != nil {
return err return err
} }
return w.Manager.SetSyncedTo(ns, &candidate) return w.Manager.SetSyncedTo(ns, &candidate)

View file

@ -498,7 +498,7 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
birthdayStamp.Hash) birthdayStamp.Hash)
err := w.Manager.SetBirthdayBlock( err := w.Manager.SetBirthdayBlock(
ns, *birthdayStamp, ns, *birthdayStamp, true,
) )
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -653,8 +653,10 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
// If a birthday stamp was found during the initial sync and the // 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 // rollback causes us to revert it, update the birthday stamp so that it
// points at the new tip. // points at the new tip.
birthdayRollback := false
if birthdayStamp != nil && rollbackStamp.Height <= birthdayStamp.Height { if birthdayStamp != nil && rollbackStamp.Height <= birthdayStamp.Height {
birthdayStamp = &rollbackStamp birthdayStamp = &rollbackStamp
birthdayRollback = true
log.Debugf("Found new birthday block after rollback: "+ log.Debugf("Found new birthday block after rollback: "+
"height=%d, hash=%v", birthdayStamp.Height, "height=%d, hash=%v", birthdayStamp.Height,
@ -662,7 +664,9 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return w.Manager.SetBirthdayBlock(ns, *birthdayStamp) return w.Manager.SetBirthdayBlock(
ns, *birthdayStamp, true,
)
}) })
if err != nil { if err != nil {
return nil return nil
@ -681,7 +685,16 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
return err return err
} }
// If this was our initial sync, we're recovering from our seed, or our
// birthday was rolled back due to a chain reorg, we'll dispatch a
// rescan from our birthday block to ensure we detect all relevant
// on-chain events from this point.
if isInitialSync || isRecovery || birthdayRollback {
return w.rescanWithTarget(addrs, unspent, birthdayStamp) return w.rescanWithTarget(addrs, unspent, birthdayStamp)
}
// Otherwise, we'll rescan from tip.
return w.rescanWithTarget(addrs, unspent, nil)
} }
// defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the // defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the
@ -2671,7 +2684,7 @@ func (w *Wallet) ImportPrivateKey(scope waddrmgr.KeyScope, wif *btcutil.WIF,
// before our current one. Otherwise, if we do, we can // before our current one. Otherwise, if we do, we can
// potentially miss detecting relevant chain events that // potentially miss detecting relevant chain events that
// occurred between them while rescanning. // occurred between them while rescanning.
birthdayBlock, err := w.Manager.BirthdayBlock(addrmgrNs) birthdayBlock, _, err := w.Manager.BirthdayBlock(addrmgrNs)
if err != nil { if err != nil {
return err return err
} }
@ -2683,7 +2696,11 @@ func (w *Wallet) ImportPrivateKey(scope waddrmgr.KeyScope, wif *btcutil.WIF,
if err != nil { if err != nil {
return err return err
} }
return w.Manager.SetBirthdayBlock(addrmgrNs, *bs)
// To ensure this birthday block is correct, we'll mark it as
// unverified to prompt a sanity check at the next restart to
// ensure it is correct as it was provided by the caller.
return w.Manager.SetBirthdayBlock(addrmgrNs, *bs, false)
}) })
if err != nil { if err != nil {
return "", err return "", err