chainntnfs/neutrinonotify: support registration for script confirmations
In this commit, we extend the NeutrinoNotifier to support registering scripts for confirmation notifications. Once the script has been detected as confirmed within the chain, a confirmation notification will be dispatched to through the Confirmed channel of the ConfirmationEvent returned upon registration. For scripts that have confirmed in the past, the `historicalConfDetails` method has been modified to determine whether the script has been confirmed by locating the script in an output of a confirmed transaction. For scripts that have yet to confirm, a filter update is sent to the underlying rescan to ensure that we match and dispatch on the script when processing new blocks. Along the way, we also address an issue where we'd miss detecting that a transaction/script has confirmed in the future due to not receiving a historical dispatch request from the underlying txNotifier. To fix this, we ensure that we always update our filters to detect the confirmation at tip, regardless of whether a historical rescan was detected or not.
This commit is contained in:
parent
1a41e23bf4
commit
f02590d8c0
@ -288,7 +288,7 @@ out:
|
|||||||
defer n.wg.Done()
|
defer n.wg.Done()
|
||||||
|
|
||||||
confDetails, err := n.historicalConfDetails(
|
confDetails, err := n.historicalConfDetails(
|
||||||
msg.TxID, msg.PkScript,
|
msg.ConfRequest,
|
||||||
msg.StartHeight, msg.EndHeight,
|
msg.StartHeight, msg.EndHeight,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -303,7 +303,7 @@ out:
|
|||||||
// cache at tip, since any pending
|
// cache at tip, since any pending
|
||||||
// rescans have now completed.
|
// rescans have now completed.
|
||||||
err = n.txNotifier.UpdateConfDetails(
|
err = n.txNotifier.UpdateConfDetails(
|
||||||
*msg.TxID, confDetails,
|
msg.ConfRequest, confDetails,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chainntnfs.Log.Error(err)
|
chainntnfs.Log.Error(err)
|
||||||
@ -447,15 +447,14 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// historicalConfDetails looks up whether a transaction is already included in
|
// historicalConfDetails looks up whether a confirmation request (txid/output
|
||||||
// a block in the active chain and, if so, returns details about the
|
// script) has already been included in a block in the active chain and, if so,
|
||||||
// confirmation.
|
// returns details about said block.
|
||||||
func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
|
||||||
pkScript []byte,
|
|
||||||
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation, error) {
|
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation, error) {
|
||||||
|
|
||||||
// Starting from the height hint, we'll walk forwards in the chain to
|
// Starting from the height hint, we'll walk forwards in the chain to
|
||||||
// see if this transaction has already been confirmed.
|
// see if this transaction/output script has already been confirmed.
|
||||||
for scanHeight := endHeight; scanHeight >= startHeight && scanHeight > 0; scanHeight-- {
|
for scanHeight := endHeight; scanHeight >= startHeight && scanHeight > 0; scanHeight-- {
|
||||||
// Ensure we haven't been requested to shut down before
|
// Ensure we haven't been requested to shut down before
|
||||||
// processing the next height.
|
// processing the next height.
|
||||||
@ -493,7 +492,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
|||||||
// In the case that the filter exists, we'll attempt to see if
|
// In the case that the filter exists, we'll attempt to see if
|
||||||
// any element in it matches our target public key script.
|
// any element in it matches our target public key script.
|
||||||
key := builder.DeriveKey(blockHash)
|
key := builder.DeriveKey(blockHash)
|
||||||
match, err := regFilter.Match(key, pkScript)
|
match, err := regFilter.Match(key, confRequest.PkScript.Script())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to query filter: %v", err)
|
return nil, fmt.Errorf("unable to query filter: %v", err)
|
||||||
}
|
}
|
||||||
@ -511,16 +510,20 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to get block from network: %v", err)
|
return nil, fmt.Errorf("unable to get block from network: %v", err)
|
||||||
}
|
}
|
||||||
for j, tx := range block.Transactions() {
|
|
||||||
txHash := tx.Hash()
|
// For every transaction in the block, check which one matches
|
||||||
if txHash.IsEqual(targetHash) {
|
// our request. If we find one that does, we can dispatch its
|
||||||
confDetails := chainntnfs.TxConfirmation{
|
// confirmation details.
|
||||||
BlockHash: blockHash,
|
for i, tx := range block.Transactions() {
|
||||||
BlockHeight: scanHeight,
|
if !confRequest.MatchesTx(tx.MsgTx()) {
|
||||||
TxIndex: uint32(j),
|
continue
|
||||||
}
|
|
||||||
return &confDetails, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &chainntnfs.TxConfirmation{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockHeight: scanHeight,
|
||||||
|
TxIndex: uint32(i),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -721,40 +724,45 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||||||
return ntfn.Event, nil
|
return ntfn.Event, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterConfirmationsNtfn registers a notification with NeutrinoNotifier
|
// RegisterConfirmationsNtfn registers an intent to be notified once the target
|
||||||
// which will be triggered once the txid reaches numConfs number of
|
// txid/output script has reached numConfs confirmations on-chain. When
|
||||||
// confirmations.
|
// intending to be notified of the confirmation of an output script, a nil txid
|
||||||
|
// must be used. The heightHint should represent the earliest height at which
|
||||||
|
// the txid/output script could have been included in the chain.
|
||||||
|
//
|
||||||
|
// Progress on the number of confirmations left can be read from the 'Updates'
|
||||||
|
// channel. Once it has reached all of its confirmations, a notification will be
|
||||||
|
// sent across the 'Confirmed' channel.
|
||||||
func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||||
pkScript []byte,
|
pkScript []byte,
|
||||||
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||||
|
|
||||||
// Construct a notification request for the transaction and send it to
|
// Construct a notification request for the transaction and send it to
|
||||||
// the main event loop.
|
// the main event loop.
|
||||||
|
confRequest, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ntfn := &chainntnfs.ConfNtfn{
|
ntfn := &chainntnfs.ConfNtfn{
|
||||||
ConfID: atomic.AddUint64(&n.confClientCounter, 1),
|
ConfID: atomic.AddUint64(&n.confClientCounter, 1),
|
||||||
TxID: txid,
|
ConfRequest: confRequest,
|
||||||
PkScript: pkScript,
|
|
||||||
NumConfirmations: numConfs,
|
NumConfirmations: numConfs,
|
||||||
Event: chainntnfs.NewConfirmationEvent(numConfs),
|
Event: chainntnfs.NewConfirmationEvent(numConfs),
|
||||||
HeightHint: heightHint,
|
HeightHint: heightHint,
|
||||||
}
|
}
|
||||||
|
|
||||||
chainntnfs.Log.Infof("New confirmation subscription: "+
|
chainntnfs.Log.Infof("New confirmation subscription: %v, num_confs=%v",
|
||||||
"txid=%v, numconfs=%v", txid, numConfs)
|
confRequest, numConfs)
|
||||||
|
|
||||||
// Register the conf notification with the TxNotifier. A non-nil value
|
// Register the conf notification with the TxNotifier. A non-nil value
|
||||||
// for `dispatch` will be returned if we are required to perform a
|
// for `dispatch` will be returned if we are required to perform a
|
||||||
// manual scan for the confirmation. Otherwise the notifier will begin
|
// manual scan for the confirmation. Otherwise the notifier will begin
|
||||||
// watching at tip for the transaction to confirm.
|
// watching at tip for the transaction to confirm.
|
||||||
dispatch, err := n.txNotifier.RegisterConf(ntfn)
|
dispatch, txNotifierTip, err := n.txNotifier.RegisterConf(ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dispatch == nil {
|
|
||||||
return ntfn.Event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// To determine whether this transaction has confirmed on-chain, we'll
|
// To determine whether this transaction has confirmed on-chain, we'll
|
||||||
// update our filter to watch for the transaction at tip and we'll also
|
// update our filter to watch for the transaction at tip and we'll also
|
||||||
// dispatch a historical rescan to determine if it has confirmed in the
|
// dispatch a historical rescan to determine if it has confirmed in the
|
||||||
@ -765,7 +773,9 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||||||
// type so we can instruct neutrino to match if the transaction
|
// type so we can instruct neutrino to match if the transaction
|
||||||
// containing the script is found in a block.
|
// containing the script is found in a block.
|
||||||
params := n.p2pNode.ChainParams()
|
params := n.p2pNode.ChainParams()
|
||||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ¶ms)
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||||
|
confRequest.PkScript.Script(), ¶ms,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to extract script: %v", err)
|
return nil, fmt.Errorf("unable to extract script: %v", err)
|
||||||
}
|
}
|
||||||
@ -777,7 +787,7 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||||||
case n.notificationRegistry <- &rescanFilterUpdate{
|
case n.notificationRegistry <- &rescanFilterUpdate{
|
||||||
updateOptions: []neutrino.UpdateOption{
|
updateOptions: []neutrino.UpdateOption{
|
||||||
neutrino.AddAddrs(addrs...),
|
neutrino.AddAddrs(addrs...),
|
||||||
neutrino.Rewind(dispatch.EndHeight),
|
neutrino.Rewind(txNotifierTip),
|
||||||
neutrino.DisableDisconnectedNtfns(true),
|
neutrino.DisableDisconnectedNtfns(true),
|
||||||
},
|
},
|
||||||
errChan: errChan,
|
errChan: errChan,
|
||||||
@ -795,7 +805,13 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||||||
return nil, fmt.Errorf("unable to update filter: %v", err)
|
return nil, fmt.Errorf("unable to update filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, with the filter updates, we can dispatch the historical
|
// If a historical rescan was not requested by the txNotifier, then we
|
||||||
|
// can return to the caller.
|
||||||
|
if dispatch == nil {
|
||||||
|
return ntfn.Event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, with the filter updated, we can dispatch the historical
|
||||||
// rescan to ensure we can detect if the event happened in the past.
|
// rescan to ensure we can detect if the event happened in the past.
|
||||||
select {
|
select {
|
||||||
case n.notificationRegistry <- dispatch:
|
case n.notificationRegistry <- dispatch:
|
||||||
|
Loading…
Reference in New Issue
Block a user