chainntnfs: refactor common registration code within RegisterSpendNtfn

This commit is contained in:
Wilmer Paulino 2019-08-16 19:56:26 -07:00
parent 5089311ca1
commit 7821eb6ffb
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
5 changed files with 240 additions and 287 deletions

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"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/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
@ -39,7 +40,6 @@ type chainUpdate struct {
// chain client. Multiple concurrent clients are supported. All notifications // chain client. Multiple concurrent clients are supported. All notifications
// are achieved via non-blocking sends on client channels. // are achieved via non-blocking sends on client channels.
type BitcoindNotifier struct { type BitcoindNotifier struct {
spendClientCounter uint64 // To be used atomically.
epochClientCounter uint64 // To be used atomically. epochClientCounter uint64 // To be used atomically.
started int32 // To be used atomically. started int32 // To be used atomically.
@ -615,23 +615,11 @@ func (b *BitcoindNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistr
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// First, we'll construct a spend notification request and hand it off // Register the conf notification with the TxNotifier. A non-nil value
// to the txNotifier. // for `dispatch` will be returned if we are required to perform a
spendID := atomic.AddUint64(&b.spendClientCounter, 1) // manual scan for the confirmation. Otherwise the notifier will begin
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript) // watching at tip for the transaction to confirm.
if err != nil { ntfn, err := b.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
return nil, err
}
ntfn := &chainntnfs.SpendNtfn{
SpendID: spendID,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(func() {
b.txNotifier.CancelSpend(spendRequest, spendID)
}),
HeightHint: heightHint,
}
historicalDispatch, _, err := b.txNotifier.RegisterSpend(ntfn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -640,17 +628,18 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// outpoint/output script as spent. // outpoint/output script as spent.
// //
// TODO(wilmer): use LoadFilter API instead. // TODO(wilmer): use LoadFilter API instead.
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint { if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
addr, err := spendRequest.PkScript.Address(b.chainParams) _, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to parse script: %v", err)
} }
addrs := []btcutil.Address{addr}
if err := b.chainConn.NotifyReceived(addrs); err != nil { if err := b.chainConn.NotifyReceived(addrs); err != nil {
return nil, err return nil, err
} }
} else { } else {
ops := []*wire.OutPoint{&spendRequest.OutPoint} ops := []*wire.OutPoint{outpoint}
if err := b.chainConn.NotifySpent(ops); err != nil { if err := b.chainConn.NotifySpent(ops); err != nil {
return nil, err return nil, err
} }
@ -659,7 +648,7 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// If the txNotifier didn't return any details to perform a historical // If the txNotifier didn't return any details to perform a historical
// scan of the chain, then we can return early as there's nothing left // scan of the chain, then we can return early as there's nothing left
// for us to do. // for us to do.
if historicalDispatch == nil { if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil return ntfn.Event, nil
} }
@ -669,9 +658,9 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// We'll short-circuit the path when dispatching the spend of a script, // We'll short-circuit the path when dispatching the spend of a script,
// rather than an outpoint, as there aren't any additional checks we can // rather than an outpoint, as there aren't any additional checks we can
// make for scripts. // make for scripts.
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint { if ntfn.HistoricalDispatch.OutPoint == chainntnfs.ZeroOutPoint {
select { select {
case b.notificationRegistry <- historicalDispatch: case b.notificationRegistry <- ntfn.HistoricalDispatch:
case <-b.quit: case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown return nil, chainntnfs.ErrChainNotifierShuttingDown
} }
@ -686,16 +675,16 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// We'll start by checking the backend's UTXO set to determine whether // We'll start by checking the backend's UTXO set to determine whether
// the outpoint has been spent. If it hasn't, we can return to the // the outpoint has been spent. If it hasn't, we can return to the
// caller as well. // caller as well.
txOut, err := b.chainConn.GetTxOut( txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
&spendRequest.OutPoint.Hash, spendRequest.OutPoint.Index, true,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if txOut != nil { if txOut != nil {
// We'll let the txNotifier know the outpoint is still unspent // We'll let the txNotifier know the outpoint is still unspent
// in order to begin updating its spend hint. // in order to begin updating its spend hint.
err := b.txNotifier.UpdateSpendDetails(spendRequest, nil) err := b.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, nil,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -710,14 +699,14 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// index (if enabled) to determine if we have a better rescan starting // index (if enabled) to determine if we have a better rescan starting
// height. We can do this as the GetRawTransaction call will return the // height. We can do this as the GetRawTransaction call will return the
// hash of the block it was included in within the chain. // hash of the block it was included in within the chain.
tx, err := b.chainConn.GetRawTransactionVerbose(&spendRequest.OutPoint.Hash) tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil { if err != nil {
// Avoid returning an error if the transaction was not found to // Avoid returning an error if the transaction was not found to
// proceed with fallback methods. // proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError) jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo { if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid %v: %v", return nil, fmt.Errorf("unable to query for txid %v: %v",
spendRequest.OutPoint.Hash, err) outpoint.Hash, err)
} }
} }
@ -740,15 +729,15 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
return nil, err return nil, err
} }
if uint32(blockHeight) > historicalDispatch.StartHeight { if uint32(blockHeight) > ntfn.HistoricalDispatch.StartHeight {
historicalDispatch.StartHeight = uint32(blockHeight) ntfn.HistoricalDispatch.StartHeight = uint32(blockHeight)
} }
} }
// Now that we've determined the starting point of our rescan, we can // Now that we've determined the starting point of our rescan, we can
// dispatch it and return. // dispatch it and return.
select { select {
case b.notificationRegistry <- historicalDispatch: case b.notificationRegistry <- ntfn.HistoricalDispatch:
case <-b.quit: case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown return nil, chainntnfs.ErrChainNotifierShuttingDown
} }

