chainntnfs/txnotifer: detect confirmations and spends of scripts at tip
In this commit, we modify the TxNotifier's ConnectTip method to also detect whether a registered script has been confirmed or spent on-chain by a transaction in the connected block. Once detected, notifications for them will be queued up for dispatch as with txids/outpoints. We've also refactored the ConnectTip method into smaller and reusable methods, which will serve useful later.
This commit is contained in:
parent
b579c23310
commit
ecd70deb8c
@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -1048,16 +1049,17 @@ func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail)
|
|||||||
// through every transaction and determine if it is relevant to any of its
|
// through every transaction and determine if it is relevant to any of its
|
||||||
// clients. A transaction can be relevant in either of the following two ways:
|
// clients. A transaction can be relevant in either of the following two ways:
|
||||||
//
|
//
|
||||||
// 1. One of the inputs in the transaction spends an outpoint for which we
|
// 1. One of the inputs in the transaction spends an outpoint/output script
|
||||||
// currently have an active spend registration for.
|
// for which we currently have an active spend registration for.
|
||||||
//
|
//
|
||||||
// 2. The transaction is a transaction for which we currently have an active
|
// 2. The transaction has a txid or output script for which we currently have
|
||||||
// confirmation registration for.
|
// an active confirmation registration for.
|
||||||
//
|
//
|
||||||
// In the event that the transaction is relevant, a confirmation/spend
|
// In the event that the transaction is relevant, a confirmation/spend
|
||||||
// notification will be queued for dispatch to the relevant clients.
|
// notification will be queued for dispatch to the relevant clients.
|
||||||
// Confirmation notifications will only be dispatched for transactions that have
|
// Confirmation notifications will only be dispatched for transactions/output
|
||||||
// met the required number of confirmations required by the client.
|
// scripts that have met the required number of confirmations required by the
|
||||||
|
// client.
|
||||||
//
|
//
|
||||||
// NOTE: In order to actually dispatch the relevant transaction notifications to
|
// NOTE: In order to actually dispatch the relevant transaction notifications to
|
||||||
// clients, NotifyHeight must be called with the same block height in order to
|
// clients, NotifyHeight must be called with the same block height in order to
|
||||||
@ -1075,7 +1077,7 @@ func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32,
|
|||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
if blockHeight != n.currentHeight+1 {
|
if blockHeight != n.currentHeight+1 {
|
||||||
return fmt.Errorf("Received blocks out of order: "+
|
return fmt.Errorf("received blocks out of order: "+
|
||||||
"current height=%d, new height=%d",
|
"current height=%d, new height=%d",
|
||||||
n.currentHeight, blockHeight)
|
n.currentHeight, blockHeight)
|
||||||
}
|
}
|
||||||
@ -1085,117 +1087,204 @@ func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32,
|
|||||||
// First, we'll iterate over all the transactions found in this block to
|
// First, we'll iterate over all the transactions found in this block to
|
||||||
// determine if it includes any relevant transactions to the TxNotifier.
|
// determine if it includes any relevant transactions to the TxNotifier.
|
||||||
for _, tx := range txns {
|
for _, tx := range txns {
|
||||||
txHash := tx.Hash()
|
n.filterTx(
|
||||||
|
tx, blockHash, blockHeight, n.handleConfDetailsAtTip,
|
||||||
|
n.handleSpendDetailsAtTip,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, now that we've determined which requests were confirmed and
|
||||||
|
// spent within the new block, we can update their entries in their
|
||||||
|
// respective caches, along with all of our unconfirmed and unspent
|
||||||
|
// requests.
|
||||||
|
n.updateHints(blockHeight)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterTx determines whether the transaction spends or confirms any
|
||||||
|
// outstanding pending requests. The onConf and onSpend callbacks can be used to
|
||||||
|
// retrieve all the requests fulfilled by this transaction as they occur.
|
||||||
|
func (n *TxNotifier) filterTx(tx *btcutil.Tx, blockHash *chainhash.Hash,
|
||||||
|
blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
|
||||||
|
onSpend func(SpendRequest, *SpendDetail)) {
|
||||||
|
|
||||||
// In order to determine if this transaction is relevant to the
|
// In order to determine if this transaction is relevant to the
|
||||||
// notifier, we'll check its inputs for any outstanding spend
|
// notifier, we'll check its inputs for any outstanding spend
|
||||||
// notifications.
|
// requests.
|
||||||
for i, txIn := range tx.MsgTx().TxIn {
|
txHash := tx.Hash()
|
||||||
prevOut := txIn.PreviousOutPoint
|
if onSpend != nil {
|
||||||
spendSet, ok := n.spendNotifications[prevOut]
|
// notifyDetails is a helper closure that will construct the
|
||||||
if !ok {
|
// spend details of a request and hand them off to the onSpend
|
||||||
continue
|
// callback.
|
||||||
}
|
notifyDetails := func(spendRequest SpendRequest,
|
||||||
|
prevOut wire.OutPoint, inputIdx uint32) {
|
||||||
|
|
||||||
// If we have any, we'll record its spend height so that
|
Log.Debugf("Found spend of %v: spend_tx=%v, "+
|
||||||
// notifications get dispatched to the respective
|
"block_height=%d", spendRequest, txHash,
|
||||||
// clients.
|
blockHeight)
|
||||||
spendDetails := &SpendDetail{
|
|
||||||
|
onSpend(spendRequest, &SpendDetail{
|
||||||
SpentOutPoint: &prevOut,
|
SpentOutPoint: &prevOut,
|
||||||
SpenderTxHash: txHash,
|
SpenderTxHash: txHash,
|
||||||
SpendingTx: tx.MsgTx(),
|
SpendingTx: tx.MsgTx(),
|
||||||
SpenderInputIndex: uint32(i),
|
SpenderInputIndex: inputIdx,
|
||||||
SpendingHeight: int32(blockHeight),
|
SpendingHeight: int32(blockHeight),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(wilmer): cancel pending historical rescans if any?
|
for i, txIn := range tx.MsgTx().TxIn {
|
||||||
spendSet.rescanStatus = rescanComplete
|
// We'll re-derive the script of the output being spent
|
||||||
spendSet.details = spendDetails
|
// to determine if the inputs spends any registered
|
||||||
for _, ntfn := range spendSet.ntfns {
|
// requests.
|
||||||
// In the event that this notification was aware
|
prevOut := txIn.PreviousOutPoint
|
||||||
// that the spending transaction of its outpoint
|
pkScript, err := txscript.ComputePkScript(
|
||||||
// was reorged out of the chain, we'll consume
|
txIn.SignatureScript, txIn.Witness,
|
||||||
// the reorg notification if it hasn't been
|
)
|
||||||
// done yet already.
|
if err != nil {
|
||||||
select {
|
|
||||||
case <-ntfn.Event.Reorg:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll note the outpoints spending height in order to
|
|
||||||
// correctly handle dispatching notifications when the
|
|
||||||
// spending transactions gets reorged out of the chain.
|
|
||||||
opSet, exists := n.opsBySpendHeight[blockHeight]
|
|
||||||
if !exists {
|
|
||||||
opSet = make(map[wire.OutPoint]struct{})
|
|
||||||
n.opsBySpendHeight[blockHeight] = opSet
|
|
||||||
}
|
|
||||||
opSet[prevOut] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have any pending notifications for this txid. If
|
|
||||||
// none are found, we can proceed to the next transaction.
|
|
||||||
confSet, ok := n.confNotifications[*txHash]
|
|
||||||
if !ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
spendRequest := SpendRequest{
|
||||||
|
OutPoint: prevOut,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}
|
||||||
|
|
||||||
Log.Debugf("Block contains txid=%v, constructing details",
|
// If we have any, we'll record their spend height so
|
||||||
txHash)
|
// that notifications get dispatched to the respective
|
||||||
|
// clients.
|
||||||
|
if _, ok := n.spendNotifications[spendRequest]; ok {
|
||||||
|
notifyDetails(spendRequest, prevOut, uint32(i))
|
||||||
|
}
|
||||||
|
spendRequest.OutPoint = ZeroOutPoint
|
||||||
|
if _, ok := n.spendNotifications[spendRequest]; ok {
|
||||||
|
notifyDetails(spendRequest, prevOut, uint32(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll also check its outputs to determine if there are any
|
||||||
|
// outstanding confirmation requests.
|
||||||
|
if onConf != nil {
|
||||||
|
// notifyDetails is a helper closure that will construct the
|
||||||
|
// confirmation details of a request and hand them off to the
|
||||||
|
// onConf callback.
|
||||||
|
notifyDetails := func(confRequest ConfRequest) {
|
||||||
|
Log.Debugf("Found initial confirmation of %v: "+
|
||||||
|
"height=%d, hash=%v", confRequest,
|
||||||
|
blockHeight, blockHash)
|
||||||
|
|
||||||
// If we have any, we'll record its confirmed height so that
|
|
||||||
// notifications get dispatched when the transaction reaches the
|
|
||||||
// clients' desired number of confirmations.
|
|
||||||
details := &TxConfirmation{
|
details := &TxConfirmation{
|
||||||
BlockHash: blockHash,
|
BlockHash: blockHash,
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
TxIndex: uint32(tx.Index()),
|
TxIndex: uint32(tx.Index()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onConf(confRequest, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, txOut := range tx.MsgTx().TxOut {
|
||||||
|
// We'll parse the script of the output to determine if
|
||||||
|
// we have any registered requests for it or the
|
||||||
|
// transaction itself.
|
||||||
|
pkScript, err := txscript.ParsePkScript(txOut.PkScript)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
confRequest := ConfRequest{
|
||||||
|
TxID: *txHash,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have any, we'll record their confirmed height
|
||||||
|
// so that notifications get dispatched when they
|
||||||
|
// reaches the clients' desired number of confirmations.
|
||||||
|
if _, ok := n.confNotifications[confRequest]; ok {
|
||||||
|
notifyDetails(confRequest)
|
||||||
|
}
|
||||||
|
confRequest.TxID = ZeroHash
|
||||||
|
if _, ok := n.confNotifications[confRequest]; ok {
|
||||||
|
notifyDetails(confRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
|
||||||
|
// script in order to properly dispatch a confirmation notification after
|
||||||
|
// meeting each request's desired number of confirmations for all current and
|
||||||
|
// future registered clients.
|
||||||
|
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
|
||||||
|
details *TxConfirmation) {
|
||||||
|
|
||||||
// TODO(wilmer): cancel pending historical rescans if any?
|
// TODO(wilmer): cancel pending historical rescans if any?
|
||||||
|
confSet := n.confNotifications[confRequest]
|
||||||
confSet.rescanStatus = rescanComplete
|
confSet.rescanStatus = rescanComplete
|
||||||
confSet.details = details
|
confSet.details = details
|
||||||
|
|
||||||
for _, ntfn := range confSet.ntfns {
|
for _, ntfn := range confSet.ntfns {
|
||||||
// In the event that this notification was aware that
|
// In the event that this notification was aware that the
|
||||||
// the transaction was reorged out of the chain, we'll
|
// transaction/output script was reorged out of the chain, we'll
|
||||||
// consume the reorg notification if it hasn't been done
|
// consume the reorg notification if it hasn't been done yet
|
||||||
// yet already.
|
// already.
|
||||||
select {
|
select {
|
||||||
case <-ntfn.Event.NegativeConf:
|
case <-ntfn.Event.NegativeConf:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll note this client's required number of
|
// We'll note this client's required number of confirmations so
|
||||||
// confirmations so that we can notify them when
|
// that we can notify them when expected.
|
||||||
// expected.
|
confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
|
||||||
confHeight := blockHeight + ntfn.NumConfirmations - 1
|
|
||||||
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
|
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
|
||||||
if !exists {
|
if !exists {
|
||||||
ntfnSet = make(map[*ConfNtfn]struct{})
|
ntfnSet = make(map[*ConfNtfn]struct{})
|
||||||
n.ntfnsByConfirmHeight[confHeight] = ntfnSet
|
n.ntfnsByConfirmHeight[confHeight] = ntfnSet
|
||||||
}
|
}
|
||||||
ntfnSet[ntfn] = struct{}{}
|
ntfnSet[ntfn] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// We'll also note the initial confirmation height in
|
// We'll also note the initial confirmation height in order to correctly
|
||||||
// order to correctly handle dispatching notifications
|
// handle dispatching notifications when the transaction/output script
|
||||||
// when the transaction gets reorged out of the chain.
|
// gets reorged out of the chain.
|
||||||
txSet, exists := n.txsByInitialHeight[blockHeight]
|
txSet, exists := n.confsByInitialHeight[details.BlockHeight]
|
||||||
if !exists {
|
if !exists {
|
||||||
txSet = make(map[chainhash.Hash]struct{})
|
txSet = make(map[ConfRequest]struct{})
|
||||||
n.txsByInitialHeight[blockHeight] = txSet
|
n.confsByInitialHeight[details.BlockHeight] = txSet
|
||||||
}
|
}
|
||||||
txSet[*txHash] = struct{}{}
|
txSet[confRequest] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
|
||||||
|
// in order to properly dispatch a spend notification for all current and future
|
||||||
|
// registered clients.
|
||||||
|
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
|
||||||
|
details *SpendDetail) {
|
||||||
|
|
||||||
|
// TODO(wilmer): cancel pending historical rescans if any?
|
||||||
|
spendSet := n.spendNotifications[spendRequest]
|
||||||
|
spendSet.rescanStatus = rescanComplete
|
||||||
|
spendSet.details = details
|
||||||
|
|
||||||
|
for _, ntfn := range spendSet.ntfns {
|
||||||
|
// In the event that this notification was aware that the
|
||||||
|
// spending transaction of its outpoint/output script was
|
||||||
|
// reorged out of the chain, we'll consume the reorg
|
||||||
|
// notification if it hasn't been done yet already.
|
||||||
|
select {
|
||||||
|
case <-ntfn.Event.Reorg:
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, now that we've determined which transactions were confirmed
|
// We'll note the spending height of the request in order to correctly
|
||||||
// and which outpoints were spent within the new block, we can update
|
// handle dispatching notifications when the spending transactions gets
|
||||||
// their entries in their respective caches, along with all of our
|
// reorged out of the chain.
|
||||||
// unconfirmed transactions and unspent outpoints.
|
spendHeight := uint32(details.SpendingHeight)
|
||||||
n.updateHints(blockHeight)
|
opSet, exists := n.spendsByHeight[spendHeight]
|
||||||
|
if !exists {
|
||||||
return nil
|
opSet = make(map[SpendRequest]struct{})
|
||||||
|
n.spendsByHeight[spendHeight] = opSet
|
||||||
|
}
|
||||||
|
opSet[spendRequest] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyHeight dispatches confirmation and spend notifications to the clients
|
// NotifyHeight dispatches confirmation and spend notifications to the clients
|
||||||
|
Loading…
Reference in New Issue
Block a user