diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 8498d368..8a8354d0 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -715,16 +715,16 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { 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. - b.bestBlock = epoch - - // Next we'll notify any subscribed clients of the block. - b.notifyBlockEpochs(int32(newBlock.height), &newBlock.hash) + // Define a helper struct for coalescing the spend notifications we will + // dispatch after trying to commit the spend hints. + type spendNtfnBatch struct { + details *chainntnfs.SpendDetail + clients map[uint64]*spendNotification + } // Scan over the list of relevant transactions and possibly dispatch // notifications for spends. + spendBatches := make(map[wire.OutPoint]spendNtfnBatch) for _, tx := range newBlock.txns { mtx := tx.MsgTx() txSha := mtx.TxHash() @@ -740,6 +740,7 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { if !ok { continue } + delete(b.spendNotifications, prevOut) spendDetails := &chainntnfs.SpendDetail{ SpentOutPoint: &prevOut, @@ -749,21 +750,10 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { SpendingHeight: int32(newBlock.height), } - for _, ntfn := range clients { - chainntnfs.Log.Infof("Dispatching spend "+ - "notification for outpoint=%v", - ntfn.targetOutpoint) - - ntfn.spendChan <- spendDetails - - // Close spendChan to ensure that any calls to - // Cancel will not block. This is safe to do - // since the channel is buffered, and the - // message can still be read by the receiver. - close(ntfn.spendChan) + spendBatches[prevOut] = spendNtfnBatch{ + details: spendDetails, + clients: clients, } - - delete(b.spendNotifications, prevOut) } } @@ -780,13 +770,39 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { uint32(epoch.Height), ops..., ) if err != nil { - // The error is not fatal, so we should not return an - // error to the caller. + // The error is not fatal since we are connecting a + // block, and advancing the spend hint is an optimistic + // optimization. chainntnfs.Log.Errorf("Unable to update spend hint to "+ "%d for %v: %v", epoch.Height, ops, err) } } + // 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. + b.bestBlock = epoch + + // Next we'll notify any subscribed clients of the block. + b.notifyBlockEpochs(int32(newBlock.height), &newBlock.hash) + + // Finally, send off the spend details to the notification subscribers. + for _, batch := range spendBatches { + for _, ntfn := range batch.clients { + chainntnfs.Log.Infof("Dispatching spend "+ + "notification for outpoint=%v", + ntfn.targetOutpoint) + + ntfn.spendChan <- batch.details + + // Close spendChan to ensure that any calls to + // Cancel will not block. This is safe to do + // since the channel is buffered, and the + // message can still be read by the receiver. + close(ntfn.spendChan) + } + } + return nil }