chainntnfs: cache spend hints within the different chain notifiers
In this commit, we extend the different ChainNotifier implementations to cache height hints for our spend events. Each outpoint we've requested a spend notification for will have its initial height hint cached. We then increment this height hint at every new block for unspent outpoints. This allows us to retrieve the *exact* height at which the outpoint has been spent. By doing this, we optimize the different ChainNotifier implementations since they will no longer have to rescan forward (and possibly fetch blocks in the neutrino/pruned node case) from the initial height hint.
This commit is contained in:
3 files changed
+167
-30
No files matched your search
@@ -662,23 +662,23 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
return fmt.Errorf("unable to get block: %v", err)
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v",
|
||||
epoch.Height, epoch.Hash)
|
||||
|
||||
txns := btcutil.NewBlock(rawBlock).Transactions()
|
||||
|
||||
newBlock := &filteredBlock{
|
||||
hash: *epoch.Hash,
|
||||
height: uint32(epoch.Height),
|
||||
txns: txns,
|
||||
txns: btcutil.NewBlock(rawBlock).Transactions(),
|
||||
connect: true,
|
||||
}
|
||||
err = b.txConfNotifier.ConnectTip(&newBlock.hash, newBlock.height,
|
||||
newBlock.txns)
|
||||
|
||||
err = b.txConfNotifier.ConnectTip(
|
||||
&newBlock.hash, newBlock.height, newBlock.txns,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect tip: %v", err)
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v", epoch.Height,
|
||||
epoch.Hash)
|
||||
|
||||
// We want to set the best block before dispatching notifications
|
||||
// so if any subscribers make queries based on their received
|
||||
// block epoch, our state is fully updated in time.
|
||||
@@ -687,8 +687,8 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
// Next we'll notify any subscribed clients of the block.
|
||||
b.notifyBlockEpochs(int32(newBlock.height), &newBlock.hash)
|
||||
|
||||
// Finally, we'll scan over the list of relevant transactions and
|
||||
// possibly dispatch notifications for confirmations and spends.
|
||||
// Scan over the list of relevant transactions and possibly dispatch
|
||||
// notifications for spends.
|
||||
for _, tx := range newBlock.txns {
|
||||
mtx := tx.MsgTx()
|
||||
txSha := mtx.TxHash()
|
||||
@@ -714,8 +714,10 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
}
|
||||
|
||||
for _, ntfn := range clients {
|
||||
chainntnfs.Log.Infof("Dispatching spend notification for "+
|
||||
"outpoint=%v", ntfn.targetOutpoint)
|
||||
chainntnfs.Log.Infof("Dispatching spend "+
|
||||
"notification for outpoint=%v",
|
||||
ntfn.targetOutpoint)
|
||||
|
||||
ntfn.spendChan <- spendDetails
|
||||
|
||||
// Close spendChan to ensure that any calls to
|
||||
@@ -729,6 +731,26 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we'll update the spend height hint for all of our watched
|
||||
// outpoints that have not been spent yet. This is safe to do as we do
|
||||
// not watch already spent outpoints for spend notifications.
|
||||
ops := make([]wire.OutPoint, 0, len(b.spendNotifications))
|
||||
for op := range b.spendNotifications {
|
||||
ops = append(ops, op)
|
||||
}
|
||||
|
||||
if len(ops) > 0 {
|
||||
err := b.spendHintCache.CommitSpendHint(
|
||||
uint32(epoch.Height), ops...,
|
||||
)
|
||||
if err != nil {
|
||||
// The error is not fatal, so we should not return an
|
||||
// error to the caller.
|
||||
chainntnfs.Log.Errorf("Unable to update spend hint to "+
|
||||
"%d for %v: %v", epoch.Height, ops, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -787,6 +809,18 @@ type spendCancel struct {
|
||||
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
||||
// Before proceeding to register the notification, we'll query our
|
||||
// height hint cache to determine whether a better one exists.
|
||||
if hint, err := b.spendHintCache.QuerySpendHint(*outpoint); err == nil {
|
||||
if hint > heightHint {
|
||||
chainntnfs.Log.Debugf("Using height hint %d retrieved "+
|
||||
"from cache for %v", hint, outpoint)
|
||||
heightHint = hint
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a notification request for the outpoint and send it to the
|
||||
// main event loop.
|
||||
ntfn := &spendNotification{
|
||||
targetOutpoint: outpoint,
|
||||
spendChan: make(chan *chainntnfs.SpendDetail, 1),
|
||||
@@ -815,7 +849,20 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if txOut == nil {
|
||||
// If the output is unspent, then we'll write it to the cache with the
|
||||
// given height hint. This allows us to increase the height hint as the
|
||||
// chain extends and the output remains unspent.
|
||||
if txOut != nil {
|
||||
err := b.spendHintCache.CommitSpendHint(heightHint, *outpoint)
|
||||
if err != nil {
|
||||
// The error is not fatal, so we should not return an
|
||||
// error to the caller.
|
||||
chainntnfs.Log.Error("Unable to update spend hint to "+
|
||||
"%d for %v: %v", heightHint, *outpoint, err)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we'll determine when the output was spent.
|
||||
//
|
||||
// First, we'll attempt to retrieve the transaction's block hash
|
||||
// using the backend's transaction index.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
|
||||
|
||||
Reference in new issue
Block a user