@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/rpcclient"
"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/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
@ -50,7 +51,6 @@ type txUpdate struct {
// notifications. Multiple concurrent clients are supported. All notifications // notifications. Multiple concurrent clients are supported. All notifications
// are achieved via non-blocking sends on client channels. // are achieved via non-blocking sends on client channels.
type BtcdNotifier struct { type BtcdNotifier struct {
spendClientCounter uint64 // To be used atomically.
epochClientCounter uint64 // To be used atomically. epochClientCounter uint64 // To be used atomically.
started int32 // To be used atomically. started int32 // To be used atomically.
@ -651,23 +651,11 @@ func (b *BtcdNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistratio
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// First, we'll construct a spend notification request and hand it off // Register the conf notification with the TxNotifier. A non-nil value
// to the txNotifier. // for `dispatch` will be returned if we are required to perform a
spendID := atomic.AddUint64(&b.spendClientCounter, 1) // manual scan for the confirmation. Otherwise the notifier will begin
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript) // watching at tip for the transaction to confirm.
if err != nil { ntfn, err := b.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
return nil, err
}
ntfn := &chainntnfs.SpendNtfn{
SpendID: spendID,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(func() {
b.txNotifier.CancelSpend(spendRequest, spendID)
}),
HeightHint: heightHint,
}
historicalDispatch, _, err := b.txNotifier.RegisterSpend(ntfn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -676,17 +664,18 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// outpoint/output script as spent. // outpoint/output script as spent.
// //
// TODO(wilmer): use LoadFilter API instead. // TODO(wilmer): use LoadFilter API instead.
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint { if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
addr, err := spendRequest.PkScript.Address(b.chainParams) _, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to parse script: %v", err)
} }
addrs := []btcutil.Address{addr}
if err := b.chainConn.NotifyReceived(addrs); err != nil { if err := b.chainConn.NotifyReceived(addrs); err != nil {
return nil, err return nil, err
} }
} else { } else {
ops := []*wire.OutPoint{&spendRequest.OutPoint} ops := []*wire.OutPoint{outpoint}
if err := b.chainConn.NotifySpent(ops); err != nil { if err := b.chainConn.NotifySpent(ops); err != nil {
return nil, err return nil, err
} }
@ -695,7 +684,7 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// If the txNotifier didn't return any details to perform a historical // If the txNotifier didn't return any details to perform a historical
// scan of the chain, then we can return early as there's nothing left // scan of the chain, then we can return early as there's nothing left
// for us to do. // for us to do.
if historicalDispatch == nil { if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil return ntfn.Event, nil
} }
@ -705,26 +694,29 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// We'll short-circuit the path when dispatching the spend of a script, // We'll short-circuit the path when dispatching the spend of a script,
// rather than an outpoint, as there aren't any additional checks we can // rather than an outpoint, as there aren't any additional checks we can
// make for scripts. // make for scripts.
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint { if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
startHash, err := b.chainConn.GetBlockHash( startHash, err := b.chainConn.GetBlockHash(
int64(historicalDispatch.StartHeight), int64(ntfn.HistoricalDispatch.StartHeight),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(wilmer): add retry logic if rescan fails? // TODO(wilmer): add retry logic if rescan fails?
addr, err := spendRequest.PkScript.Address(b.chainParams) _, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to parse address: %v", err)
} }
addrs := []btcutil.Address{addr}
asyncResult := b.chainConn.RescanAsync(startHash, addrs, nil) asyncResult := b.chainConn.RescanAsync(startHash, addrs, nil)
go func() { go func() {
if rescanErr := asyncResult.Receive(); rescanErr != nil { if rescanErr := asyncResult.Receive(); rescanErr != nil {
chainntnfs.Log.Errorf("Rescan to determine "+ chainntnfs.Log.Errorf("Rescan to determine "+
"the spend details of %v failed: %v", "the spend details of %v failed: %v",
spendRequest, rescanErr) ntfn.HistoricalDispatch.SpendRequest,
rescanErr)
} }
}() }()
@ -738,16 +730,16 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// We'll start by checking the backend's UTXO set to determine whether // We'll start by checking the backend's UTXO set to determine whether
// the outpoint has been spent. If it hasn't, we can return to the // the outpoint has been spent. If it hasn't, we can return to the
// caller as well. // caller as well.
txOut, err := b.chainConn.GetTxOut( txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
&spendRequest.OutPoint.Hash, spendRequest.OutPoint.Index, true,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if txOut != nil { if txOut != nil {
// We'll let the txNotifier know the outpoint is still unspent // We'll let the txNotifier know the outpoint is still unspent
// in order to begin updating its spend hint. // in order to begin updating its spend hint.
err := b.txNotifier.UpdateSpendDetails(spendRequest, nil) err := b.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, nil,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -759,25 +751,25 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// set, we'll determine when it happened by scanning the chain. We'll // set, we'll determine when it happened by scanning the chain. We'll
// begin by fetching the block hash of our starting height. // begin by fetching the block hash of our starting height.
startHash, err := b.chainConn.GetBlockHash( startHash, err := b.chainConn.GetBlockHash(
int64(historicalDispatch.StartHeight), int64(ntfn.HistoricalDispatch.StartHeight),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get block hash for height "+ return nil, fmt.Errorf("unable to get block hash for height "+
"%d: %v", historicalDispatch.StartHeight, err) "%d: %v", ntfn.HistoricalDispatch.StartHeight, err)
} }
// As a minimal optimization, we'll query the backend's transaction // As a minimal optimization, we'll query the backend's transaction
// index (if enabled) to determine if we have a better rescan starting // index (if enabled) to determine if we have a better rescan starting
// height. We can do this as the GetRawTransaction call will return the // height. We can do this as the GetRawTransaction call will return the
// hash of the block it was included in within the chain. // hash of the block it was included in within the chain.
tx, err := b.chainConn.GetRawTransactionVerbose(&spendRequest.OutPoint.Hash) tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil { if err != nil {
// Avoid returning an error if the transaction was not found to // Avoid returning an error if the transaction was not found to
// proceed with fallback methods. // proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError) jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo { if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid %v: %v", return nil, fmt.Errorf("unable to query for txid %v: %v",
spendRequest.OutPoint.Hash, err) outpoint.Hash, err)
} }
} }
@ -801,7 +793,7 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
"block %v: %v", blockHash, err) "block %v: %v", blockHash, err)
} }
if uint32(blockHeader.Height) > historicalDispatch.StartHeight { if uint32(blockHeader.Height) > ntfn.HistoricalDispatch.StartHeight {
startHash, err = b.chainConn.GetBlockHash( startHash, err = b.chainConn.GetBlockHash(
int64(blockHeader.Height), int64(blockHeader.Height),
) )
@ -824,13 +816,12 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// //
// TODO(wilmer): add retry logic if rescan fails? // TODO(wilmer): add retry logic if rescan fails?
asyncResult := b.chainConn.RescanAsync( asyncResult := b.chainConn.RescanAsync(
startHash, nil, []*wire.OutPoint{&spendRequest.OutPoint}, startHash, nil, []*wire.OutPoint{outpoint},
) )
go func() { go func() {
if rescanErr := asyncResult.Receive(); rescanErr != nil { if rescanErr := asyncResult.Receive(); rescanErr != nil {
chainntnfs.Log.Errorf("Rescan to determine the spend "+ chainntnfs.Log.Errorf("Rescan to determine the spend "+
"details of %v failed: %v", spendRequest, "details of %v failed: %v", outpoint, rescanErr)
rescanErr)
} }
}() }()

@ -37,7 +37,6 @@ const (
// TODO(roasbeef): heavily consolidate with NeutrinoNotifier code // TODO(roasbeef): heavily consolidate with NeutrinoNotifier code
// * maybe combine into single package? // * maybe combine into single package?
type NeutrinoNotifier struct { type NeutrinoNotifier struct {
spendClientCounter uint64 // To be used atomically.
epochClientCounter uint64 // To be used atomically. epochClientCounter uint64 // To be used atomically.
started int32 // To be used atomically. started int32 // To be used atomically.
@ -662,23 +661,11 @@ func (n *NeutrinoNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistr
func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// First, we'll construct a spend notification request and hand it off // Register the conf notification with the TxNotifier. A non-nil value
// to the txNotifier. // for `dispatch` will be returned if we are required to perform a
spendID := atomic.AddUint64(&n.spendClientCounter, 1) // manual scan for the confirmation. Otherwise the notifier will begin
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript) // watching at tip for the transaction to confirm.
if err != nil { ntfn, err := n.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
return nil, err
}
ntfn := &chainntnfs.SpendNtfn{
SpendID: spendID,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(func() {
n.txNotifier.CancelSpend(spendRequest, spendID)
}),
HeightHint: heightHint,
}
historicalDispatch, txNotifierTip, err := n.txNotifier.RegisterSpend(ntfn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -690,9 +677,12 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// //
// We'll update our filter first to ensure we can immediately detect the // We'll update our filter first to ensure we can immediately detect the
// spend at tip. // spend at tip.
if outpoint == nil {
outpoint = &chainntnfs.ZeroOutPoint
}
inputToWatch := neutrino.InputWithScript{ inputToWatch := neutrino.InputWithScript{
OutPoint: spendRequest.OutPoint, OutPoint: *outpoint,
PkScript: spendRequest.PkScript.Script(), PkScript: pkScript,
} }
updateOptions := []neutrino.UpdateOption{ updateOptions := []neutrino.UpdateOption{
neutrino.AddInputs(inputToWatch), neutrino.AddInputs(inputToWatch),
@ -703,10 +693,9 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// update. In the case of an output script spend request, we'll check if // update. In the case of an output script spend request, we'll check if
// we should perform a historical rescan and start from there, as we // we should perform a historical rescan and start from there, as we
// cannot do so with GetUtxo since it matches outpoints. // cannot do so with GetUtxo since it matches outpoints.
rewindHeight := txNotifierTip rewindHeight := ntfn.Height
if historicalDispatch != nil && if ntfn.HistoricalDispatch != nil && *outpoint == chainntnfs.ZeroOutPoint {
spendRequest.OutPoint == chainntnfs.ZeroOutPoint { rewindHeight = ntfn.HistoricalDispatch.StartHeight
rewindHeight = historicalDispatch.StartHeight
} }
updateOptions = append(updateOptions, neutrino.Rewind(rewindHeight)) updateOptions = append(updateOptions, neutrino.Rewind(rewindHeight))
@ -733,8 +722,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// scan of the chain, or if we already performed one like in the case of // scan of the chain, or if we already performed one like in the case of
// output script spend requests, then we can return early as there's // output script spend requests, then we can return early as there's
// nothing left for us to do. // nothing left for us to do.
if historicalDispatch == nil || if ntfn.HistoricalDispatch == nil || *outpoint == chainntnfs.ZeroOutPoint {
spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
return ntfn.Event, nil return ntfn.Event, nil
} }
@ -752,7 +740,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
currentHeight := uint32(n.bestBlock.Height) currentHeight := uint32(n.bestBlock.Height)
n.bestBlockMtx.RUnlock() n.bestBlockMtx.RUnlock()
if currentHeight >= historicalDispatch.StartHeight { if currentHeight >= ntfn.HistoricalDispatch.StartHeight {
break break
} }
@ -766,10 +754,10 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
spendReport, err := n.p2pNode.GetUtxo( spendReport, err := n.p2pNode.GetUtxo(
neutrino.WatchInputs(inputToWatch), neutrino.WatchInputs(inputToWatch),
neutrino.StartBlock(&waddrmgr.BlockStamp{ neutrino.StartBlock(&waddrmgr.BlockStamp{
Height: int32(historicalDispatch.StartHeight), Height: int32(ntfn.HistoricalDispatch.StartHeight),
}), }),
neutrino.EndBlock(&waddrmgr.BlockStamp{ neutrino.EndBlock(&waddrmgr.BlockStamp{
Height: int32(historicalDispatch.EndHeight), Height: int32(ntfn.HistoricalDispatch.EndHeight),
}), }),
neutrino.QuitChan(n.quit), neutrino.QuitChan(n.quit),
) )
@ -784,7 +772,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
if spendReport != nil && spendReport.SpendingTx != nil { if spendReport != nil && spendReport.SpendingTx != nil {
spendingTxHash := spendReport.SpendingTx.TxHash() spendingTxHash := spendReport.SpendingTx.TxHash()
spendDetails = &chainntnfs.SpendDetail{ spendDetails = &chainntnfs.SpendDetail{
SpentOutPoint: &spendRequest.OutPoint, SpentOutPoint: outpoint,
SpenderTxHash: &spendingTxHash, SpenderTxHash: &spendingTxHash,
SpendingTx: spendReport.SpendingTx, SpendingTx: spendReport.SpendingTx,
SpenderInputIndex: spendReport.SpendingInputIndex, SpenderInputIndex: spendReport.SpendingInputIndex,
@ -796,7 +784,9 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// not, we'll mark our historical rescan as complete to ensure the // not, we'll mark our historical rescan as complete to ensure the
// outpoint's spend hint gets updated upon connected/disconnected // outpoint's spend hint gets updated upon connected/disconnected
// blocks. // blocks.
err = n.txNotifier.UpdateSpendDetails(spendRequest, spendDetails) err = n.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, spendDetails,
)
if err != nil { if err != nil {
chainntnfs.Log.Errorf("Failed to update spend details: %v", err) chainntnfs.Log.Errorf("Failed to update spend details: %v", err)
return return

@ -415,13 +415,33 @@ type HistoricalSpendDispatch struct {
EndHeight uint32 EndHeight uint32
} }
// SpendRegistration encompasses all of the information required for callers to
// retrieve details about a spend event.
type SpendRegistration struct {
// Event contains references to the channels that the notifications are
// to be sent over.
Event *SpendEvent
// HistoricalDispatch, if non-nil, signals to the client who registered
// the notification that they are responsible for attempting to manually
// rescan blocks for the txid/output script between the start and end
// heights.
HistoricalDispatch *HistoricalSpendDispatch
// Height is the height of the TxNotifier at the time the spend
// notification was registered. This can be used so that backends can
// request to be notified of spends from this point forwards.
Height uint32
}
// TxNotifier is a struct responsible for delivering transaction notifications // TxNotifier is a struct responsible for delivering transaction notifications
// to subscribers. These notifications can be of two different types: // to subscribers. These notifications can be of two different types:
// transaction/output script confirmations and/or outpoint/output script spends. // transaction/output script confirmations and/or outpoint/output script spends.
// The TxNotifier will watch the blockchain as new blocks come in, in order to // The TxNotifier will watch the blockchain as new blocks come in, in order to
// satisfy its client requests. // satisfy its client requests.
type TxNotifier struct { type TxNotifier struct {
confClientCounter uint64 // To be used atomically. confClientCounter uint64 // To be used atomically.
spendClientCounter uint64 // To be used atomically.
// currentHeight is the height of the tracked blockchain. It is used to // currentHeight is the height of the tracked blockchain. It is used to
// determine the number of confirmations a tx has and ensure blocks are // determine the number of confirmations a tx has and ensure blocks are
@ -885,29 +905,50 @@ func (n *TxNotifier) dispatchConfDetails(
return nil return nil
} }
// newSpendNtfn validates all of the parameters required to successfully create
// and register a spend notification.
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
spendRequest, err := NewSpendRequest(outpoint, pkScript)
if err != nil {
return nil, err
}
spendID := atomic.AddUint64(&n.spendClientCounter, 1)
return &SpendNtfn{
SpendID: spendID,
SpendRequest: spendRequest,
Event: NewSpendEvent(func() {
n.CancelSpend(spendRequest, spendID)
}),
HeightHint: heightHint,
}, nil
}
// RegisterSpend handles a new spend notification request. The client will be // RegisterSpend handles a new spend notification request. The client will be
// notified once the outpoint/output script is detected as spent within the // notified once the outpoint/output script is detected as spent within the
// chain. // chain.
// //
// The registration succeeds if no error is returned. If the returned
// HistoricalSpendDisaptch is non-nil, the caller is responsible for attempting
// to determine whether the outpoint/output script has been spent between the
// start and end heights. The notifier's current height is also returned so that
// backends can request to be notified of spends from this point forwards.
//
// NOTE: If the outpoint/output script has already been spent within the chain // NOTE: If the outpoint/output script has already been spent within the chain
// before the notifier's current tip, the spend details must be provided with // before the notifier's current tip, the spend details must be provided with
// the UpdateSpendDetails method, otherwise we will wait for the outpoint/output // the UpdateSpendDetails method, otherwise we will wait for the outpoint/output
// script to be spent at tip, even though it already has. // script to be spent at tip, even though it already has.
func (n *TxNotifier) RegisterSpend(ntfn *SpendNtfn) (*HistoricalSpendDispatch, func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
uint32, error) { heightHint uint32) (*SpendRegistration, error) {
select { select {
case <-n.quit: case <-n.quit:
return nil, 0, ErrTxNotifierExiting return nil, ErrTxNotifierExiting
default: default:
} }
// We'll start by performing a series of validation checks.
ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
if err != nil {
return nil, err
}
// Before proceeding to register the notification, we'll query our spend // Before proceeding to register the notification, we'll query our spend
// hint cache to determine whether a better one exists. // hint cache to determine whether a better one exists.
startHeight := ntfn.HeightHint startHeight := ntfn.HeightHint
@ -953,9 +994,16 @@ func (n *TxNotifier) RegisterSpend(ntfn *SpendNtfn) (*HistoricalSpendDispatch,
"registration since rescan has finished", "registration since rescan has finished",
ntfn.SpendRequest) ntfn.SpendRequest)
return nil, n.currentHeight, n.dispatchSpendDetails( err := n.dispatchSpendDetails(ntfn, spendSet.details)
ntfn, spendSet.details, if err != nil {
) return nil, err
}
return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// If there is an active rescan to determine whether the request has // If there is an active rescan to determine whether the request has
// been spent, then we won't trigger another one. // been spent, then we won't trigger another one.
@ -963,7 +1011,11 @@ func (n *TxNotifier) RegisterSpend(ntfn *SpendNtfn) (*HistoricalSpendDispatch,
Log.Debugf("Waiting for pending rescan to finish before "+ Log.Debugf("Waiting for pending rescan to finish before "+
"notifying %v at tip", ntfn.SpendRequest) "notifying %v at tip", ntfn.SpendRequest)
return nil, n.currentHeight, nil return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// Otherwise, we'll fall through and let the caller know that a rescan // Otherwise, we'll fall through and let the caller know that a rescan
// should be dispatched to determine whether the request has already // should be dispatched to determine whether the request has already
@ -983,7 +1035,11 @@ func (n *TxNotifier) RegisterSpend(ntfn *SpendNtfn) (*HistoricalSpendDispatch,
// spend hints for this request get updated upon // spend hints for this request get updated upon
// connected/disconnected blocks. // connected/disconnected blocks.
spendSet.rescanStatus = rescanComplete spendSet.rescanStatus = rescanComplete
return nil, n.currentHeight, nil return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
} }
// We'll set the rescan status to pending to ensure subsequent // We'll set the rescan status to pending to ensure subsequent
@ -993,11 +1049,15 @@ func (n *TxNotifier) RegisterSpend(ntfn *SpendNtfn) (*HistoricalSpendDispatch,
Log.Debugf("Dispatching historical spend rescan for %v", Log.Debugf("Dispatching historical spend rescan for %v",
ntfn.SpendRequest) ntfn.SpendRequest)
return &HistoricalSpendDispatch{ return &SpendRegistration{
SpendRequest: ntfn.SpendRequest, Event: ntfn.Event,
StartHeight: startHeight, HistoricalDispatch: &HistoricalSpendDispatch{
EndHeight: n.currentHeight, SpendRequest: ntfn.SpendRequest,
}, n.currentHeight, nil StartHeight: startHeight,
EndHeight: n.currentHeight,
},
Height: n.currentHeight,
}, nil
} }
// CancelSpend cancels an existing request for a spend notification of an // CancelSpend cancels an existing request for a spend notification of an

@ -479,14 +479,9 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) {
// We'll start off by registering for a spend notification of an // We'll start off by registering for a spend notification of an
// outpoint. // outpoint.
ntfn := &chainntnfs.SpendNtfn{ op := wire.OutPoint{Index: 1}
SpendRequest: chainntnfs.SpendRequest{ ntfn, err := n.RegisterSpend(&op, testRawScript, 1)
OutPoint: wire.OutPoint{Index: 1}, if err != nil {
PkScript: testScript,
},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
@ -503,14 +498,14 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) {
// spend notification. // spend notification.
spendTx := wire.NewMsgTx(2) spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{ spendTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: ntfn.OutPoint, PreviousOutPoint: op,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
spendTxHash := spendTx.TxHash() spendTxHash := spendTx.TxHash()
block := btcutil.NewBlock(&wire.MsgBlock{ block := btcutil.NewBlock(&wire.MsgBlock{
Transactions: []*wire.MsgTx{spendTx}, Transactions: []*wire.MsgTx{spendTx},
}) })
err := n.ConnectTip(block.Hash(), 11, block.Transactions()) err = n.ConnectTip(block.Hash(), 11, block.Transactions())
if err != nil { if err != nil {
t.Fatalf("unable to connect block: %v", err) t.Fatalf("unable to connect block: %v", err)
} }
@ -519,7 +514,7 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) {
} }
expectedSpendDetails := &chainntnfs.SpendDetail{ expectedSpendDetails := &chainntnfs.SpendDetail{
SpentOutPoint: &ntfn.OutPoint, SpentOutPoint: &op,
SpenderTxHash: &spendTxHash, SpenderTxHash: &spendTxHash,
SpendingTx: spendTx, SpendingTx: spendTx,
SpenderInputIndex: 0, SpenderInputIndex: 0,
@ -593,11 +588,8 @@ func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
// We'll register for a spend notification of the outpoint and ensure // We'll register for a spend notification of the outpoint and ensure
// that a notification isn't dispatched. // that a notification isn't dispatched.
ntfn := &chainntnfs.SpendNtfn{ ntfn, err := n.RegisterSpend(&spentOutpoint, testRawScript, 1)
SpendRequest: chainntnfs.SpendRequest{OutPoint: spentOutpoint}, if err != nil {
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
@ -611,7 +603,9 @@ func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
// we'll hand off the spending details of the outpoint to the notifier // we'll hand off the spending details of the outpoint to the notifier
// as it is not possible for it to view historical events in the chain. // as it is not possible for it to view historical events in the chain.
// By doing this, we replicate the functionality of the ChainNotifier. // By doing this, we replicate the functionality of the ChainNotifier.
err := n.UpdateSpendDetails(ntfn.SpendRequest, expectedSpendDetails) err = n.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, expectedSpendDetails,
)
if err != nil { if err != nil {
t.Fatalf("unable to update spend details: %v", err) t.Fatalf("unable to update spend details: %v", err)
} }
@ -721,35 +715,23 @@ func TestTxNotifierMultipleHistoricalSpendRescans(t *testing.T) {
// The first registration for an outpoint in the notifier should request // The first registration for an outpoint in the notifier should request
// a historical spend rescan as it does not have a historical view of // a historical spend rescan as it does not have a historical view of
// the chain. // the chain.
spendRequest := chainntnfs.SpendRequest{ op := wire.OutPoint{Index: 1}
OutPoint: wire.OutPoint{Index: 1}, ntfn1, err := n.RegisterSpend(&op, testRawScript, 1)
}
ntfn1 := &chainntnfs.SpendNtfn{
SpendID: 0,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(nil),
}
historicalDispatch1, _, err := n.RegisterSpend(ntfn1)
if err != nil { if err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
if historicalDispatch1 == nil { if ntfn1.HistoricalDispatch == nil {
t.Fatal("expected to receive historical dispatch request") t.Fatal("expected to receive historical dispatch request")
} }
// We'll register another spend notification for the same outpoint. This // We'll register another spend notification for the same outpoint. This
// should not request a historical spend rescan since the first one is // should not request a historical spend rescan since the first one is
// still pending. // still pending.
ntfn2 := &chainntnfs.SpendNtfn{ ntfn2, err := n.RegisterSpend(&op, testRawScript, 1)
SpendID: 1,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(nil),
}
historicalDispatch2, _, err := n.RegisterSpend(ntfn2)
if err != nil { if err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
if historicalDispatch2 != nil { if ntfn2.HistoricalDispatch != nil {
t.Fatal("received unexpected historical rescan request") t.Fatal("received unexpected historical rescan request")
} }
@ -758,27 +740,24 @@ func TestTxNotifierMultipleHistoricalSpendRescans(t *testing.T) {
// historical rescan request since the confirmation details should be // historical rescan request since the confirmation details should be
// cached. // cached.
spendDetails := &chainntnfs.SpendDetail{ spendDetails := &chainntnfs.SpendDetail{
SpentOutPoint: &ntfn2.OutPoint, SpentOutPoint: &op,
SpenderTxHash: &chainntnfs.ZeroHash, SpenderTxHash: &chainntnfs.ZeroHash,
SpendingTx: wire.NewMsgTx(2), SpendingTx: wire.NewMsgTx(2),
SpenderInputIndex: 0, SpenderInputIndex: 0,
SpendingHeight: startingHeight - 1, SpendingHeight: startingHeight - 1,
} }
err = n.UpdateSpendDetails(ntfn2.SpendRequest, spendDetails) err = n.UpdateSpendDetails(
ntfn1.HistoricalDispatch.SpendRequest, spendDetails,
)
if err != nil { if err != nil {
t.Fatalf("unable to update spend details: %v", err) t.Fatalf("unable to update spend details: %v", err)
} }
ntfn3 := &chainntnfs.SpendNtfn{ ntfn3, err := n.RegisterSpend(&op, testRawScript, 1)
SpendID: 2,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(nil),
}
historicalDispatch3, _, err := n.RegisterSpend(ntfn3)
if err != nil { if err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
if historicalDispatch3 != nil { if ntfn3.HistoricalDispatch != nil {
t.Fatal("received unexpected historical rescan request") t.Fatal("received unexpected historical rescan request")
} }
} }
@ -872,19 +851,14 @@ func TestTxNotifierMultipleHistoricalNtfns(t *testing.T) {
} }
// Similarly, we'll do the same thing but for spend notifications. // Similarly, we'll do the same thing but for spend notifications.
spendRequest := chainntnfs.SpendRequest{ op := wire.OutPoint{Index: 1}
OutPoint: wire.OutPoint{Index: 1}, spendNtfns := make([]*chainntnfs.SpendRegistration, numNtfns)
}
spendNtfns := make([]*chainntnfs.SpendNtfn, numNtfns)
for i := uint64(0); i < numNtfns; i++ { for i := uint64(0); i < numNtfns; i++ {
spendNtfns[i] = &chainntnfs.SpendNtfn{ ntfn, err := n.RegisterSpend(&op, testRawScript, 1)
SpendID: i, if err != nil {
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(spendNtfns[i]); err != nil {
t.Fatalf("unable to register spend ntfn #%d: %v", i, err) t.Fatalf("unable to register spend ntfn #%d: %v", i, err)
} }
spendNtfns[i] = ntfn
} }
// Ensure none of them have received the spend details. // Ensure none of them have received the spend details.
@ -901,13 +875,15 @@ func TestTxNotifierMultipleHistoricalNtfns(t *testing.T) {
// following spend details. We'll let the notifier know so that it can // following spend details. We'll let the notifier know so that it can
// stop watching at tip. // stop watching at tip.
expectedSpendDetails := &chainntnfs.SpendDetail{ expectedSpendDetails := &chainntnfs.SpendDetail{
SpentOutPoint: &spendNtfns[0].OutPoint, SpentOutPoint: &op,
SpenderTxHash: &chainntnfs.ZeroHash, SpenderTxHash: &chainntnfs.ZeroHash,
SpendingTx: wire.NewMsgTx(2), SpendingTx: wire.NewMsgTx(2),
SpenderInputIndex: 0, SpenderInputIndex: 0,
SpendingHeight: startingHeight - 1, SpendingHeight: startingHeight - 1,
} }
err = n.UpdateSpendDetails(spendNtfns[0].SpendRequest, expectedSpendDetails) err = n.UpdateSpendDetails(
spendNtfns[0].HistoricalDispatch.SpendRequest, expectedSpendDetails,
)
if err != nil { if err != nil {
t.Fatalf("unable to update spend details: %v", err) t.Fatalf("unable to update spend details: %v", err)
} }
@ -928,16 +904,11 @@ func TestTxNotifierMultipleHistoricalNtfns(t *testing.T) {
// cached, we'll register another client for the same outpoint. We // cached, we'll register another client for the same outpoint. We
// should not see a historical rescan request and the spend notification // should not see a historical rescan request and the spend notification
// should come through immediately. // should come through immediately.
extraSpendNtfn := &chainntnfs.SpendNtfn{ extraSpendNtfn, err := n.RegisterSpend(&op, testRawScript, 1)
SpendID: numNtfns + 1,
SpendRequest: spendRequest,
Event: chainntnfs.NewSpendEvent(nil),
}
historicalSpendRescan, _, err := n.RegisterSpend(extraSpendNtfn)
if err != nil { if err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
if historicalSpendRescan != nil { if extraSpendNtfn.HistoricalDispatch != nil {
t.Fatal("received unexpected historical rescan request") t.Fatal("received unexpected historical rescan request")
} }
@ -1035,26 +1006,15 @@ func TestTxNotifierCancelSpend(t *testing.T) {
// We'll register two notification requests. Only the second one will be // We'll register two notification requests. Only the second one will be
// canceled. // canceled.
ntfn1 := &chainntnfs.SpendNtfn{ op1 := wire.OutPoint{Index: 1}
SpendID: 0, ntfn1, err := n.RegisterSpend(&op1, testRawScript, 1)
SpendRequest: chainntnfs.SpendRequest{ if err != nil {
OutPoint: wire.OutPoint{Index: 1},
PkScript: testScript,
},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn1); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
ntfn2 := &chainntnfs.SpendNtfn{ op2 := wire.OutPoint{Index: 2}
SpendID: 1, ntfn2, err := n.RegisterSpend(&op2, testRawScript, 1)
SpendRequest: chainntnfs.SpendRequest{ if err != nil {
OutPoint: wire.OutPoint{Index: 2},
},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn2); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
@ -1062,12 +1022,12 @@ func TestTxNotifierCancelSpend(t *testing.T) {
// block containing it. // block containing it.
spendTx := wire.NewMsgTx(2) spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{ spendTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: ntfn1.OutPoint, PreviousOutPoint: op1,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
spendTxHash := spendTx.TxHash() spendTxHash := spendTx.TxHash()
expectedSpendDetails := &chainntnfs.SpendDetail{ expectedSpendDetails := &chainntnfs.SpendDetail{
SpentOutPoint: &ntfn1.OutPoint, SpentOutPoint: &op1,
SpenderTxHash: &spendTxHash, SpenderTxHash: &spendTxHash,
SpendingTx: spendTx, SpendingTx: spendTx,
SpenderInputIndex: 0, SpenderInputIndex: 0,
@ -1080,9 +1040,9 @@ func TestTxNotifierCancelSpend(t *testing.T) {
// Before extending the notifier's tip with the dummy block above, we'll // Before extending the notifier's tip with the dummy block above, we'll
// cancel the second request. // cancel the second request.
n.CancelSpend(ntfn2.SpendRequest, ntfn2.SpendID) n.CancelSpend(ntfn2.HistoricalDispatch.SpendRequest, 2)
err := n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions()) err = n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions())
if err != nil { if err != nil {
t.Fatalf("unable to connect block: %v", err) t.Fatalf("unable to connect block: %v", err)
} }
@ -1404,35 +1364,29 @@ func TestTxNotifierSpendReorg(t *testing.T) {
// We'll have two outpoints that will be spent throughout the test. The // We'll have two outpoints that will be spent throughout the test. The
// first will be spent and will not experience a reorg, while the second // first will be spent and will not experience a reorg, while the second
// one will. // one will.
spendRequest1 := chainntnfs.SpendRequest{ op1 := wire.OutPoint{Index: 1}
OutPoint: wire.OutPoint{Index: 1},
PkScript: testScript,
}
spendTx1 := wire.NewMsgTx(2) spendTx1 := wire.NewMsgTx(2)
spendTx1.AddTxIn(&wire.TxIn{ spendTx1.AddTxIn(&wire.TxIn{
PreviousOutPoint: spendRequest1.OutPoint, PreviousOutPoint: op1,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
spendTxHash1 := spendTx1.TxHash() spendTxHash1 := spendTx1.TxHash()
expectedSpendDetails1 := &chainntnfs.SpendDetail{ expectedSpendDetails1 := &chainntnfs.SpendDetail{
SpentOutPoint: &spendRequest1.OutPoint, SpentOutPoint: &op1,
SpenderTxHash: &spendTxHash1, SpenderTxHash: &spendTxHash1,
SpendingTx: spendTx1, SpendingTx: spendTx1,
SpenderInputIndex: 0, SpenderInputIndex: 0,
SpendingHeight: startingHeight + 1, SpendingHeight: startingHeight + 1,
} }
spendRequest2 := chainntnfs.SpendRequest{ op2 := wire.OutPoint{Index: 2}
OutPoint: wire.OutPoint{Index: 2},
PkScript: testScript,
}
spendTx2 := wire.NewMsgTx(2) spendTx2 := wire.NewMsgTx(2)
spendTx2.AddTxIn(&wire.TxIn{ spendTx2.AddTxIn(&wire.TxIn{
PreviousOutPoint: chainntnfs.ZeroOutPoint, PreviousOutPoint: chainntnfs.ZeroOutPoint,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
spendTx2.AddTxIn(&wire.TxIn{ spendTx2.AddTxIn(&wire.TxIn{
PreviousOutPoint: spendRequest2.OutPoint, PreviousOutPoint: op2,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
spendTxHash2 := spendTx2.TxHash() spendTxHash2 := spendTx2.TxHash()
@ -1441,7 +1395,7 @@ func TestTxNotifierSpendReorg(t *testing.T) {
// different height, so we'll need to construct the spend details for // different height, so we'll need to construct the spend details for
// before and after the reorg. // before and after the reorg.
expectedSpendDetails2BeforeReorg := chainntnfs.SpendDetail{ expectedSpendDetails2BeforeReorg := chainntnfs.SpendDetail{
SpentOutPoint: &spendRequest2.OutPoint, SpentOutPoint: &op2,
SpenderTxHash: &spendTxHash2, SpenderTxHash: &spendTxHash2,
SpendingTx: spendTx2, SpendingTx: spendTx2,
SpenderInputIndex: 1, SpenderInputIndex: 1,
@ -1454,21 +1408,13 @@ func TestTxNotifierSpendReorg(t *testing.T) {
expectedSpendDetails2AfterReorg.SpendingHeight++ expectedSpendDetails2AfterReorg.SpendingHeight++
// We'll register for a spend notification for each outpoint above. // We'll register for a spend notification for each outpoint above.
ntfn1 := &chainntnfs.SpendNtfn{ ntfn1, err := n.RegisterSpend(&op1, testRawScript, 1)
SpendID: 78, if err != nil {
SpendRequest: spendRequest1,
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn1); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
ntfn2 := &chainntnfs.SpendNtfn{ ntfn2, err := n.RegisterSpend(&op2, testRawScript, 1)
SpendID: 21, if err != nil {
SpendRequest: spendRequest2,
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn2); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
@ -1477,7 +1423,7 @@ func TestTxNotifierSpendReorg(t *testing.T) {
block1 := btcutil.NewBlock(&wire.MsgBlock{ block1 := btcutil.NewBlock(&wire.MsgBlock{
Transactions: []*wire.MsgTx{spendTx1}, Transactions: []*wire.MsgTx{spendTx1},
}) })
err := n.ConnectTip(block1.Hash(), startingHeight+1, block1.Transactions()) err = n.ConnectTip(block1.Hash(), startingHeight+1, block1.Transactions())
if err != nil { if err != nil {
t.Fatalf("unable to connect block: %v", err) t.Fatalf("unable to connect block: %v", err)
} }
@ -1836,40 +1782,27 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
) )
// Create two test outpoints and register them for spend notifications. // Create two test outpoints and register them for spend notifications.
ntfn1 := &chainntnfs.SpendNtfn{ op1 := wire.OutPoint{Index: 1}
SpendID: 1, ntfn1, err := n.RegisterSpend(&op1, testRawScript, 1)
SpendRequest: chainntnfs.SpendRequest{ if err != nil {
OutPoint: wire.OutPoint{Index: 1},
PkScript: testScript,
},
Event: chainntnfs.NewSpendEvent(nil),
}
ntfn2 := &chainntnfs.SpendNtfn{
SpendID: 2,
SpendRequest: chainntnfs.SpendRequest{
OutPoint: wire.OutPoint{Index: 2},
PkScript: testScript,
},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(ntfn1); err != nil {
t.Fatalf("unable to register spend for op1: %v", err) t.Fatalf("unable to register spend for op1: %v", err)
} }
if _, _, err := n.RegisterSpend(ntfn2); err != nil { op2 := wire.OutPoint{Index: 2}
ntfn2, err := n.RegisterSpend(&op2, testRawScript, 1)
if err != nil {
t.Fatalf("unable to register spend for op2: %v", err) t.Fatalf("unable to register spend for op2: %v", err)
} }
// Both outpoints should not have a spend hint set upon registration, as // Both outpoints should not have a spend hint set upon registration, as
// we must first determine whether they have already been spent in the // we must first determine whether they have already been spent in the
// chain. // chain.
_, err := hintCache.QuerySpendHint(ntfn1.SpendRequest) _, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != chainntnfs.ErrSpendHintNotFound { if err != chainntnfs.ErrSpendHintNotFound {
t.Fatalf("unexpected error when querying for height hint "+ t.Fatalf("unexpected error when querying for height hint "+
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound, "expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
err) err)
} }
_, err = hintCache.QuerySpendHint(ntfn2.SpendRequest) _, err = hintCache.QuerySpendHint(ntfn2.HistoricalDispatch.SpendRequest)
if err != chainntnfs.ErrSpendHintNotFound { if err != chainntnfs.ErrSpendHintNotFound {
t.Fatalf("unexpected error when querying for height hint "+ t.Fatalf("unexpected error when querying for height hint "+
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound, "expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
@ -1891,13 +1824,13 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// Since we haven't called UpdateSpendDetails on any of the test // Since we haven't called UpdateSpendDetails on any of the test
// outpoints, this implies that there is a still a pending historical // outpoints, this implies that there is a still a pending historical
// rescan for them, so their spend hints should not be created/updated. // rescan for them, so their spend hints should not be created/updated.
_, err = hintCache.QuerySpendHint(ntfn1.SpendRequest) _, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != chainntnfs.ErrSpendHintNotFound { if err != chainntnfs.ErrSpendHintNotFound {
t.Fatalf("unexpected error when querying for height hint "+ t.Fatalf("unexpected error when querying for height hint "+
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound, "expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
err) err)
} }
_, err = hintCache.QuerySpendHint(ntfn2.SpendRequest) _, err = hintCache.QuerySpendHint(ntfn2.HistoricalDispatch.SpendRequest)
if err != chainntnfs.ErrSpendHintNotFound { if err != chainntnfs.ErrSpendHintNotFound {
t.Fatalf("unexpected error when querying for height hint "+ t.Fatalf("unexpected error when querying for height hint "+
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound, "expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
@ -1907,10 +1840,12 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// Now, we'll simulate that their historical rescans have finished by // Now, we'll simulate that their historical rescans have finished by
// calling UpdateSpendDetails. This should allow their spend hints to be // calling UpdateSpendDetails. This should allow their spend hints to be
// updated upon every block connected/disconnected. // updated upon every block connected/disconnected.
if err := n.UpdateSpendDetails(ntfn1.SpendRequest, nil); err != nil { err = n.UpdateSpendDetails(ntfn1.HistoricalDispatch.SpendRequest, nil)
if err != nil {
t.Fatalf("unable to update spend details: %v", err) t.Fatalf("unable to update spend details: %v", err)
} }
if err := n.UpdateSpendDetails(ntfn2.SpendRequest, nil); err != nil { err = n.UpdateSpendDetails(ntfn2.HistoricalDispatch.SpendRequest, nil)
if err != nil {
t.Fatalf("unable to update spend details: %v", err) t.Fatalf("unable to update spend details: %v", err)
} }
@ -1918,7 +1853,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// of the first outpoint. // of the first outpoint.
spendTx1 := wire.NewMsgTx(2) spendTx1 := wire.NewMsgTx(2)
spendTx1.AddTxIn(&wire.TxIn{ spendTx1.AddTxIn(&wire.TxIn{
PreviousOutPoint: ntfn1.OutPoint, PreviousOutPoint: op1,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
block1 := btcutil.NewBlock(&wire.MsgBlock{ block1 := btcutil.NewBlock(&wire.MsgBlock{
@ -1935,14 +1870,14 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// Both outpoints should have their spend hints reflect the height of // Both outpoints should have their spend hints reflect the height of
// the new block being connected due to the first outpoint being spent // the new block being connected due to the first outpoint being spent
// at this height, and the second outpoint still being unspent. // at this height, and the second outpoint still being unspent.
op1Hint, err := hintCache.QuerySpendHint(ntfn1.SpendRequest) op1Hint, err := hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err) t.Fatalf("unable to query for spend hint of op1: %v", err)
} }
if op1Hint != op1Height { if op1Hint != op1Height {
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint) t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
} }
op2Hint, err := hintCache.QuerySpendHint(ntfn2.SpendRequest) op2Hint, err := hintCache.QuerySpendHint(ntfn2.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op2: %v", err) t.Fatalf("unable to query for spend hint of op2: %v", err)
} }
@ -1953,7 +1888,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// Then, we'll create another block that spends the second outpoint. // Then, we'll create another block that spends the second outpoint.
spendTx2 := wire.NewMsgTx(2) spendTx2 := wire.NewMsgTx(2)
spendTx2.AddTxIn(&wire.TxIn{ spendTx2.AddTxIn(&wire.TxIn{
PreviousOutPoint: ntfn2.OutPoint, PreviousOutPoint: op2,
SignatureScript: testSigScript, SignatureScript: testSigScript,
}) })
block2 := btcutil.NewBlock(&wire.MsgBlock{ block2 := btcutil.NewBlock(&wire.MsgBlock{
@ -1970,14 +1905,14 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// Only the second outpoint should have its spend hint updated due to // Only the second outpoint should have its spend hint updated due to
// being spent within the new block. The first outpoint's spend hint // being spent within the new block. The first outpoint's spend hint
// should remain the same as it's already been spent before. // should remain the same as it's already been spent before.
op1Hint, err = hintCache.QuerySpendHint(ntfn1.SpendRequest) op1Hint, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err) t.Fatalf("unable to query for spend hint of op1: %v", err)
} }
if op1Hint != op1Height { if op1Hint != op1Height {
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint) t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
} }
op2Hint, err = hintCache.QuerySpendHint(ntfn2.SpendRequest) op2Hint, err = hintCache.QuerySpendHint(ntfn2.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op2: %v", err) t.Fatalf("unable to query for spend hint of op2: %v", err)
} }
@ -1995,14 +1930,14 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
// to the previous height, as that's where its spending transaction was // to the previous height, as that's where its spending transaction was
// included in within the chain. The first outpoint's spend hint should // included in within the chain. The first outpoint's spend hint should
// remain the same. // remain the same.
op1Hint, err = hintCache.QuerySpendHint(ntfn1.SpendRequest) op1Hint, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err) t.Fatalf("unable to query for spend hint of op1: %v", err)
} }
if op1Hint != op1Height { if op1Hint != op1Height {
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint) t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
} }
op2Hint, err = hintCache.QuerySpendHint(ntfn2.SpendRequest) op2Hint, err = hintCache.QuerySpendHint(ntfn2.HistoricalDispatch.SpendRequest)
if err != nil { if err != nil {
t.Fatalf("unable to query for spend hint of op2: %v", err) t.Fatalf("unable to query for spend hint of op2: %v", err)
} }
@ -2027,16 +1962,8 @@ func TestTxNotifierNtfnDone(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to register conf ntfn: %v", err) t.Fatalf("unable to register conf ntfn: %v", err)
} }
spendNtfn, err := n.RegisterSpend(&chainntnfs.ZeroOutPoint, testRawScript, 1)
spendNtfn := &chainntnfs.SpendNtfn{ if err != nil {
SpendID: 2,
SpendRequest: chainntnfs.SpendRequest{
OutPoint: chainntnfs.ZeroOutPoint,
PkScript: testScript,
},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(spendNtfn); err != nil {
t.Fatalf("unable to register spend: %v", err) t.Fatalf("unable to register spend: %v", err)
} }
@ -2165,13 +2092,8 @@ func TestTxNotifierTearDown(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to register conf ntfn: %v", err) t.Fatalf("unable to register conf ntfn: %v", err)
} }
spendNtfn, err := n.RegisterSpend(&chainntnfs.ZeroOutPoint, testRawScript, 1)
spendNtfn := &chainntnfs.SpendNtfn{ if err != nil {
SpendID: 1,
SpendRequest: chainntnfs.SpendRequest{OutPoint: chainntnfs.ZeroOutPoint},
Event: chainntnfs.NewSpendEvent(nil),
}
if _, _, err := n.RegisterSpend(spendNtfn); err != nil {
t.Fatalf("unable to register spend ntfn: %v", err) t.Fatalf("unable to register spend ntfn: %v", err)
} }
@ -2212,7 +2134,8 @@ func TestTxNotifierTearDown(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected confirmation registration to fail") t.Fatal("expected confirmation registration to fail")
} }
if _, _, err := n.RegisterSpend(spendNtfn); err == nil { _, err = n.RegisterSpend(&chainntnfs.ZeroOutPoint, testRawScript, 1)
if err == nil {
t.Fatal("expected spend registration to fail") t.Fatal("expected spend registration to fail")
} }
} }