mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 22:46:40 +01:00
Merge pull request #2314 from wpaulino/chainnotifier-subserver
chainntnfs+lnrpc/chainrpc: add ChainNotifier RPC subserver
This commit is contained in:
commit
375be936ce
29 changed files with 4587 additions and 1420 deletions
|
@ -1,6 +1,8 @@
|
|||
package bitcoindnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
@ -8,6 +10,7 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
|
@ -23,11 +26,6 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chainntnfs: system interrupt " +
|
||||
"while attempting to register for spend notification.")
|
||||
|
||||
// ErrTransactionNotFound is an error returned when we attempt to find a
|
||||
// transaction by manually scanning the chain within a specific range
|
||||
// but it is not found.
|
||||
|
@ -58,7 +56,8 @@ type BitcoindNotifier struct {
|
|||
started int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
chainConn *chain.BitcoindClient
|
||||
chainConn *chain.BitcoindClient
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
notificationCancels chan interface{}
|
||||
notificationRegistry chan interface{}
|
||||
|
@ -88,12 +87,15 @@ type BitcoindNotifier struct {
|
|||
var _ chainntnfs.ChainNotifier = (*BitcoindNotifier)(nil)
|
||||
|
||||
// New returns a new BitcoindNotifier instance. This function assumes the
|
||||
// bitcoind node detailed in the passed configuration is already running, and
|
||||
// bitcoind node detailed in the passed configuration is already running, and
|
||||
// willing to accept RPC requests and new zmq clients.
|
||||
func New(chainConn *chain.BitcoindConn, spendHintCache chainntnfs.SpendHintCache,
|
||||
func New(chainConn *chain.BitcoindConn, chainParams *chaincfg.Params,
|
||||
spendHintCache chainntnfs.SpendHintCache,
|
||||
confirmHintCache chainntnfs.ConfirmHintCache) *BitcoindNotifier {
|
||||
|
||||
notifier := &BitcoindNotifier{
|
||||
chainParams: chainParams,
|
||||
|
||||
notificationCancels: make(chan interface{}),
|
||||
notificationRegistry: make(chan interface{}),
|
||||
|
||||
|
@ -229,7 +231,8 @@ out:
|
|||
defer b.wg.Done()
|
||||
|
||||
confDetails, _, err := b.historicalConfDetails(
|
||||
msg.TxID, msg.StartHeight, msg.EndHeight,
|
||||
msg.ConfRequest,
|
||||
msg.StartHeight, msg.EndHeight,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
|
@ -244,7 +247,7 @@ out:
|
|||
// cache at tip, since any pending
|
||||
// rescans have now completed.
|
||||
err = b.txNotifier.UpdateConfDetails(
|
||||
*msg.TxID, confDetails,
|
||||
msg.ConfRequest, confDetails,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
|
@ -265,29 +268,50 @@ out:
|
|||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Rescan to "+
|
||||
"determine the spend "+
|
||||
"details of %v failed: %v",
|
||||
msg.OutPoint, err)
|
||||
"details of %v within "+
|
||||
"range %d-%d failed: %v",
|
||||
msg.SpendRequest,
|
||||
msg.StartHeight,
|
||||
msg.EndHeight, err)
|
||||
}
|
||||
}()
|
||||
|
||||
case *blockEpochRegistration:
|
||||
chainntnfs.Log.Infof("New block epoch subscription")
|
||||
|
||||
b.blockEpochClients[msg.epochID] = msg
|
||||
if msg.bestBlock != nil {
|
||||
missedBlocks, err :=
|
||||
chainntnfs.GetClientMissedBlocks(
|
||||
b.chainConn, msg.bestBlock,
|
||||
b.bestBlock.Height, true,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
for _, block := range missedBlocks {
|
||||
b.notifyBlockEpochClient(msg,
|
||||
block.Height, block.Hash)
|
||||
}
|
||||
|
||||
// If the client did not provide their best
|
||||
// known block, then we'll immediately dispatch
|
||||
// a notification for the current tip.
|
||||
if msg.bestBlock == nil {
|
||||
b.notifyBlockEpochClient(
|
||||
msg, b.bestBlock.Height,
|
||||
b.bestBlock.Hash,
|
||||
)
|
||||
|
||||
msg.errorChan <- nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we'll attempt to deliver the
|
||||
// backlog of notifications from their best
|
||||
// known block.
|
||||
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
|
||||
b.chainConn, msg.bestBlock,
|
||||
b.bestBlock.Height, true,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, block := range missedBlocks {
|
||||
b.notifyBlockEpochClient(
|
||||
msg, block.Height, block.Hash,
|
||||
)
|
||||
}
|
||||
|
||||
msg.errorChan <- nil
|
||||
}
|
||||
|
||||
|
@ -372,14 +396,14 @@ out:
|
|||
continue
|
||||
}
|
||||
|
||||
tx := &item.TxRecord.MsgTx
|
||||
tx := btcutil.NewTx(&item.TxRecord.MsgTx)
|
||||
err := b.txNotifier.ProcessRelevantSpendTx(
|
||||
tx, item.Block.Height,
|
||||
tx, uint32(item.Block.Height),
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to "+
|
||||
"process transaction %v: %v",
|
||||
tx.TxHash(), err)
|
||||
tx.Hash(), err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,15 +414,28 @@ out:
|
|||
b.wg.Done()
|
||||
}
|
||||
|
||||
// historicalConfDetails looks up whether a transaction is already included in a
|
||||
// block in the active chain and, if so, returns details about the confirmation.
|
||||
func (b *BitcoindNotifier) historicalConfDetails(txid *chainhash.Hash,
|
||||
// historicalConfDetails looks up whether a confirmation request (txid/output
|
||||
// script) has already been included in a block in the active chain and, if so,
|
||||
// returns details about said block.
|
||||
func (b *BitcoindNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
|
||||
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
|
||||
chainntnfs.TxConfStatus, error) {
|
||||
|
||||
// If a txid was not provided, then we should dispatch upon seeing the
|
||||
// script on-chain, so we'll short-circuit straight to scanning manually
|
||||
// as there doesn't exist a script index to query.
|
||||
if confRequest.TxID == chainntnfs.ZeroHash {
|
||||
return b.confDetailsManually(
|
||||
confRequest, startHeight, endHeight,
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise, we'll dispatch upon seeing a transaction on-chain with the
|
||||
// given hash.
|
||||
//
|
||||
// We'll first attempt to retrieve the transaction using the node's
|
||||
// txindex.
|
||||
txConf, txStatus, err := b.confDetailsFromTxIndex(txid)
|
||||
txConf, txStatus, err := b.confDetailsFromTxIndex(&confRequest.TxID)
|
||||
|
||||
// We'll then check the status of the transaction lookup returned to
|
||||
// determine whether we should proceed with any fallback methods.
|
||||
|
@ -409,7 +446,7 @@ func (b *BitcoindNotifier) historicalConfDetails(txid *chainhash.Hash,
|
|||
case err != nil:
|
||||
chainntnfs.Log.Debugf("Failed getting conf details from "+
|
||||
"index (%v), scanning manually", err)
|
||||
return b.confDetailsManually(txid, startHeight, endHeight)
|
||||
return b.confDetailsManually(confRequest, startHeight, endHeight)
|
||||
|
||||
// The transaction was found within the node's mempool.
|
||||
case txStatus == chainntnfs.TxFoundMempool:
|
||||
|
@ -440,7 +477,7 @@ func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
|
||||
// If the transaction has some or all of its confirmations required,
|
||||
// then we may be able to dispatch it immediately.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(txid)
|
||||
rawTxRes, err := b.chainConn.GetRawTransactionVerbose(txid)
|
||||
if err != nil {
|
||||
// If the transaction lookup was successful, but it wasn't found
|
||||
// within the index itself, then we can exit early. We'll also
|
||||
|
@ -461,20 +498,19 @@ func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
// Make sure we actually retrieved a transaction that is included in a
|
||||
// block. If not, the transaction must be unconfirmed (in the mempool),
|
||||
// and we'll return TxFoundMempool together with a nil TxConfirmation.
|
||||
if tx.BlockHash == "" {
|
||||
if rawTxRes.BlockHash == "" {
|
||||
return nil, chainntnfs.TxFoundMempool, nil
|
||||
}
|
||||
|
||||
// As we need to fully populate the returned TxConfirmation struct,
|
||||
// grab the block in which the transaction was confirmed so we can
|
||||
// locate its exact index within the block.
|
||||
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
|
||||
blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block hash %v for "+
|
||||
"historical dispatch: %v", tx.BlockHash, err)
|
||||
"historical dispatch: %v", rawTxRes.BlockHash, err)
|
||||
}
|
||||
|
||||
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
|
@ -484,36 +520,49 @@ func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
|
||||
// If the block was obtained, locate the transaction's index within the
|
||||
// block so we can give the subscriber full confirmation details.
|
||||
targetTxidStr := txid.String()
|
||||
txidStr := txid.String()
|
||||
for txIndex, txHash := range block.Tx {
|
||||
if txHash == targetTxidStr {
|
||||
details := &chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}
|
||||
return details, chainntnfs.TxFoundIndex, nil
|
||||
if txHash != txidStr {
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the hex-encoded transaction to include it in the
|
||||
// confirmation details.
|
||||
rawTx, err := hex.DecodeString(rawTxRes.Hex)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
txHash, err)
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
|
||||
return nil, chainntnfs.TxFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
txHash, err)
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: &tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}, chainntnfs.TxFoundIndex, nil
|
||||
}
|
||||
|
||||
// We return an error because we should have found the transaction
|
||||
// within the block, but didn't.
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
fmt.Errorf("unable to locate tx %v in block %v", txid,
|
||||
blockHash)
|
||||
return nil, chainntnfs.TxNotFoundIndex, fmt.Errorf("unable to locate "+
|
||||
"tx %v in block %v", txid, blockHash)
|
||||
}
|
||||
|
||||
// confDetailsManually looks up whether a transaction is already included in a
|
||||
// block in the active chain by scanning the chain's blocks, starting from the
|
||||
// earliest height the transaction could have been included in, to the current
|
||||
// height in the chain. If the transaction is found, its confirmation details
|
||||
// are returned. Otherwise, nil is returned.
|
||||
func (b *BitcoindNotifier) confDetailsManually(txid *chainhash.Hash,
|
||||
// confDetailsManually looks up whether a transaction/output script has already
|
||||
// been included in a block in the active chain by scanning the chain's blocks
|
||||
// within the given range. If the transaction/output script is found, its
|
||||
// confirmation details are returned. Otherwise, nil is returned.
|
||||
func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
|
||||
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation,
|
||||
chainntnfs.TxConfStatus, error) {
|
||||
|
||||
targetTxidStr := txid.String()
|
||||
|
||||
// Begin scanning blocks at every height to determine where the
|
||||
// transaction was included in.
|
||||
for height := currentHeight; height >= heightHint && height > 0; height-- {
|
||||
|
@ -522,7 +571,7 @@ func (b *BitcoindNotifier) confDetailsManually(txid *chainhash.Hash,
|
|||
select {
|
||||
case <-b.quit:
|
||||
return nil, chainntnfs.TxNotFoundManually,
|
||||
ErrChainNotifierShuttingDown
|
||||
chainntnfs.ErrChainNotifierShuttingDown
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -533,24 +582,27 @@ func (b *BitcoindNotifier) confDetailsManually(txid *chainhash.Hash,
|
|||
"with height %d", height)
|
||||
}
|
||||
|
||||
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
||||
block, err := b.chainConn.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundManually,
|
||||
fmt.Errorf("unable to get block with hash "+
|
||||
"%v: %v", blockHash, err)
|
||||
}
|
||||
|
||||
for txIndex, txHash := range block.Tx {
|
||||
// If we're able to find the transaction in this block,
|
||||
// return its confirmation details.
|
||||
if txHash == targetTxidStr {
|
||||
details := &chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
}
|
||||
return details, chainntnfs.TxFoundManually, nil
|
||||
// For every transaction in the block, check which one matches
|
||||
// our request. If we find one that does, we can dispatch its
|
||||
// confirmation details.
|
||||
for txIndex, tx := range block.Transactions {
|
||||
if !confRequest.MatchesTx(tx) {
|
||||
continue
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
}, chainntnfs.TxFoundManually, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,33 +672,57 @@ func (b *BitcoindNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistr
|
|||
}
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint has been spent by a transaction on-chain. Once a spend of the target
|
||||
// outpoint has been detected, the details of the spending event will be sent
|
||||
// across the 'Spend' channel. The heightHint should represent the earliest
|
||||
// height in the chain where the transaction could have been spent in.
|
||||
// outpoint/output script has been spent by a transaction on-chain. When
|
||||
// intending to be notified of the spend of an output script, a nil outpoint
|
||||
// must be used. The heightHint should represent the earliest height in the
|
||||
// chain of the transaction that spent the outpoint/output script.
|
||||
//
|
||||
// Once a spend of has been detected, the details of the spending event will be
|
||||
// sent across the 'Spend' channel.
|
||||
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
||||
// First, we'll construct a spend notification request and hand it off
|
||||
// to the txNotifier.
|
||||
spendID := atomic.AddUint64(&b.spendClientCounter, 1)
|
||||
cancel := func() {
|
||||
b.txNotifier.CancelSpend(*outpoint, spendID)
|
||||
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
SpendID: spendID,
|
||||
OutPoint: *outpoint,
|
||||
PkScript: pkScript,
|
||||
Event: chainntnfs.NewSpendEvent(cancel),
|
||||
SpendID: spendID,
|
||||
SpendRequest: spendRequest,
|
||||
Event: chainntnfs.NewSpendEvent(func() {
|
||||
b.txNotifier.CancelSpend(spendRequest, spendID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
historicalDispatch, err := b.txNotifier.RegisterSpend(ntfn)
|
||||
historicalDispatch, _, err := b.txNotifier.RegisterSpend(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll then request the backend to notify us when it has detected the
|
||||
// outpoint/output script as spent.
|
||||
//
|
||||
// TODO(wilmer): use LoadFilter API instead.
|
||||
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
addr, err := spendRequest.PkScript.Address(b.chainParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs := []btcutil.Address{addr}
|
||||
if err := b.chainConn.NotifyReceived(addrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ops := []*wire.OutPoint{&spendRequest.OutPoint}
|
||||
if err := b.chainConn.NotifySpent(ops); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// for us to do.
|
||||
|
@ -654,23 +730,39 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// We'll then request the backend to notify us when it has detected the
|
||||
// outpoint as spent.
|
||||
if err := b.chainConn.NotifySpent([]*wire.OutPoint{outpoint}); err != nil {
|
||||
return nil, err
|
||||
// Otherwise, we'll need to dispatch a historical rescan to determine if
|
||||
// the outpoint was already spent at a previous height.
|
||||
//
|
||||
// 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
|
||||
// make for scripts.
|
||||
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
select {
|
||||
case b.notificationRegistry <- historicalDispatch:
|
||||
case <-b.quit:
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// In addition to the check above, we'll also check the backend's UTXO
|
||||
// set to determine whether the outpoint has been spent. If it hasn't,
|
||||
// we can return to the caller as well.
|
||||
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
|
||||
// When dispatching spends of outpoints, there are a number of checks we
|
||||
// can make to start our rescan from a better height or completely avoid
|
||||
// it.
|
||||
//
|
||||
// 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
|
||||
// caller as well.
|
||||
txOut, err := b.chainConn.GetTxOut(
|
||||
&spendRequest.OutPoint.Hash, spendRequest.OutPoint.Index, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if txOut != nil {
|
||||
// We'll let the txNotifier know the outpoint is still unspent
|
||||
// in order to begin updating its spend hint.
|
||||
err := b.txNotifier.UpdateSpendDetails(*outpoint, nil)
|
||||
err := b.txNotifier.UpdateSpendDetails(spendRequest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -678,22 +770,21 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll determine when the output was spent by scanning the
|
||||
// chain. We'll begin by determining where to start our historical
|
||||
// rescan.
|
||||
// Since the outpoint was spent, as it no longer exists within the UTXO
|
||||
// set, we'll determine when it happened by scanning the chain.
|
||||
//
|
||||
// As a minimal optimization, we'll query the backend's transaction
|
||||
// index (if enabled) to determine if we have a better rescan starting
|
||||
// height. We can do this as the GetRawTransaction call will return the
|
||||
// hash of the block it was included in within the chain.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(&spendRequest.OutPoint.Hash)
|
||||
if err != nil {
|
||||
// Avoid returning an error if the transaction was not found to
|
||||
// proceed with fallback methods.
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
||||
return nil, fmt.Errorf("unable to query for "+
|
||||
"txid %v: %v", outpoint.Hash, err)
|
||||
return nil, fmt.Errorf("unable to query for txid %v: %v",
|
||||
spendRequest.OutPoint.Hash, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,23 +813,24 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
}
|
||||
|
||||
// Now that we've determined the starting point of our rescan, we can
|
||||
// dispatch it.
|
||||
// dispatch it and return.
|
||||
select {
|
||||
case b.notificationRegistry <- historicalDispatch:
|
||||
return ntfn.Event, nil
|
||||
case <-b.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// disaptchSpendDetailsManually attempts to manually scan the chain within the
|
||||
// given height range for a transaction that spends the given outpoint. If one
|
||||
// is found, it's spending details are sent to the notifier dispatcher, which
|
||||
// will then dispatch the notification to all of its clients.
|
||||
// given height range for a transaction that spends the given outpoint/output
|
||||
// script. If one is found, it's spending details are sent to the TxNotifier,
|
||||
// which will then dispatch the notification to all of its clients.
|
||||
func (b *BitcoindNotifier) dispatchSpendDetailsManually(
|
||||
historicalDispatchDetails *chainntnfs.HistoricalSpendDispatch) error {
|
||||
|
||||
op := historicalDispatchDetails.OutPoint
|
||||
spendRequest := historicalDispatchDetails.SpendRequest
|
||||
startHeight := historicalDispatchDetails.StartHeight
|
||||
endHeight := historicalDispatchDetails.EndHeight
|
||||
|
||||
|
@ -749,7 +841,7 @@ func (b *BitcoindNotifier) dispatchSpendDetailsManually(
|
|||
// processing the next height.
|
||||
select {
|
||||
case <-b.quit:
|
||||
return ErrChainNotifierShuttingDown
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -765,61 +857,75 @@ func (b *BitcoindNotifier) dispatchSpendDetailsManually(
|
|||
"%v: %v", blockHash, err)
|
||||
}
|
||||
|
||||
// Then, we'll manually go over every transaction in it and
|
||||
// determine whether it spends the outpoint in question.
|
||||
// Then, we'll manually go over every input in every transaction
|
||||
// in it and determine whether it spends the request in
|
||||
// question. If we find one, we'll dispatch the spend details.
|
||||
for _, tx := range block.Transactions {
|
||||
for i, txIn := range tx.TxIn {
|
||||
if txIn.PreviousOutPoint != op {
|
||||
continue
|
||||
}
|
||||
|
||||
// If it does, we'll construct its spend details
|
||||
// and hand them over to the TxNotifier so that
|
||||
// it can properly notify its registered
|
||||
// clients.
|
||||
txHash := tx.TxHash()
|
||||
details := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &op,
|
||||
SpenderTxHash: &txHash,
|
||||
SpendingTx: tx,
|
||||
SpenderInputIndex: uint32(i),
|
||||
SpendingHeight: int32(height),
|
||||
}
|
||||
|
||||
return b.txNotifier.UpdateSpendDetails(
|
||||
op, details,
|
||||
)
|
||||
matches, inputIdx, err := spendRequest.MatchesTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
txHash := tx.TxHash()
|
||||
details := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &tx.TxIn[inputIdx].PreviousOutPoint,
|
||||
SpenderTxHash: &txHash,
|
||||
SpendingTx: tx,
|
||||
SpenderInputIndex: inputIdx,
|
||||
SpendingHeight: int32(height),
|
||||
}
|
||||
|
||||
return b.txNotifier.UpdateSpendDetails(
|
||||
historicalDispatchDetails.SpendRequest,
|
||||
details,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return ErrTransactionNotFound
|
||||
}
|
||||
|
||||
// RegisterConfirmationsNtfn registers a notification with BitcoindNotifier
|
||||
// which will be triggered once the txid reaches numConfs number of
|
||||
// confirmations.
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once the target
|
||||
// txid/output script has reached numConfs confirmations on-chain. When
|
||||
// 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 (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
_ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
pkScript []byte,
|
||||
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
|
||||
// Construct a notification request for the transaction and send it to
|
||||
// the main event loop.
|
||||
confID := atomic.AddUint64(&b.confClientCounter, 1)
|
||||
confRequest, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntfn := &chainntnfs.ConfNtfn{
|
||||
ConfID: atomic.AddUint64(&b.confClientCounter, 1),
|
||||
TxID: txid,
|
||||
ConfID: confID,
|
||||
ConfRequest: confRequest,
|
||||
NumConfirmations: numConfs,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs),
|
||||
HeightHint: heightHint,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs, func() {
|
||||
b.txNotifier.CancelConf(confRequest, confID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New confirmation subscription: "+
|
||||
"txid=%v, numconfs=%v", txid, numConfs)
|
||||
chainntnfs.Log.Infof("New confirmation subscription: %v, num_confs=%v",
|
||||
confRequest, numConfs)
|
||||
|
||||
// Register the conf notification with the TxNotifier. A non-nil value
|
||||
// for `dispatch` will be returned if we are required to perform a
|
||||
// manual scan for the confirmation. Otherwise the notifier will begin
|
||||
// watching at tip for the transaction to confirm.
|
||||
dispatch, err := b.txNotifier.RegisterConf(ntfn)
|
||||
dispatch, _, err := b.txNotifier.RegisterConf(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -832,7 +938,7 @@ func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||
case b.notificationRegistry <- dispatch:
|
||||
return ntfn.Event, nil
|
||||
case <-b.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -863,7 +969,9 @@ type epochCancel struct {
|
|||
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
|
||||
// caller to receive notifications, of each new block connected to the main
|
||||
// chain. Clients have the option of passing in their best known block, which
|
||||
// the notifier uses to check if they are behind on blocks and catch them up.
|
||||
// the notifier uses to check if they are behind on blocks and catch them up. If
|
||||
// they do not provide one, then a notification will be dispatched immediately
|
||||
// for the current tip of the chain upon a successful registration.
|
||||
func (b *BitcoindNotifier) RegisterBlockEpochNtfn(
|
||||
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package bitcoindnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -14,6 +15,20 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
||||
var (
|
||||
testScript = []byte{
|
||||
// OP_HASH160
|
||||
0xA9,
|
||||
// OP_DATA_20
|
||||
0x14,
|
||||
// <20-byte hash>
|
||||
0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c, 0xa5, 0x15,
|
||||
0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03, 0x06, 0xf6, 0x96, 0xcd,
|
||||
// OP_EQUAL
|
||||
0x87,
|
||||
}
|
||||
)
|
||||
|
||||
func initHintCache(t *testing.T) *chainntnfs.HeightHintCache {
|
||||
t.Helper()
|
||||
|
||||
|
@ -41,7 +56,10 @@ func setUpNotifier(t *testing.T, bitcoindConn *chain.BitcoindConn,
|
|||
|
||||
t.Helper()
|
||||
|
||||
notifier := New(bitcoindConn, spendHintCache, confirmHintCache)
|
||||
notifier := New(
|
||||
bitcoindConn, chainntnfs.NetParams, spendHintCache,
|
||||
confirmHintCache,
|
||||
)
|
||||
if err := notifier.Start(); err != nil {
|
||||
t.Fatalf("unable to start notifier: %v", err)
|
||||
}
|
||||
|
@ -104,8 +122,13 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
// A transaction unknown to the node should not be found within the
|
||||
// txindex even if it is enabled, so we should not proceed with any
|
||||
// fallback methods.
|
||||
var zeroHash chainhash.Hash
|
||||
_, txStatus, err := notifier.historicalConfDetails(&zeroHash, 0, 0)
|
||||
var unknownHash chainhash.Hash
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
|
||||
unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
_, txStatus, err := notifier.historicalConfDetails(unknownConfReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -120,16 +143,20 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
|
||||
// Now, we'll create a test transaction, confirm it, and attempt to
|
||||
// retrieve its confirmation details.
|
||||
txid, _, err := chainntnfs.GetTestTxidAndScript(miner)
|
||||
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create tx: %v", err)
|
||||
}
|
||||
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
|
||||
// The transaction should be found in the mempool at this point.
|
||||
_, txStatus, err = notifier.historicalConfDetails(txid, 0, 0)
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -151,7 +178,7 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
// the txindex includes the transaction just mined.
|
||||
syncNotifierWithMiner(t, notifier, miner)
|
||||
|
||||
_, txStatus, err = notifier.historicalConfDetails(txid, 0, 0)
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -186,10 +213,15 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
// Since the node has its txindex disabled, we fall back to scanning the
|
||||
// chain manually. A transaction unknown to the network should not be
|
||||
// found.
|
||||
var zeroHash chainhash.Hash
|
||||
var unknownHash chainhash.Hash
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
|
||||
unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
broadcastHeight := syncNotifierWithMiner(t, notifier, miner)
|
||||
_, txStatus, err := notifier.historicalConfDetails(
|
||||
&zeroHash, uint32(broadcastHeight), uint32(broadcastHeight),
|
||||
unknownConfReq, uint32(broadcastHeight), uint32(broadcastHeight),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
|
@ -210,8 +242,8 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
// one output, which we will manually spend. The backend node's
|
||||
// transaction index should also be disabled, which we've already
|
||||
// ensured above.
|
||||
output, pkScript := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
spendTx := chainntnfs.CreateSpendTx(t, output, pkScript)
|
||||
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
spendTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
|
||||
spendTxHash, err := miner.Node.SendRawTransaction(spendTx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast tx: %v", err)
|
||||
|
@ -225,9 +257,13 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
|
||||
// Ensure the notifier and miner are synced to the same height to ensure
|
||||
// we can find the transaction when manually scanning the chain.
|
||||
confReq, err := chainntnfs.NewConfRequest(&outpoint.Hash, output.PkScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
currentHeight := syncNotifierWithMiner(t, notifier, miner)
|
||||
_, txStatus, err = notifier.historicalConfDetails(
|
||||
&output.Hash, uint32(broadcastHeight), uint32(currentHeight),
|
||||
confReq, uint32(broadcastHeight), uint32(currentHeight),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
)
|
||||
|
@ -11,9 +12,9 @@ import (
|
|||
// createNewNotifier creates a new instance of the ChainNotifier interface
|
||||
// implemented by BitcoindNotifier.
|
||||
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
||||
if len(args) != 3 {
|
||||
if len(args) != 4 {
|
||||
return nil, fmt.Errorf("incorrect number of arguments to "+
|
||||
".New(...), expected 2, instead passed %v", len(args))
|
||||
".New(...), expected 4, instead passed %v", len(args))
|
||||
}
|
||||
|
||||
chainConn, ok := args[0].(*chain.BitcoindConn)
|
||||
|
@ -22,19 +23,25 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
|||
"is incorrect, expected a *chain.BitcoindConn")
|
||||
}
|
||||
|
||||
spendHintCache, ok := args[1].(chainntnfs.SpendHintCache)
|
||||
chainParams, ok := args[1].(*chaincfg.Params)
|
||||
if !ok {
|
||||
return nil, errors.New("second argument to bitcoindnotify.New " +
|
||||
"is incorrect, expected a *chaincfg.Params")
|
||||
}
|
||||
|
||||
spendHintCache, ok := args[2].(chainntnfs.SpendHintCache)
|
||||
if !ok {
|
||||
return nil, errors.New("third argument to bitcoindnotify.New " +
|
||||
"is incorrect, expected a chainntnfs.SpendHintCache")
|
||||
}
|
||||
|
||||
confirmHintCache, ok := args[2].(chainntnfs.ConfirmHintCache)
|
||||
confirmHintCache, ok := args[3].(chainntnfs.ConfirmHintCache)
|
||||
if !ok {
|
||||
return nil, errors.New("third argument to bitcoindnotify.New " +
|
||||
return nil, errors.New("fourth argument to bitcoindnotify.New " +
|
||||
"is incorrect, expected a chainntnfs.ConfirmHintCache")
|
||||
}
|
||||
|
||||
return New(chainConn, spendHintCache, confirmHintCache), nil
|
||||
return New(chainConn, chainParams, spendHintCache, confirmHintCache), nil
|
||||
}
|
||||
|
||||
// init registers a driver for the BtcdNotifier concrete implementation of the
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package btcdnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
@ -9,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
@ -23,13 +26,6 @@ const (
|
|||
notifierType = "btcd"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chainntnfs: system interrupt " +
|
||||
"while attempting to register for spend notification.")
|
||||
)
|
||||
|
||||
// chainUpdate encapsulates an update to the current main chain. This struct is
|
||||
// used as an element within an unbounded queue in order to avoid blocking the
|
||||
// main rpc dispatch rule.
|
||||
|
@ -64,7 +60,8 @@ type BtcdNotifier struct {
|
|||
started int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
chainConn *rpcclient.Client
|
||||
chainConn *rpcclient.Client
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
notificationCancels chan interface{}
|
||||
notificationRegistry chan interface{}
|
||||
|
@ -98,10 +95,13 @@ var _ chainntnfs.ChainNotifier = (*BtcdNotifier)(nil)
|
|||
// New returns a new BtcdNotifier instance. This function assumes the btcd node
|
||||
// detailed in the passed configuration is already running, and willing to
|
||||
// accept new websockets clients.
|
||||
func New(config *rpcclient.ConnConfig, spendHintCache chainntnfs.SpendHintCache,
|
||||
func New(config *rpcclient.ConnConfig, chainParams *chaincfg.Params,
|
||||
spendHintCache chainntnfs.SpendHintCache,
|
||||
confirmHintCache chainntnfs.ConfirmHintCache) (*BtcdNotifier, error) {
|
||||
|
||||
notifier := &BtcdNotifier{
|
||||
chainParams: chainParams,
|
||||
|
||||
notificationCancels: make(chan interface{}),
|
||||
notificationRegistry: make(chan interface{}),
|
||||
|
||||
|
@ -289,10 +289,10 @@ out:
|
|||
case registerMsg := <-b.notificationRegistry:
|
||||
switch msg := registerMsg.(type) {
|
||||
case *chainntnfs.HistoricalConfDispatch:
|
||||
// Look up whether the transaction is already
|
||||
// included in the active chain. We'll do this
|
||||
// in a goroutine to prevent blocking
|
||||
// potentially long rescans.
|
||||
// Look up whether the transaction/output script
|
||||
// has already confirmed in the active chain.
|
||||
// We'll do this in a goroutine to prevent
|
||||
// blocking potentially long rescans.
|
||||
//
|
||||
// TODO(wilmer): add retry logic if rescan fails?
|
||||
b.wg.Add(1)
|
||||
|
@ -300,7 +300,8 @@ out:
|
|||
defer b.wg.Done()
|
||||
|
||||
confDetails, _, err := b.historicalConfDetails(
|
||||
msg.TxID, msg.StartHeight, msg.EndHeight,
|
||||
msg.ConfRequest,
|
||||
msg.StartHeight, msg.EndHeight,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
|
@ -315,7 +316,7 @@ out:
|
|||
// cache at tip, since any pending
|
||||
// rescans have now completed.
|
||||
err = b.txNotifier.UpdateConfDetails(
|
||||
*msg.TxID, confDetails,
|
||||
msg.ConfRequest, confDetails,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
|
@ -324,23 +325,40 @@ out:
|
|||
|
||||
case *blockEpochRegistration:
|
||||
chainntnfs.Log.Infof("New block epoch subscription")
|
||||
b.blockEpochClients[msg.epochID] = msg
|
||||
if msg.bestBlock != nil {
|
||||
missedBlocks, err :=
|
||||
chainntnfs.GetClientMissedBlocks(
|
||||
b.chainConn, msg.bestBlock,
|
||||
b.bestBlock.Height, true,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
for _, block := range missedBlocks {
|
||||
b.notifyBlockEpochClient(msg,
|
||||
block.Height, block.Hash)
|
||||
}
|
||||
|
||||
b.blockEpochClients[msg.epochID] = msg
|
||||
|
||||
// If the client did not provide their best
|
||||
// known block, then we'll immediately dispatch
|
||||
// a notification for the current tip.
|
||||
if msg.bestBlock == nil {
|
||||
b.notifyBlockEpochClient(
|
||||
msg, b.bestBlock.Height,
|
||||
b.bestBlock.Hash,
|
||||
)
|
||||
|
||||
msg.errorChan <- nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we'll attempt to deliver the
|
||||
// backlog of notifications from their best
|
||||
// known block.
|
||||
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
|
||||
b.chainConn, msg.bestBlock,
|
||||
b.bestBlock.Height, true,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, block := range missedBlocks {
|
||||
b.notifyBlockEpochClient(
|
||||
msg, block.Height, block.Hash,
|
||||
)
|
||||
}
|
||||
|
||||
msg.errorChan <- nil
|
||||
}
|
||||
|
||||
|
@ -425,13 +443,13 @@ out:
|
|||
continue
|
||||
}
|
||||
|
||||
tx := newSpend.tx.MsgTx()
|
||||
err := b.txNotifier.ProcessRelevantSpendTx(
|
||||
tx, newSpend.details.Height,
|
||||
newSpend.tx, uint32(newSpend.details.Height),
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to process "+
|
||||
"transaction %v: %v", tx.TxHash(), err)
|
||||
"transaction %v: %v",
|
||||
newSpend.tx.Hash(), err)
|
||||
}
|
||||
|
||||
case <-b.quit:
|
||||
|
@ -441,15 +459,28 @@ out:
|
|||
b.wg.Done()
|
||||
}
|
||||
|
||||
// historicalConfDetails looks up whether a transaction is already included in a
|
||||
// block in the active chain and, if so, returns details about the confirmation.
|
||||
func (b *BtcdNotifier) historicalConfDetails(txid *chainhash.Hash,
|
||||
// historicalConfDetails looks up whether a confirmation request (txid/output
|
||||
// script) has already been included in a block in the active chain and, if so,
|
||||
// returns details about said block.
|
||||
func (b *BtcdNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
|
||||
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
|
||||
chainntnfs.TxConfStatus, error) {
|
||||
|
||||
// If a txid was not provided, then we should dispatch upon seeing the
|
||||
// script on-chain, so we'll short-circuit straight to scanning manually
|
||||
// as there doesn't exist a script index to query.
|
||||
if confRequest.TxID == chainntnfs.ZeroHash {
|
||||
return b.confDetailsManually(
|
||||
confRequest, startHeight, endHeight,
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise, we'll dispatch upon seeing a transaction on-chain with the
|
||||
// given hash.
|
||||
//
|
||||
// We'll first attempt to retrieve the transaction using the node's
|
||||
// txindex.
|
||||
txConf, txStatus, err := b.confDetailsFromTxIndex(txid)
|
||||
txConf, txStatus, err := b.confDetailsFromTxIndex(&confRequest.TxID)
|
||||
|
||||
// We'll then check the status of the transaction lookup returned to
|
||||
// determine whether we should proceed with any fallback methods.
|
||||
|
@ -458,9 +489,13 @@ func (b *BtcdNotifier) historicalConfDetails(txid *chainhash.Hash,
|
|||
// We failed querying the index for the transaction, fall back to
|
||||
// scanning manually.
|
||||
case err != nil:
|
||||
chainntnfs.Log.Debugf("Failed getting conf details from "+
|
||||
"index (%v), scanning manually", err)
|
||||
return b.confDetailsManually(txid, startHeight, endHeight)
|
||||
chainntnfs.Log.Debugf("Unable to determine confirmation of %v "+
|
||||
"through the backend's txindex (%v), scanning manually",
|
||||
confRequest.TxID, err)
|
||||
|
||||
return b.confDetailsManually(
|
||||
confRequest, startHeight, endHeight,
|
||||
)
|
||||
|
||||
// The transaction was found within the node's mempool.
|
||||
case txStatus == chainntnfs.TxFoundMempool:
|
||||
|
@ -491,7 +526,7 @@ func (b *BtcdNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
|
||||
// If the transaction has some or all of its confirmations required,
|
||||
// then we may be able to dispatch it immediately.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(txid)
|
||||
rawTxRes, err := b.chainConn.GetRawTransactionVerbose(txid)
|
||||
if err != nil {
|
||||
// If the transaction lookup was successful, but it wasn't found
|
||||
// within the index itself, then we can exit early. We'll also
|
||||
|
@ -512,20 +547,19 @@ func (b *BtcdNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
// Make sure we actually retrieved a transaction that is included in a
|
||||
// block. If not, the transaction must be unconfirmed (in the mempool),
|
||||
// and we'll return TxFoundMempool together with a nil TxConfirmation.
|
||||
if tx.BlockHash == "" {
|
||||
if rawTxRes.BlockHash == "" {
|
||||
return nil, chainntnfs.TxFoundMempool, nil
|
||||
}
|
||||
|
||||
// As we need to fully populate the returned TxConfirmation struct,
|
||||
// grab the block in which the transaction was confirmed so we can
|
||||
// locate its exact index within the block.
|
||||
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
|
||||
blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block hash %v for "+
|
||||
"historical dispatch: %v", tx.BlockHash, err)
|
||||
"historical dispatch: %v", rawTxRes.BlockHash, err)
|
||||
}
|
||||
|
||||
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
|
@ -535,36 +569,49 @@ func (b *BtcdNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
|||
|
||||
// If the block was obtained, locate the transaction's index within the
|
||||
// block so we can give the subscriber full confirmation details.
|
||||
targetTxidStr := txid.String()
|
||||
txidStr := txid.String()
|
||||
for txIndex, txHash := range block.Tx {
|
||||
if txHash == targetTxidStr {
|
||||
details := &chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}
|
||||
return details, chainntnfs.TxFoundIndex, nil
|
||||
if txHash != txidStr {
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the hex-encoded transaction to include it in the
|
||||
// confirmation details.
|
||||
rawTx, err := hex.DecodeString(rawTxRes.Hex)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
txHash, err)
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
|
||||
return nil, chainntnfs.TxFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
txHash, err)
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: &tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}, chainntnfs.TxFoundIndex, nil
|
||||
}
|
||||
|
||||
// We return an error because we should have found the transaction
|
||||
// within the block, but didn't.
|
||||
return nil, chainntnfs.TxNotFoundIndex,
|
||||
fmt.Errorf("unable to locate tx %v in block %v", txid,
|
||||
blockHash)
|
||||
return nil, chainntnfs.TxNotFoundIndex, fmt.Errorf("unable to locate "+
|
||||
"tx %v in block %v", txid, blockHash)
|
||||
}
|
||||
|
||||
// confDetailsManually looks up whether a transaction is already included in a
|
||||
// block in the active chain by scanning the chain's blocks, starting from the
|
||||
// earliest height the transaction could have been included in, to the current
|
||||
// height in the chain. If the transaction is found, its confirmation details
|
||||
// are returned. Otherwise, nil is returned.
|
||||
func (b *BtcdNotifier) confDetailsManually(txid *chainhash.Hash, startHeight,
|
||||
endHeight uint32) (*chainntnfs.TxConfirmation,
|
||||
// confDetailsManually looks up whether a transaction/output script has already
|
||||
// been included in a block in the active chain by scanning the chain's blocks
|
||||
// within the given range. If the transaction/output script is found, its
|
||||
// confirmation details are returned. Otherwise, nil is returned.
|
||||
func (b *BtcdNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
|
||||
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
|
||||
chainntnfs.TxConfStatus, error) {
|
||||
|
||||
targetTxidStr := txid.String()
|
||||
|
||||
// Begin scanning blocks at every height to determine where the
|
||||
// transaction was included in.
|
||||
for height := endHeight; height >= startHeight && height > 0; height-- {
|
||||
|
@ -573,7 +620,7 @@ func (b *BtcdNotifier) confDetailsManually(txid *chainhash.Hash, startHeight,
|
|||
select {
|
||||
case <-b.quit:
|
||||
return nil, chainntnfs.TxNotFoundManually,
|
||||
ErrChainNotifierShuttingDown
|
||||
chainntnfs.ErrChainNotifierShuttingDown
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -585,24 +632,27 @@ func (b *BtcdNotifier) confDetailsManually(txid *chainhash.Hash, startHeight,
|
|||
}
|
||||
|
||||
// TODO: fetch the neutrino filters instead.
|
||||
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
||||
block, err := b.chainConn.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return nil, chainntnfs.TxNotFoundManually,
|
||||
fmt.Errorf("unable to get block with hash "+
|
||||
"%v: %v", blockHash, err)
|
||||
}
|
||||
|
||||
for txIndex, txHash := range block.Tx {
|
||||
// If we're able to find the transaction in this block,
|
||||
// return its confirmation details.
|
||||
if txHash == targetTxidStr {
|
||||
details := &chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
}
|
||||
return details, chainntnfs.TxFoundManually, nil
|
||||
// For every transaction in the block, check which one matches
|
||||
// our request. If we find one that does, we can dispatch its
|
||||
// confirmation details.
|
||||
for txIndex, tx := range block.Transactions {
|
||||
if !confRequest.MatchesTx(tx) {
|
||||
continue
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
}, chainntnfs.TxFoundManually, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -681,32 +731,57 @@ func (b *BtcdNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistratio
|
|||
}
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint has been spent by a transaction on-chain. Once a spend of the target
|
||||
// outpoint has been detected, the details of the spending event will be sent
|
||||
// across the 'Spend' channel. The heightHint should represent the earliest
|
||||
// height in the chain where the transaction could have been spent in.
|
||||
// outpoint/output script has been spent by a transaction on-chain. When
|
||||
// intending to be notified of the spend of an output script, a nil outpoint
|
||||
// must be used. The heightHint should represent the earliest height in the
|
||||
// chain of the transaction that spent the outpoint/output script.
|
||||
//
|
||||
// Once a spend of has been detected, the details of the spending event will be
|
||||
// sent across the 'Spend' channel.
|
||||
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
||||
// First, we'll construct a spend notification request and hand it off
|
||||
// to the txNotifier.
|
||||
spendID := atomic.AddUint64(&b.spendClientCounter, 1)
|
||||
cancel := func() {
|
||||
b.txNotifier.CancelSpend(*outpoint, spendID)
|
||||
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
SpendID: spendID,
|
||||
OutPoint: *outpoint,
|
||||
PkScript: pkScript,
|
||||
Event: chainntnfs.NewSpendEvent(cancel),
|
||||
SpendID: spendID,
|
||||
SpendRequest: spendRequest,
|
||||
Event: chainntnfs.NewSpendEvent(func() {
|
||||
b.txNotifier.CancelSpend(spendRequest, spendID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
historicalDispatch, err := b.txNotifier.RegisterSpend(ntfn)
|
||||
historicalDispatch, _, err := b.txNotifier.RegisterSpend(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll then request the backend to notify us when it has detected the
|
||||
// outpoint/output script as spent.
|
||||
//
|
||||
// TODO(wilmer): use LoadFilter API instead.
|
||||
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
addr, err := spendRequest.PkScript.Address(b.chainParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs := []btcutil.Address{addr}
|
||||
if err := b.chainConn.NotifyReceived(addrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ops := []*wire.OutPoint{&spendRequest.OutPoint}
|
||||
if err := b.chainConn.NotifySpent(ops); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// for us to do.
|
||||
|
@ -714,24 +789,55 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// We'll then request the backend to notify us when it has detected the
|
||||
// outpoint as spent.
|
||||
ops := []*wire.OutPoint{outpoint}
|
||||
if err := b.chainConn.NotifySpent(ops); err != nil {
|
||||
return nil, err
|
||||
// Otherwise, we'll need to dispatch a historical rescan to determine if
|
||||
// the outpoint was already spent at a previous height.
|
||||
//
|
||||
// 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
|
||||
// make for scripts.
|
||||
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
startHash, err := b.chainConn.GetBlockHash(
|
||||
int64(historicalDispatch.StartHeight),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(wilmer): add retry logic if rescan fails?
|
||||
addr, err := spendRequest.PkScript.Address(b.chainParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs := []btcutil.Address{addr}
|
||||
asyncResult := b.chainConn.RescanAsync(startHash, addrs, nil)
|
||||
go func() {
|
||||
if rescanErr := asyncResult.Receive(); rescanErr != nil {
|
||||
chainntnfs.Log.Errorf("Rescan to determine "+
|
||||
"the spend details of %v failed: %v",
|
||||
spendRequest, rescanErr)
|
||||
}
|
||||
}()
|
||||
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// In addition to the check above, we'll also check the backend's UTXO
|
||||
// set to determine whether the outpoint has been spent. If it hasn't,
|
||||
// we can return to the caller as well.
|
||||
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
|
||||
// When dispatching spends of outpoints, there are a number of checks we
|
||||
// can make to start our rescan from a better height or completely avoid
|
||||
// it.
|
||||
//
|
||||
// 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
|
||||
// caller as well.
|
||||
txOut, err := b.chainConn.GetTxOut(
|
||||
&spendRequest.OutPoint.Hash, spendRequest.OutPoint.Index, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if txOut != nil {
|
||||
// We'll let the txNotifier know the outpoint is still unspent
|
||||
// in order to begin updating its spend hint.
|
||||
err := b.txNotifier.UpdateSpendDetails(*outpoint, nil)
|
||||
err := b.txNotifier.UpdateSpendDetails(spendRequest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -739,9 +845,9 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll determine when the output was spent by scanning the
|
||||
// chain. We'll begin by determining where to start our historical
|
||||
// rescan.
|
||||
// Since the outpoint was spent, as it no longer exists within the UTXO
|
||||
// set, we'll determine when it happened by scanning the chain. We'll
|
||||
// begin by fetching the block hash of our starting height.
|
||||
startHash, err := b.chainConn.GetBlockHash(
|
||||
int64(historicalDispatch.StartHeight),
|
||||
)
|
||||
|
@ -754,14 +860,14 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
// index (if enabled) to determine if we have a better rescan starting
|
||||
// height. We can do this as the GetRawTransaction call will return the
|
||||
// hash of the block it was included in within the chain.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(&spendRequest.OutPoint.Hash)
|
||||
if err != nil {
|
||||
// Avoid returning an error if the transaction was not found to
|
||||
// proceed with fallback methods.
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
||||
return nil, fmt.Errorf("unable to query for "+
|
||||
"txid %v: %v", outpoint.Hash, err)
|
||||
return nil, fmt.Errorf("unable to query for txid %v: %v",
|
||||
spendRequest.OutPoint.Hash, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,6 +903,9 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
}
|
||||
}
|
||||
|
||||
// Now that we've determined the best starting point for our rescan,
|
||||
// we can go ahead and dispatch it.
|
||||
//
|
||||
// In order to ensure that we don't block the caller on what may be a
|
||||
// long rescan, we'll launch a new goroutine to handle the async result
|
||||
// of the rescan. We purposefully prevent from adding this goroutine to
|
||||
|
@ -804,41 +913,58 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
// asyncResult channel not being exposed.
|
||||
//
|
||||
// TODO(wilmer): add retry logic if rescan fails?
|
||||
asyncResult := b.chainConn.RescanAsync(startHash, nil, ops)
|
||||
asyncResult := b.chainConn.RescanAsync(
|
||||
startHash, nil, []*wire.OutPoint{&spendRequest.OutPoint},
|
||||
)
|
||||
go func() {
|
||||
if rescanErr := asyncResult.Receive(); rescanErr != nil {
|
||||
chainntnfs.Log.Errorf("Rescan to determine the spend "+
|
||||
"details of %v failed: %v", outpoint, rescanErr)
|
||||
"details of %v failed: %v", spendRequest,
|
||||
rescanErr)
|
||||
}
|
||||
}()
|
||||
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// RegisterConfirmationsNtfn registers a notification with BtcdNotifier
|
||||
// which will be triggered once the txid reaches numConfs number of
|
||||
// confirmations.
|
||||
func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte,
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once the target
|
||||
// txid/output script has reached numConfs confirmations on-chain. When
|
||||
// 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 (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
pkScript []byte,
|
||||
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
|
||||
// Construct a notification request for the transaction and send it to
|
||||
// the main event loop.
|
||||
confID := atomic.AddUint64(&b.confClientCounter, 1)
|
||||
confRequest, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntfn := &chainntnfs.ConfNtfn{
|
||||
ConfID: atomic.AddUint64(&b.confClientCounter, 1),
|
||||
TxID: txid,
|
||||
ConfID: confID,
|
||||
ConfRequest: confRequest,
|
||||
NumConfirmations: numConfs,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs),
|
||||
HeightHint: heightHint,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs, func() {
|
||||
b.txNotifier.CancelConf(confRequest, confID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New confirmation subscription: "+
|
||||
"txid=%v, numconfs=%v", txid, numConfs)
|
||||
chainntnfs.Log.Infof("New confirmation subscription: %v, num_confs=%v ",
|
||||
confRequest, numConfs)
|
||||
|
||||
// Register the conf notification with the TxNotifier. A non-nil value
|
||||
// for `dispatch` will be returned if we are required to perform a
|
||||
// manual scan for the confirmation. Otherwise the notifier will begin
|
||||
// watching at tip for the transaction to confirm.
|
||||
dispatch, err := b.txNotifier.RegisterConf(ntfn)
|
||||
dispatch, _, err := b.txNotifier.RegisterConf(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -851,7 +977,7 @@ func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte,
|
|||
case b.notificationRegistry <- dispatch:
|
||||
return ntfn.Event, nil
|
||||
case <-b.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -882,7 +1008,9 @@ type epochCancel struct {
|
|||
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
|
||||
// caller to receive notifications, of each new block connected to the main
|
||||
// chain. Clients have the option of passing in their best known block, which
|
||||
// the notifier uses to check if they are behind on blocks and catch them up.
|
||||
// the notifier uses to check if they are behind on blocks and catch them up. If
|
||||
// they do not provide one, then a notification will be dispatched immediately
|
||||
// for the current tip of the chain upon a successful registration.
|
||||
func (b *BtcdNotifier) RegisterBlockEpochNtfn(
|
||||
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
|
@ -894,6 +1022,7 @@ func (b *BtcdNotifier) RegisterBlockEpochNtfn(
|
|||
bestBlock: bestBlock,
|
||||
errorChan: make(chan error, 1),
|
||||
}
|
||||
|
||||
reg.epochQueue.Start()
|
||||
|
||||
// Before we send the request to the main goroutine, we'll launch a new
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package btcdnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
|
@ -12,6 +13,20 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
||||
var (
|
||||
testScript = []byte{
|
||||
// OP_HASH160
|
||||
0xA9,
|
||||
// OP_DATA_20
|
||||
0x14,
|
||||
// <20-byte hash>
|
||||
0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c, 0xa5, 0x15,
|
||||
0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03, 0x06, 0xf6, 0x96, 0xcd,
|
||||
// OP_EQUAL
|
||||
0x87,
|
||||
}
|
||||
)
|
||||
|
||||
func initHintCache(t *testing.T) *chainntnfs.HeightHintCache {
|
||||
t.Helper()
|
||||
|
||||
|
@ -37,7 +52,7 @@ func setUpNotifier(t *testing.T, h *rpctest.Harness) *BtcdNotifier {
|
|||
hintCache := initHintCache(t)
|
||||
|
||||
rpcCfg := h.RPCConfig()
|
||||
notifier, err := New(&rpcCfg, hintCache, hintCache)
|
||||
notifier, err := New(&rpcCfg, chainntnfs.NetParams, hintCache, hintCache)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create notifier: %v", err)
|
||||
}
|
||||
|
@ -64,8 +79,13 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
// A transaction unknown to the node should not be found within the
|
||||
// txindex even if it is enabled, so we should not proceed with any
|
||||
// fallback methods.
|
||||
var zeroHash chainhash.Hash
|
||||
_, txStatus, err := notifier.historicalConfDetails(&zeroHash, 0, 0)
|
||||
var unknownHash chainhash.Hash
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
|
||||
unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
_, txStatus, err := notifier.historicalConfDetails(unknownConfReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -80,16 +100,20 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
|
||||
// Now, we'll create a test transaction and attempt to retrieve its
|
||||
// confirmation details.
|
||||
txid, _, err := chainntnfs.GetTestTxidAndScript(harness)
|
||||
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(harness)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create tx: %v", err)
|
||||
}
|
||||
if err := chainntnfs.WaitForMempoolTx(harness, txid); err != nil {
|
||||
t.Fatalf("unable to find tx in the mempool: %v", err)
|
||||
}
|
||||
confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
|
||||
// The transaction should be found in the mempool at this point.
|
||||
_, txStatus, err = notifier.historicalConfDetails(txid, 0, 0)
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -109,7 +133,7 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) {
|
|||
t.Fatalf("unable to generate block: %v", err)
|
||||
}
|
||||
|
||||
_, txStatus, err = notifier.historicalConfDetails(txid, 0, 0)
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -139,8 +163,13 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
// Since the node has its txindex disabled, we fall back to scanning the
|
||||
// chain manually. A transaction unknown to the network should not be
|
||||
// found.
|
||||
var zeroHash chainhash.Hash
|
||||
_, txStatus, err := notifier.historicalConfDetails(&zeroHash, 0, 0)
|
||||
var unknownHash chainhash.Hash
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
|
||||
unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
_, txStatus, err := notifier.historicalConfDetails(unknownConfReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -161,15 +190,19 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
|
||||
txid, _, err := chainntnfs.GetTestTxidAndScript(harness)
|
||||
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(harness)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create tx: %v", err)
|
||||
}
|
||||
if err := chainntnfs.WaitForMempoolTx(harness, txid); err != nil {
|
||||
t.Fatalf("unable to find tx in the mempool: %v", err)
|
||||
}
|
||||
confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create conf request: %v", err)
|
||||
}
|
||||
|
||||
_, txStatus, err = notifier.historicalConfDetails(txid, 0, 0)
|
||||
_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
}
|
||||
|
@ -188,7 +221,7 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
|
|||
}
|
||||
|
||||
_, txStatus, err = notifier.historicalConfDetails(
|
||||
txid, uint32(currentHeight), uint32(currentHeight)+1,
|
||||
confReq, uint32(currentHeight), uint32(currentHeight)+1,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve historical conf details: %v", err)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
)
|
||||
|
@ -11,30 +12,36 @@ import (
|
|||
// createNewNotifier creates a new instance of the ChainNotifier interface
|
||||
// implemented by BtcdNotifier.
|
||||
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
||||
if len(args) != 3 {
|
||||
if len(args) != 4 {
|
||||
return nil, fmt.Errorf("incorrect number of arguments to "+
|
||||
".New(...), expected 2, instead passed %v", len(args))
|
||||
".New(...), expected 4, instead passed %v", len(args))
|
||||
}
|
||||
|
||||
config, ok := args[0].(*rpcclient.ConnConfig)
|
||||
if !ok {
|
||||
return nil, errors.New("first argument to btcdnotifier.New " +
|
||||
return nil, errors.New("first argument to btcdnotify.New " +
|
||||
"is incorrect, expected a *rpcclient.ConnConfig")
|
||||
}
|
||||
|
||||
spendHintCache, ok := args[1].(chainntnfs.SpendHintCache)
|
||||
chainParams, ok := args[1].(*chaincfg.Params)
|
||||
if !ok {
|
||||
return nil, errors.New("second argument to btcdnotifier.New " +
|
||||
return nil, errors.New("second argument to btcdnotify.New " +
|
||||
"is incorrect, expected a *chaincfg.Params")
|
||||
}
|
||||
|
||||
spendHintCache, ok := args[2].(chainntnfs.SpendHintCache)
|
||||
if !ok {
|
||||
return nil, errors.New("third argument to btcdnotify.New " +
|
||||
"is incorrect, expected a chainntnfs.SpendHintCache")
|
||||
}
|
||||
|
||||
confirmHintCache, ok := args[2].(chainntnfs.ConfirmHintCache)
|
||||
confirmHintCache, ok := args[3].(chainntnfs.ConfirmHintCache)
|
||||
if !ok {
|
||||
return nil, errors.New("third argument to btcdnotifier.New " +
|
||||
return nil, errors.New("fourth argument to btcdnotify.New " +
|
||||
"is incorrect, expected a chainntnfs.ConfirmHintCache")
|
||||
}
|
||||
|
||||
return New(config, spendHintCache, confirmHintCache)
|
||||
return New(config, chainParams, spendHintCache, confirmHintCache)
|
||||
}
|
||||
|
||||
// init registers a driver for the BtcdNotifier concrete implementation of the
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
bolt "github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
@ -51,16 +49,16 @@ var (
|
|||
// which an outpoint could have been spent within.
|
||||
type SpendHintCache interface {
|
||||
// CommitSpendHint commits a spend hint for the outpoints to the cache.
|
||||
CommitSpendHint(height uint32, ops ...wire.OutPoint) error
|
||||
CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
|
||||
|
||||
// QuerySpendHint returns the latest spend hint for an outpoint.
|
||||
// ErrSpendHintNotFound is returned if a spend hint does not exist
|
||||
// within the cache for the outpoint.
|
||||
QuerySpendHint(op wire.OutPoint) (uint32, error)
|
||||
QuerySpendHint(spendRequest SpendRequest) (uint32, error)
|
||||
|
||||
// PurgeSpendHint removes the spend hint for the outpoints from the
|
||||
// cache.
|
||||
PurgeSpendHint(ops ...wire.OutPoint) error
|
||||
PurgeSpendHint(spendRequests ...SpendRequest) error
|
||||
}
|
||||
|
||||
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
|
||||
|
@ -69,16 +67,16 @@ type SpendHintCache interface {
|
|||
type ConfirmHintCache interface {
|
||||
// CommitConfirmHint commits a confirm hint for the transactions to the
|
||||
// cache.
|
||||
CommitConfirmHint(height uint32, txids ...chainhash.Hash) error
|
||||
CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
|
||||
|
||||
// QueryConfirmHint returns the latest confirm hint for a transaction
|
||||
// hash. ErrConfirmHintNotFound is returned if a confirm hint does not
|
||||
// exist within the cache for the transaction hash.
|
||||
QueryConfirmHint(txid chainhash.Hash) (uint32, error)
|
||||
QueryConfirmHint(confRequest ConfRequest) (uint32, error)
|
||||
|
||||
// PurgeConfirmHint removes the confirm hint for the transactions from
|
||||
// the cache.
|
||||
PurgeConfirmHint(txids ...chainhash.Hash) error
|
||||
PurgeConfirmHint(confRequests ...ConfRequest) error
|
||||
}
|
||||
|
||||
// HeightHintCache is an implementation of the SpendHintCache and
|
||||
|
@ -118,12 +116,15 @@ func (c *HeightHintCache) initBuckets() error {
|
|||
}
|
||||
|
||||
// CommitSpendHint commits a spend hint for the outpoints to the cache.
|
||||
func (c *HeightHintCache) CommitSpendHint(height uint32, ops ...wire.OutPoint) error {
|
||||
if len(ops) == 0 {
|
||||
func (c *HeightHintCache) CommitSpendHint(height uint32,
|
||||
spendRequests ...SpendRequest) error {
|
||||
|
||||
if len(spendRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Updating spend hint to height %d for %v", height, ops)
|
||||
Log.Tracef("Updating spend hint to height %d for %v", height,
|
||||
spendRequests)
|
||||
|
||||
return c.db.Batch(func(tx *bolt.Tx) error {
|
||||
spendHints := tx.Bucket(spendHintBucket)
|
||||
|
@ -136,14 +137,12 @@ func (c *HeightHintCache) CommitSpendHint(height uint32, ops ...wire.OutPoint) e
|
|||
return err
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
var outpoint bytes.Buffer
|
||||
err := channeldb.WriteElement(&outpoint, op)
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = spendHints.Put(outpoint.Bytes(), hint.Bytes())
|
||||
err = spendHints.Put(spendHintKey, hint.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -156,7 +155,7 @@ func (c *HeightHintCache) CommitSpendHint(height uint32, ops ...wire.OutPoint) e
|
|||
// QuerySpendHint returns the latest spend hint for an outpoint.
|
||||
// ErrSpendHintNotFound is returned if a spend hint does not exist within the
|
||||
// cache for the outpoint.
|
||||
func (c *HeightHintCache) QuerySpendHint(op wire.OutPoint) (uint32, error) {
|
||||
func (c *HeightHintCache) QuerySpendHint(spendRequest SpendRequest) (uint32, error) {
|
||||
var hint uint32
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
spendHints := tx.Bucket(spendHintBucket)
|
||||
|
@ -164,12 +163,11 @@ func (c *HeightHintCache) QuerySpendHint(op wire.OutPoint) (uint32, error) {
|
|||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var outpoint bytes.Buffer
|
||||
if err := channeldb.WriteElement(&outpoint, op); err != nil {
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spendHint := spendHints.Get(outpoint.Bytes())
|
||||
spendHint := spendHints.Get(spendHintKey)
|
||||
if spendHint == nil {
|
||||
return ErrSpendHintNotFound
|
||||
}
|
||||
|
@ -184,12 +182,12 @@ func (c *HeightHintCache) QuerySpendHint(op wire.OutPoint) (uint32, error) {
|
|||
}
|
||||
|
||||
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
|
||||
func (c *HeightHintCache) PurgeSpendHint(ops ...wire.OutPoint) error {
|
||||
if len(ops) == 0 {
|
||||
func (c *HeightHintCache) PurgeSpendHint(spendRequests ...SpendRequest) error {
|
||||
if len(spendRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Removing spend hints for %v", ops)
|
||||
Log.Tracef("Removing spend hints for %v", spendRequests)
|
||||
|
||||
return c.db.Batch(func(tx *bolt.Tx) error {
|
||||
spendHints := tx.Bucket(spendHintBucket)
|
||||
|
@ -197,15 +195,12 @@ func (c *HeightHintCache) PurgeSpendHint(ops ...wire.OutPoint) error {
|
|||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
var outpoint bytes.Buffer
|
||||
err := channeldb.WriteElement(&outpoint, op)
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = spendHints.Delete(outpoint.Bytes())
|
||||
if err != nil {
|
||||
if err := spendHints.Delete(spendHintKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -215,12 +210,15 @@ func (c *HeightHintCache) PurgeSpendHint(ops ...wire.OutPoint) error {
|
|||
}
|
||||
|
||||
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
|
||||
func (c *HeightHintCache) CommitConfirmHint(height uint32, txids ...chainhash.Hash) error {
|
||||
if len(txids) == 0 {
|
||||
func (c *HeightHintCache) CommitConfirmHint(height uint32,
|
||||
confRequests ...ConfRequest) error {
|
||||
|
||||
if len(confRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Updating confirm hints to height %d for %v", height, txids)
|
||||
Log.Tracef("Updating confirm hints to height %d for %v", height,
|
||||
confRequests)
|
||||
|
||||
return c.db.Batch(func(tx *bolt.Tx) error {
|
||||
confirmHints := tx.Bucket(confirmHintBucket)
|
||||
|
@ -233,14 +231,12 @@ func (c *HeightHintCache) CommitConfirmHint(height uint32, txids ...chainhash.Ha
|
|||
return err
|
||||
}
|
||||
|
||||
for _, txid := range txids {
|
||||
var txHash bytes.Buffer
|
||||
err := channeldb.WriteElement(&txHash, txid)
|
||||
for _, confRequest := range confRequests {
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = confirmHints.Put(txHash.Bytes(), hint.Bytes())
|
||||
err = confirmHints.Put(confHintKey, hint.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -253,7 +249,7 @@ func (c *HeightHintCache) CommitConfirmHint(height uint32, txids ...chainhash.Ha
|
|||
// QueryConfirmHint returns the latest confirm hint for a transaction hash.
|
||||
// ErrConfirmHintNotFound is returned if a confirm hint does not exist within
|
||||
// the cache for the transaction hash.
|
||||
func (c *HeightHintCache) QueryConfirmHint(txid chainhash.Hash) (uint32, error) {
|
||||
func (c *HeightHintCache) QueryConfirmHint(confRequest ConfRequest) (uint32, error) {
|
||||
var hint uint32
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
confirmHints := tx.Bucket(confirmHintBucket)
|
||||
|
@ -261,12 +257,11 @@ func (c *HeightHintCache) QueryConfirmHint(txid chainhash.Hash) (uint32, error)
|
|||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var txHash bytes.Buffer
|
||||
if err := channeldb.WriteElement(&txHash, txid); err != nil {
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
confirmHint := confirmHints.Get(txHash.Bytes())
|
||||
confirmHint := confirmHints.Get(confHintKey)
|
||||
if confirmHint == nil {
|
||||
return ErrConfirmHintNotFound
|
||||
}
|
||||
|
@ -282,12 +277,12 @@ func (c *HeightHintCache) QueryConfirmHint(txid chainhash.Hash) (uint32, error)
|
|||
|
||||
// PurgeConfirmHint removes the confirm hint for the transactions from the
|
||||
// cache.
|
||||
func (c *HeightHintCache) PurgeConfirmHint(txids ...chainhash.Hash) error {
|
||||
if len(txids) == 0 {
|
||||
func (c *HeightHintCache) PurgeConfirmHint(confRequests ...ConfRequest) error {
|
||||
if len(confRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Removing confirm hints for %v", txids)
|
||||
Log.Tracef("Removing confirm hints for %v", confRequests)
|
||||
|
||||
return c.db.Batch(func(tx *bolt.Tx) error {
|
||||
confirmHints := tx.Bucket(confirmHintBucket)
|
||||
|
@ -295,15 +290,12 @@ func (c *HeightHintCache) PurgeConfirmHint(txids ...chainhash.Hash) error {
|
|||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, txid := range txids {
|
||||
var txHash bytes.Buffer
|
||||
err := channeldb.WriteElement(&txHash, txid)
|
||||
for _, confRequest := range confRequests {
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = confirmHints.Delete(txHash.Bytes())
|
||||
if err != nil {
|
||||
if err := confirmHints.Delete(confHintKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ func TestHeightHintCacheConfirms(t *testing.T) {
|
|||
// Querying for a transaction hash not found within the cache should
|
||||
// return an error indication so.
|
||||
var unknownHash chainhash.Hash
|
||||
_, err := hintCache.QueryConfirmHint(unknownHash)
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x01}, 32))
|
||||
unknownConfRequest := ConfRequest{TxID: unknownHash}
|
||||
_, err := hintCache.QueryConfirmHint(unknownConfRequest)
|
||||
if err != ErrConfirmHintNotFound {
|
||||
t.Fatalf("expected ErrConfirmHintNotFound, got: %v", err)
|
||||
}
|
||||
|
@ -48,23 +50,24 @@ func TestHeightHintCacheConfirms(t *testing.T) {
|
|||
// cache with the same confirm hint.
|
||||
const height = 100
|
||||
const numHashes = 5
|
||||
txHashes := make([]chainhash.Hash, numHashes)
|
||||
confRequests := make([]ConfRequest, numHashes)
|
||||
for i := 0; i < numHashes; i++ {
|
||||
var txHash chainhash.Hash
|
||||
copy(txHash[:], bytes.Repeat([]byte{byte(i)}, 32))
|
||||
txHashes[i] = txHash
|
||||
copy(txHash[:], bytes.Repeat([]byte{byte(i + 1)}, 32))
|
||||
confRequests[i] = ConfRequest{TxID: txHash}
|
||||
}
|
||||
|
||||
if err := hintCache.CommitConfirmHint(height, txHashes...); err != nil {
|
||||
err = hintCache.CommitConfirmHint(height, confRequests...)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add entries to cache: %v", err)
|
||||
}
|
||||
|
||||
// With the hashes committed, we'll now query the cache to ensure that
|
||||
// we're able to properly retrieve the confirm hints.
|
||||
for _, txHash := range txHashes {
|
||||
confirmHint, err := hintCache.QueryConfirmHint(txHash)
|
||||
for _, confRequest := range confRequests {
|
||||
confirmHint, err := hintCache.QueryConfirmHint(confRequest)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for hint: %v", err)
|
||||
t.Fatalf("unable to query for hint of %v: %v", confRequest, err)
|
||||
}
|
||||
if confirmHint != height {
|
||||
t.Fatalf("expected confirm hint %d, got %d", height,
|
||||
|
@ -74,14 +77,14 @@ func TestHeightHintCacheConfirms(t *testing.T) {
|
|||
|
||||
// We'll also attempt to purge all of them in a single database
|
||||
// transaction.
|
||||
if err := hintCache.PurgeConfirmHint(txHashes...); err != nil {
|
||||
if err := hintCache.PurgeConfirmHint(confRequests...); err != nil {
|
||||
t.Fatalf("unable to remove confirm hints: %v", err)
|
||||
}
|
||||
|
||||
// Finally, we'll attempt to query for each hash. We should expect not
|
||||
// to find a hint for any of them.
|
||||
for _, txHash := range txHashes {
|
||||
_, err := hintCache.QueryConfirmHint(txHash)
|
||||
for _, confRequest := range confRequests {
|
||||
_, err := hintCache.QueryConfirmHint(confRequest)
|
||||
if err != ErrConfirmHintNotFound {
|
||||
t.Fatalf("expected ErrConfirmHintNotFound, got :%v", err)
|
||||
}
|
||||
|
@ -97,8 +100,9 @@ func TestHeightHintCacheSpends(t *testing.T) {
|
|||
|
||||
// Querying for an outpoint not found within the cache should return an
|
||||
// error indication so.
|
||||
var unknownOutPoint wire.OutPoint
|
||||
_, err := hintCache.QuerySpendHint(unknownOutPoint)
|
||||
unknownOutPoint := wire.OutPoint{Index: 1}
|
||||
unknownSpendRequest := SpendRequest{OutPoint: unknownOutPoint}
|
||||
_, err := hintCache.QuerySpendHint(unknownSpendRequest)
|
||||
if err != ErrSpendHintNotFound {
|
||||
t.Fatalf("expected ErrSpendHintNotFound, got: %v", err)
|
||||
}
|
||||
|
@ -107,21 +111,22 @@ func TestHeightHintCacheSpends(t *testing.T) {
|
|||
// the same spend hint.
|
||||
const height = 100
|
||||
const numOutpoints = 5
|
||||
var txHash chainhash.Hash
|
||||
copy(txHash[:], bytes.Repeat([]byte{0xFF}, 32))
|
||||
outpoints := make([]wire.OutPoint, numOutpoints)
|
||||
spendRequests := make([]SpendRequest, numOutpoints)
|
||||
for i := uint32(0); i < numOutpoints; i++ {
|
||||
outpoints[i] = wire.OutPoint{Hash: txHash, Index: i}
|
||||
spendRequests[i] = SpendRequest{
|
||||
OutPoint: wire.OutPoint{Index: i + 1},
|
||||
}
|
||||
}
|
||||
|
||||
if err := hintCache.CommitSpendHint(height, outpoints...); err != nil {
|
||||
t.Fatalf("unable to add entry to cache: %v", err)
|
||||
err = hintCache.CommitSpendHint(height, spendRequests...)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add entries to cache: %v", err)
|
||||
}
|
||||
|
||||
// With the outpoints committed, we'll now query the cache to ensure
|
||||
// that we're able to properly retrieve the confirm hints.
|
||||
for _, op := range outpoints {
|
||||
spendHint, err := hintCache.QuerySpendHint(op)
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHint, err := hintCache.QuerySpendHint(spendRequest)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for hint: %v", err)
|
||||
}
|
||||
|
@ -133,14 +138,14 @@ func TestHeightHintCacheSpends(t *testing.T) {
|
|||
|
||||
// We'll also attempt to purge all of them in a single database
|
||||
// transaction.
|
||||
if err := hintCache.PurgeSpendHint(outpoints...); err != nil {
|
||||
if err := hintCache.PurgeSpendHint(spendRequests...); err != nil {
|
||||
t.Fatalf("unable to remove spend hint: %v", err)
|
||||
}
|
||||
|
||||
// Finally, we'll attempt to query for each outpoint. We should expect
|
||||
// not to find a hint for any of them.
|
||||
for _, op := range outpoints {
|
||||
_, err = hintCache.QuerySpendHint(op)
|
||||
for _, spendRequest := range spendRequests {
|
||||
_, err = hintCache.QuerySpendHint(spendRequest)
|
||||
if err != ErrSpendHintNotFound {
|
||||
t.Fatalf("expected ErrSpendHintNotFound, got: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package chainntnfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
|
@ -9,6 +10,12 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down")
|
||||
)
|
||||
|
||||
// TxConfStatus denotes the status of a transaction's lookup.
|
||||
type TxConfStatus uint8
|
||||
|
||||
|
@ -65,36 +72,44 @@ func (t TxConfStatus) String() string {
|
|||
//
|
||||
// Concrete implementations of ChainNotifier should be able to support multiple
|
||||
// concurrent client requests, as well as multiple concurrent notification events.
|
||||
// TODO(roasbeef): all events should have a Cancel() method to free up the
|
||||
// resource
|
||||
type ChainNotifier interface {
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once
|
||||
// txid reaches numConfs confirmations. We also pass in the pkScript as
|
||||
// the default light client instead needs to match on scripts created
|
||||
// in the block. The returned ConfirmationEvent should properly notify
|
||||
// the client once the specified number of confirmations has been
|
||||
// reached for the txid, as well as if the original tx gets re-org'd
|
||||
// out of the mainchain. The heightHint parameter is provided as a
|
||||
// convenience to light clients. The heightHint denotes the earliest
|
||||
// height in the blockchain in which the target txid _could_ have been
|
||||
// included in the chain. This can be used to bound the search space
|
||||
// when checking to see if a notification can immediately be dispatched
|
||||
// due to historical data.
|
||||
// the default light client instead needs to match on scripts created in
|
||||
// the block. If a nil txid is passed in, then not only should we match
|
||||
// on the script, but we should also dispatch once the transaction
|
||||
// containing the script reaches numConfs confirmations. This can be
|
||||
// useful in instances where we only know the script in advance, but not
|
||||
// the transaction containing it.
|
||||
//
|
||||
// The returned ConfirmationEvent should properly notify the client once
|
||||
// the specified number of confirmations has been reached for the txid,
|
||||
// as well as if the original tx gets re-org'd out of the mainchain. The
|
||||
// heightHint parameter is provided as a convenience to light clients.
|
||||
// It heightHint denotes the earliest height in the blockchain in which
|
||||
// the target txid _could_ have been included in the chain. This can be
|
||||
// used to bound the search space when checking to see if a notification
|
||||
// can immediately be dispatched due to historical data.
|
||||
//
|
||||
// NOTE: Dispatching notifications to multiple clients subscribed to
|
||||
// the same (txid, numConfs) tuple MUST be supported.
|
||||
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte, numConfs,
|
||||
heightHint uint32) (*ConfirmationEvent, error)
|
||||
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
|
||||
numConfs, heightHint uint32) (*ConfirmationEvent, error)
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint is successfully spent within a transaction. The script that
|
||||
// the outpoint creates must also be specified. This allows this
|
||||
// interface to be implemented by BIP 158-like filtering. The returned
|
||||
// SpendEvent will receive a send on the 'Spend' transaction once a
|
||||
// transaction spending the input is detected on the blockchain. The
|
||||
// heightHint parameter is provided as a convenience to light clients.
|
||||
// The heightHint denotes the earliest height in the blockchain in
|
||||
// which the target output could have been created.
|
||||
// interface to be implemented by BIP 158-like filtering. If a nil
|
||||
// outpoint is passed in, then not only should we match on the script,
|
||||
// but we should also dispatch once a transaction spends the output
|
||||
// containing said script. This can be useful in instances where we only
|
||||
// know the script in advance, but not the outpoint itself.
|
||||
//
|
||||
// The returned SpendEvent will receive a send on the 'Spend'
|
||||
// transaction once a transaction spending the input is detected on the
|
||||
// blockchain. The heightHint parameter is provided as a convenience to
|
||||
// light clients. It denotes the earliest height in the blockchain in
|
||||
// which the target output could have been spent.
|
||||
//
|
||||
// NOTE: The notification should only be triggered when the spending
|
||||
// transaction receives a single confirmation.
|
||||
|
@ -112,7 +127,9 @@ type ChainNotifier interface {
|
|||
// Clients have the option of passing in their best known block.
|
||||
// If they specify a block, the ChainNotifier checks whether the client
|
||||
// is behind on blocks. If they are, the ChainNotifier sends a backlog
|
||||
// of block notifications for the missed blocks.
|
||||
// of block notifications for the missed blocks. If they do not provide
|
||||
// one, then a notification will be dispatched immediately for the
|
||||
// current tip of the chain upon a successful registration.
|
||||
RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
|
||||
|
||||
// Start the ChainNotifier. Once started, the implementation should be
|
||||
|
@ -140,6 +157,9 @@ type TxConfirmation struct {
|
|||
// TxIndex is the index within the block of the ultimate confirmed
|
||||
// transaction.
|
||||
TxIndex uint32
|
||||
|
||||
// Tx is the transaction for which the notification was requested for.
|
||||
Tx *wire.MsgTx
|
||||
}
|
||||
|
||||
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
|
||||
|
@ -155,6 +175,9 @@ type TxConfirmation struct {
|
|||
// If the event that the original transaction becomes re-org'd out of the main
|
||||
// chain, the 'NegativeConf' will be sent upon with a value representing the
|
||||
// depth of the re-org.
|
||||
//
|
||||
// NOTE: If the caller wishes to cancel their registered spend notification,
|
||||
// the Cancel closure MUST be called.
|
||||
type ConfirmationEvent struct {
|
||||
// Confirmed is a channel that will be sent upon once the transaction
|
||||
// has been fully confirmed. The struct sent will contain all the
|
||||
|
@ -171,26 +194,34 @@ type ConfirmationEvent struct {
|
|||
// confirmations.
|
||||
Updates chan uint32
|
||||
|
||||
// TODO(roasbeef): all goroutines on ln channel updates should also
|
||||
// have a struct chan that's closed if funding gets re-org out. Need
|
||||
// to sync, to request another confirmation event ntfn, then re-open
|
||||
// channel after confs.
|
||||
|
||||
// NegativeConf is a channel that will be sent upon if the transaction
|
||||
// confirms, but is later reorged out of the chain. The integer sent
|
||||
// through the channel represents the reorg depth.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
NegativeConf chan int32
|
||||
|
||||
// Done is a channel that gets sent upon once the confirmation request
|
||||
// is no longer under the risk of being reorged out of the chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Done chan struct{}
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to prematurely abandon their registered confirmation
|
||||
// notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
|
||||
// channels.
|
||||
func NewConfirmationEvent(numConfs uint32) *ConfirmationEvent {
|
||||
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
|
||||
return &ConfirmationEvent{
|
||||
Confirmed: make(chan *TxConfirmation, 1),
|
||||
Updates: make(chan uint32, numConfs),
|
||||
NegativeConf: make(chan int32, 1),
|
||||
Done: make(chan struct{}, 1),
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,8 +258,14 @@ type SpendEvent struct {
|
|||
// NOTE: This channel must be buffered.
|
||||
Reorg chan struct{}
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the
|
||||
// case that they wish to prematurely abandon their registered spend
|
||||
// Done is a channel that gets sent upon once the confirmation request
|
||||
// is no longer under the risk of being reorged out of the chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Done chan struct{}
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to prematurely abandon their registered spend
|
||||
// notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
@ -238,6 +275,7 @@ func NewSpendEvent(cancel func()) *SpendEvent {
|
|||
return &SpendEvent{
|
||||
Spend: make(chan *SpendDetail, 1),
|
||||
Reorg: make(chan struct{}, 1),
|
||||
Done: make(chan struct{}, 1),
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
@ -267,8 +305,8 @@ type BlockEpochEvent struct {
|
|||
// NOTE: This channel must be buffered.
|
||||
Epochs <-chan *BlockEpoch
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the
|
||||
// case that they wish to abandon their registered spend notification.
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to abandon their registered block epochs notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
|
|
|
@ -16,29 +16,18 @@ import (
|
|||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
_ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to auto-register the boltdb walletdb implementation.
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
|
||||
// Required to auto-register the bitcoind backed ChainNotifier
|
||||
// implementation.
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
|
||||
|
||||
// Required to auto-register the btcd backed ChainNotifier
|
||||
// implementation.
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
|
||||
// Required to auto-register the neutrino backed ChainNotifier
|
||||
// implementation.
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
|
||||
// Required to register the boltdb walletdb implementation.
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
||||
func testSingleConfirmationNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test the case of being notified once a txid reaches
|
||||
// a *single* confirmation.
|
||||
|
@ -62,9 +51,16 @@ func testSingleConfirmationNotification(miner *rpctest.Harness,
|
|||
// Now that we have a txid, register a confirmation notification with
|
||||
// the chainntfn source.
|
||||
numConfs := uint32(1)
|
||||
confIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var confIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -106,7 +102,7 @@ func testSingleConfirmationNotification(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
func testMultiConfirmationNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test the case of being notified once a txid reaches
|
||||
// N confirmations, where N > 1.
|
||||
|
@ -127,9 +123,16 @@ func testMultiConfirmationNotification(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
numConfs := uint32(6)
|
||||
confIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var confIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -152,7 +155,7 @@ func testMultiConfirmationNotification(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
func testBatchConfirmationNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test a case of serving notifications to multiple
|
||||
// clients, each requesting to be notified once a txid receives
|
||||
|
@ -174,9 +177,16 @@ func testBatchConfirmationNotification(miner *rpctest.Harness,
|
|||
if err != nil {
|
||||
t.Fatalf("unable to create test addr: %v", err)
|
||||
}
|
||||
confIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var confIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -257,13 +267,13 @@ func checkNotificationFields(ntfn *chainntnfs.SpendDetail,
|
|||
}
|
||||
|
||||
func testSpendNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test the spend notifications for all ChainNotifier
|
||||
// concrete implementations.
|
||||
//
|
||||
// To do so, we first create a new output to our test target address.
|
||||
outpoint, pkScript := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
|
@ -277,9 +287,16 @@ func testSpendNotification(miner *rpctest.Harness,
|
|||
const numClients = 5
|
||||
spendClients := make([]*chainntnfs.SpendEvent, numClients)
|
||||
for i := 0; i < numClients; i++ {
|
||||
spentIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
var spentIntent *chainntnfs.SpendEvent
|
||||
if scriptDispatch {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
nil, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
outpoint, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for spend ntfn: %v", err)
|
||||
}
|
||||
|
@ -288,7 +305,7 @@ func testSpendNotification(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
// Next, create a new transaction spending that output.
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, pkScript)
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
|
||||
|
||||
// Broadcast our spending transaction.
|
||||
spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true)
|
||||
|
@ -328,9 +345,16 @@ func testSpendNotification(miner *rpctest.Harness,
|
|||
|
||||
// Make sure registering a client after the tx is in the mempool still
|
||||
// doesn't trigger a notification.
|
||||
spentIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
var spentIntent *chainntnfs.SpendEvent
|
||||
if scriptDispatch {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
nil, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
outpoint, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for spend ntfn: %v", err)
|
||||
}
|
||||
|
@ -374,22 +398,23 @@ func testBlockEpochNotification(miner *rpctest.Harness,
|
|||
// block epoch notifications.
|
||||
|
||||
const numBlocks = 10
|
||||
const numNtfns = numBlocks + 1
|
||||
const numClients = 5
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Create numClients clients which will listen for block notifications. We
|
||||
// expect each client to receive 10 notifications for each of the ten
|
||||
// blocks we generate below. So we'll use a WaitGroup to synchronize the
|
||||
// test.
|
||||
// expect each client to receive 11 notifications, one for the current
|
||||
// tip of the chain, and one for each of the ten blocks we generate
|
||||
// below. So we'll use a WaitGroup to synchronize the test.
|
||||
for i := 0; i < numClients; i++ {
|
||||
epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for epoch notification")
|
||||
}
|
||||
|
||||
wg.Add(numBlocks)
|
||||
wg.Add(numNtfns)
|
||||
go func() {
|
||||
for i := 0; i < numBlocks; i++ {
|
||||
for i := 0; i < numNtfns; i++ {
|
||||
<-epochClient.Epochs
|
||||
wg.Done()
|
||||
}
|
||||
|
@ -416,7 +441,7 @@ func testBlockEpochNotification(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
func testMultiClientConfirmationNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test the case of a multiple clients registered to
|
||||
// receive a confirmation notification for the same transaction.
|
||||
|
@ -442,9 +467,16 @@ func testMultiClientConfirmationNotification(miner *rpctest.Harness,
|
|||
// Register for a conf notification for the above generated txid with
|
||||
// numConfsClients distinct clients.
|
||||
for i := 0; i < numConfsClients; i++ {
|
||||
confClient, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var confClient *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
confClient, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
confClient, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for confirmation: %v", err)
|
||||
}
|
||||
|
@ -479,7 +511,7 @@ func testMultiClientConfirmationNotification(miner *rpctest.Harness,
|
|||
// transaction that has already been included in a block. In this case, the
|
||||
// confirmation notification should be dispatched immediately.
|
||||
func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// First, let's send some coins to "ourself", obtaining a txid. We're
|
||||
// spending from a coinbase output here, so we use the dedicated
|
||||
|
@ -533,9 +565,16 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
// which is included in the last block. The height hint is the height before
|
||||
// the block is included. This notification should fire immediately since
|
||||
// only 1 confirmation is required.
|
||||
ntfn1, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid1, pkScript1, 1, uint32(currentHeight),
|
||||
)
|
||||
var ntfn1 *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
ntfn1, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript1, 1, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
ntfn1, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid1, pkScript1, 1, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -572,9 +611,16 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
// Register a confirmation notification for tx2, requiring 3 confirmations.
|
||||
// This transaction is only partially confirmed, so the notification should
|
||||
// not fire yet.
|
||||
ntfn2, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid2, pkScript2, 3, uint32(currentHeight),
|
||||
)
|
||||
var ntfn2 *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
ntfn2, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript2, 3, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
ntfn2, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid2, pkScript2, 3, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -600,9 +646,16 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
// Finally register a confirmation notification for tx3, requiring 1
|
||||
// confirmation. Ensure that conf notifications do not refire on txs
|
||||
// 1 or 2.
|
||||
ntfn3, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid3, pkScript3, 1, uint32(currentHeight-1),
|
||||
)
|
||||
var ntfn3 *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
ntfn3, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript3, 1, uint32(currentHeight-1),
|
||||
)
|
||||
} else {
|
||||
ntfn3, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid3, pkScript3, 1, uint32(currentHeight-1),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -632,7 +685,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
// checking for a confirmation. This should not cause the notifier to stop
|
||||
// working
|
||||
func testLazyNtfnConsumer(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// Create a transaction to be notified about. We'll register for
|
||||
// notifications on this transaction but won't be prompt in checking them
|
||||
|
@ -657,9 +710,16 @@ func testLazyNtfnConsumer(miner *rpctest.Harness,
|
|||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
}
|
||||
|
||||
firstConfIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var firstConfIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
firstConfIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
firstConfIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -686,10 +746,16 @@ func testLazyNtfnConsumer(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
numConfs = 1
|
||||
|
||||
secondConfIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var secondConfIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
secondConfIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
secondConfIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -719,16 +785,21 @@ func testLazyNtfnConsumer(miner *rpctest.Harness,
|
|||
// has already been included in a block. In this case, the spend notification
|
||||
// should be dispatched immediately.
|
||||
func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test the spend notifications for all ChainNotifier
|
||||
// concrete implementations.
|
||||
//
|
||||
// To do so, we first create a new output to our test target address.
|
||||
outpoint, pkScript := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
|
||||
_, heightHint, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
}
|
||||
|
||||
// We'll then spend this output and broadcast the spend transaction.
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, pkScript)
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
|
||||
spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast tx: %v", err)
|
||||
|
@ -748,8 +819,7 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
_, spendHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
}
|
||||
|
@ -763,9 +833,17 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
const numClients = 2
|
||||
spendClients := make([]*chainntnfs.SpendEvent, numClients)
|
||||
for i := 0; i < numClients; i++ {
|
||||
spentIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
var spentIntent *chainntnfs.SpendEvent
|
||||
if scriptDispatch {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
nil, output.PkScript, uint32(heightHint),
|
||||
)
|
||||
} else {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
outpoint, output.PkScript,
|
||||
uint32(heightHint),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for spend ntfn: %v",
|
||||
err)
|
||||
|
@ -779,8 +857,9 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
case ntfn := <-client.Spend:
|
||||
// We've received the spend nftn. So now verify
|
||||
// all the fields have been set properly.
|
||||
checkNotificationFields(ntfn, outpoint, spenderSha,
|
||||
currentHeight, t)
|
||||
checkNotificationFields(
|
||||
ntfn, outpoint, spenderSha, spendHeight, t,
|
||||
)
|
||||
case <-time.After(30 * time.Second):
|
||||
t.Fatalf("spend ntfn never received")
|
||||
}
|
||||
|
@ -824,14 +903,14 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
|||
}
|
||||
|
||||
func testCancelSpendNtfn(node *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'd like to test that once a spend notification is registered, it
|
||||
// can be cancelled before the notification is dispatched.
|
||||
|
||||
// First, we'll start by creating a new output that we can spend
|
||||
// ourselves.
|
||||
outpoint, pkScript := chainntnfs.CreateSpendableOutput(t, node)
|
||||
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, node)
|
||||
|
||||
_, currentHeight, err := node.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
|
@ -844,9 +923,16 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
|||
const numClients = 2
|
||||
spendClients := make([]*chainntnfs.SpendEvent, numClients)
|
||||
for i := 0; i < numClients; i++ {
|
||||
spentIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
var spentIntent *chainntnfs.SpendEvent
|
||||
if scriptDispatch {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
nil, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
spentIntent, err = notifier.RegisterSpendNtfn(
|
||||
outpoint, output.PkScript, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for spend ntfn: %v", err)
|
||||
}
|
||||
|
@ -855,7 +941,7 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
|||
}
|
||||
|
||||
// Next, create a new transaction spending that output.
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, pkScript)
|
||||
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
|
||||
|
||||
// Before we broadcast the spending transaction, we'll cancel the
|
||||
// notification of the first client.
|
||||
|
@ -877,8 +963,8 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
|||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
|
||||
// However, the spend notification for the first client should have
|
||||
// been dispatched.
|
||||
// The spend notification for the first client should have been
|
||||
// dispatched.
|
||||
select {
|
||||
case ntfn := <-spendClients[0].Spend:
|
||||
// We've received the spend nftn. So now verify all the
|
||||
|
@ -902,7 +988,7 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
|||
t.Fatalf("spend ntfn never received")
|
||||
}
|
||||
|
||||
// However, The spend notification of the second client should NOT have
|
||||
// However, the spend notification of the second client should NOT have
|
||||
// been dispatched.
|
||||
select {
|
||||
case _, ok := <-spendClients[1].Spend:
|
||||
|
@ -914,8 +1000,8 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
|||
}
|
||||
}
|
||||
|
||||
func testCancelEpochNtfn(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
|
||||
t *testing.T) {
|
||||
func testCancelEpochNtfn(node *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
|
||||
// We'd like to ensure that once a client cancels their block epoch
|
||||
// notifications, no further notifications are sent over the channel
|
||||
|
@ -964,8 +1050,8 @@ func testCancelEpochNtfn(node *rpctest.Harness, notifier chainntnfs.TestChainNot
|
|||
}
|
||||
}
|
||||
|
||||
func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
|
||||
t *testing.T) {
|
||||
func testReorgConf(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// Set up a new miner that we can use to cause a reorg.
|
||||
miner2, err := rpctest.New(chainntnfs.NetParams, nil, []string{"--txindex"})
|
||||
|
@ -1026,9 +1112,16 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.TestChainNotifier
|
|||
// Now that we have a txid, register a confirmation notification with
|
||||
// the chainntfn source.
|
||||
numConfs := uint32(2)
|
||||
confIntent, err := notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
var confIntent *chainntnfs.ConfirmationEvent
|
||||
if scriptDispatch {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
nil, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
} else {
|
||||
confIntent, err = notifier.RegisterConfirmationsNtfn(
|
||||
txid, pkScript, numConfs, uint32(currentHeight),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
|
@ -1116,18 +1209,26 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.TestChainNotifier
|
|||
// correctly handle outpoints whose spending transaction has been reorged out of
|
||||
// the chain.
|
||||
func testReorgSpend(miner *rpctest.Harness,
|
||||
notifier chainntnfs.TestChainNotifier, t *testing.T) {
|
||||
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
|
||||
|
||||
// We'll start by creating an output and registering a spend
|
||||
// notification for it.
|
||||
outpoint, pkScript := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
_, heightHint, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
spendIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
|
||||
var spendIntent *chainntnfs.SpendEvent
|
||||
if scriptDispatch {
|
||||
spendIntent, err = notifier.RegisterSpendNtfn(
|
||||
nil, output.PkScript, uint32(heightHint),
|
||||
)
|
||||
} else {
|
||||
spendIntent, err = notifier.RegisterSpendNtfn(
|
||||
outpoint, output.PkScript, uint32(heightHint),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register for spend: %v", err)
|
||||
}
|
||||
|
@ -1174,7 +1275,7 @@ func testReorgSpend(miner *rpctest.Harness,
|
|||
|
||||
// Craft the spending transaction for the outpoint created above and
|
||||
// confirm it under the chain of the original miner.
|
||||
spendTx := chainntnfs.CreateSpendTx(t, outpoint, pkScript)
|
||||
spendTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
|
||||
spendTxHash, err := miner.Node.SendRawTransaction(spendTx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast spend tx: %v", err)
|
||||
|
@ -1186,14 +1287,17 @@ func testReorgSpend(miner *rpctest.Harness,
|
|||
if _, err := miner.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
}
|
||||
_, spendHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get spend height: %v", err)
|
||||
}
|
||||
|
||||
// We should see a spend notification dispatched with the correct spend
|
||||
// details.
|
||||
select {
|
||||
case spendDetails := <-spendIntent.Spend:
|
||||
checkNotificationFields(
|
||||
spendDetails, outpoint, spendTxHash,
|
||||
currentHeight+numBlocks, t,
|
||||
spendDetails, outpoint, spendTxHash, spendHeight, t,
|
||||
)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("expected spend notification to be dispatched")
|
||||
|
@ -1243,19 +1347,18 @@ func testReorgSpend(miner *rpctest.Harness,
|
|||
if err := chainntnfs.WaitForMempoolTx(miner, spendTxHash); err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
_, currentHeight, err = miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
if _, err := miner.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
_, spendHeight, err = miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case spendDetails := <-spendIntent.Spend:
|
||||
checkNotificationFields(
|
||||
spendDetails, outpoint, spendTxHash,
|
||||
currentHeight+numBlocks, t,
|
||||
spendDetails, outpoint, spendTxHash, spendHeight, t,
|
||||
)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("expected spend notification to be dispatched")
|
||||
|
@ -1392,6 +1495,16 @@ func testCatchUpOnMissedBlocks(miner *rpctest.Harness,
|
|||
if err != nil {
|
||||
t.Fatalf("unable to register for epoch notification: %v", err)
|
||||
}
|
||||
|
||||
// Drain the notification dispatched upon registration as we're
|
||||
// not interested in it.
|
||||
select {
|
||||
case <-epochClient.Epochs:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("expected to receive epoch for current block " +
|
||||
"upon registration")
|
||||
}
|
||||
|
||||
clients = append(clients, epochClient)
|
||||
}
|
||||
|
||||
|
@ -1567,6 +1680,16 @@ func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
|
|||
if err != nil {
|
||||
t.Fatalf("unable to register for epoch notification: %v", err)
|
||||
}
|
||||
|
||||
// Drain the notification dispatched upon registration as we're
|
||||
// not interested in it.
|
||||
select {
|
||||
case <-epochClient.Epochs:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("expected to receive epoch for current block " +
|
||||
"upon registration")
|
||||
}
|
||||
|
||||
clients = append(clients, epochClient)
|
||||
}
|
||||
|
||||
|
@ -1642,7 +1765,13 @@ func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
|
|||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
type txNtfnTestCase struct {
|
||||
name string
|
||||
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
|
||||
scriptDispatch bool, t *testing.T)
|
||||
}
|
||||
|
||||
type blockNtfnTestCase struct {
|
||||
name string
|
||||
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
|
||||
t *testing.T)
|
||||
|
@ -1654,7 +1783,7 @@ type blockCatchupTestCase struct {
|
|||
t *testing.T)
|
||||
}
|
||||
|
||||
var ntfnTests = []testCase{
|
||||
var txNtfnTests = []txNtfnTestCase{
|
||||
{
|
||||
name: "single conf ntfn",
|
||||
test: testSingleConfirmationNotification,
|
||||
|
@ -1672,41 +1801,44 @@ var ntfnTests = []testCase{
|
|||
test: testMultiClientConfirmationNotification,
|
||||
},
|
||||
{
|
||||
name: "spend ntfn",
|
||||
test: testSpendNotification,
|
||||
},
|
||||
{
|
||||
name: "block epoch",
|
||||
test: testBlockEpochNotification,
|
||||
name: "lazy ntfn consumer",
|
||||
test: testLazyNtfnConsumer,
|
||||
},
|
||||
{
|
||||
name: "historical conf dispatch",
|
||||
test: testTxConfirmedBeforeNtfnRegistration,
|
||||
},
|
||||
{
|
||||
name: "reorg conf",
|
||||
test: testReorgConf,
|
||||
},
|
||||
{
|
||||
name: "spend ntfn",
|
||||
test: testSpendNotification,
|
||||
},
|
||||
{
|
||||
name: "historical spend dispatch",
|
||||
test: testSpendBeforeNtfnRegistration,
|
||||
},
|
||||
{
|
||||
name: "reorg spend",
|
||||
test: testReorgSpend,
|
||||
},
|
||||
{
|
||||
name: "cancel spend ntfn",
|
||||
test: testCancelSpendNtfn,
|
||||
},
|
||||
}
|
||||
|
||||
var blockNtfnTests = []blockNtfnTestCase{
|
||||
{
|
||||
name: "block epoch",
|
||||
test: testBlockEpochNotification,
|
||||
},
|
||||
{
|
||||
name: "cancel epoch ntfn",
|
||||
test: testCancelEpochNtfn,
|
||||
},
|
||||
{
|
||||
name: "lazy ntfn consumer",
|
||||
test: testLazyNtfnConsumer,
|
||||
},
|
||||
{
|
||||
name: "reorg conf",
|
||||
test: testReorgConf,
|
||||
},
|
||||
{
|
||||
name: "reorg spend",
|
||||
test: testReorgSpend,
|
||||
},
|
||||
}
|
||||
|
||||
var blockCatchupTests = []blockCatchupTestCase{
|
||||
|
@ -1746,7 +1878,8 @@ func TestInterfaces(t *testing.T) {
|
|||
rpcConfig := miner.RPCConfig()
|
||||
p2pAddr := miner.P2PAddress()
|
||||
|
||||
log.Printf("Running %v ChainNotifier interface tests", len(ntfnTests))
|
||||
log.Printf("Running %v ChainNotifier interface tests",
|
||||
2*len(txNtfnTests)+len(blockNtfnTests)+len(blockCatchupTests))
|
||||
|
||||
for _, notifierDriver := range chainntnfs.RegisteredNotifiers() {
|
||||
// Initialize a height hint cache for each notifier.
|
||||
|
@ -1777,14 +1910,16 @@ func TestInterfaces(t *testing.T) {
|
|||
)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return bitcoindnotify.New(
|
||||
bitcoindConn, hintCache, hintCache,
|
||||
bitcoindConn, chainntnfs.NetParams,
|
||||
hintCache, hintCache,
|
||||
), nil
|
||||
}
|
||||
|
||||
case "btcd":
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return btcdnotify.New(
|
||||
&rpcConfig, hintCache, hintCache,
|
||||
&rpcConfig, chainntnfs.NetParams,
|
||||
hintCache, hintCache,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1796,7 +1931,7 @@ func TestInterfaces(t *testing.T) {
|
|||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return neutrinonotify.New(
|
||||
spvNode, hintCache, hintCache,
|
||||
)
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1813,12 +1948,30 @@ func TestInterfaces(t *testing.T) {
|
|||
notifierType, err)
|
||||
}
|
||||
|
||||
for _, ntfnTest := range ntfnTests {
|
||||
testName := fmt.Sprintf("%v: %v", notifierType,
|
||||
ntfnTest.name)
|
||||
for _, txNtfnTest := range txNtfnTests {
|
||||
for _, scriptDispatch := range []bool{false, true} {
|
||||
testName := fmt.Sprintf("%v %v", notifierType,
|
||||
txNtfnTest.name)
|
||||
if scriptDispatch {
|
||||
testName += " with script dispatch"
|
||||
}
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
txNtfnTest.test(
|
||||
miner, notifier, scriptDispatch,
|
||||
t,
|
||||
)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, blockNtfnTest := range blockNtfnTests {
|
||||
testName := fmt.Sprintf("%v %v", notifierType,
|
||||
blockNtfnTest.name)
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
ntfnTest.test(miner, notifier, t)
|
||||
blockNtfnTest.test(miner, notifier, t)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
|
@ -1836,7 +1989,7 @@ func TestInterfaces(t *testing.T) {
|
|||
notifierType, err)
|
||||
}
|
||||
|
||||
testName := fmt.Sprintf("%v: %v", notifierType,
|
||||
testName := fmt.Sprintf("%v %v", notifierType,
|
||||
blockCatchupTest.name)
|
||||
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
// createNewNotifier creates a new instance of the ChainNotifier interface
|
||||
// implemented by NeutrinoNotifier.
|
||||
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
||||
if len(args) != 2 {
|
||||
if len(args) != 3 {
|
||||
return nil, fmt.Errorf("incorrect number of arguments to "+
|
||||
".New(...), expected 2, instead passed %v", len(args))
|
||||
".New(...), expected 3, instead passed %v", len(args))
|
||||
}
|
||||
|
||||
config, ok := args[0].(*neutrino.ChainService)
|
||||
|
@ -34,7 +34,7 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
|||
"is incorrect, expected a chainntfs.ConfirmHintCache")
|
||||
}
|
||||
|
||||
return New(config, spendHintCache, confirmHintCache)
|
||||
return New(config, spendHintCache, confirmHintCache), nil
|
||||
}
|
||||
|
||||
// init registers a driver for the NeutrinoNotify concrete implementation of
|
||||
|
|
|
@ -27,13 +27,6 @@ const (
|
|||
notifierType = "neutrino"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chainntnfs: system interrupt " +
|
||||
"while attempting to register for spend notification.")
|
||||
)
|
||||
|
||||
// NeutrinoNotifier is a version of ChainNotifier that's backed by the neutrino
|
||||
// Bitcoin light client. Unlike other implementations, this implementation
|
||||
// speaks directly to the p2p network. As a result, this implementation of the
|
||||
|
@ -51,8 +44,8 @@ type NeutrinoNotifier struct {
|
|||
started int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
heightMtx sync.RWMutex
|
||||
bestHeight uint32
|
||||
bestBlockMtx sync.RWMutex
|
||||
bestBlock chainntnfs.BlockEpoch
|
||||
|
||||
p2pNode *neutrino.ChainService
|
||||
chainView *neutrino.Rescan
|
||||
|
@ -69,6 +62,7 @@ type NeutrinoNotifier struct {
|
|||
rescanErr <-chan error
|
||||
|
||||
chainUpdates *queue.ConcurrentQueue
|
||||
txUpdates *queue.ConcurrentQueue
|
||||
|
||||
// spendHintCache is a cache used to query and update the latest height
|
||||
// hints for an outpoint. Each height hint represents the earliest
|
||||
|
@ -93,27 +87,27 @@ var _ chainntnfs.ChainNotifier = (*NeutrinoNotifier)(nil)
|
|||
// NOTE: The passed neutrino node should already be running and active before
|
||||
// being passed into this function.
|
||||
func New(node *neutrino.ChainService, spendHintCache chainntnfs.SpendHintCache,
|
||||
confirmHintCache chainntnfs.ConfirmHintCache) (*NeutrinoNotifier, error) {
|
||||
confirmHintCache chainntnfs.ConfirmHintCache) *NeutrinoNotifier {
|
||||
|
||||
notifier := &NeutrinoNotifier{
|
||||
return &NeutrinoNotifier{
|
||||
notificationCancels: make(chan interface{}),
|
||||
notificationRegistry: make(chan interface{}),
|
||||
|
||||
blockEpochClients: make(map[uint64]*blockEpochRegistration),
|
||||
|
||||
p2pNode: node,
|
||||
p2pNode: node,
|
||||
chainConn: &NeutrinoChainConn{node},
|
||||
|
||||
rescanErr: make(chan error),
|
||||
|
||||
chainUpdates: queue.NewConcurrentQueue(10),
|
||||
txUpdates: queue.NewConcurrentQueue(10),
|
||||
|
||||
spendHintCache: spendHintCache,
|
||||
confirmHintCache: confirmHintCache,
|
||||
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
return notifier, nil
|
||||
}
|
||||
|
||||
// Start contacts the running neutrino light client and kicks off an initial
|
||||
|
@ -132,8 +126,13 @@ func (n *NeutrinoNotifier) Start() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.bestBlock.Hash = &startingPoint.Hash
|
||||
n.bestBlock.Height = startingPoint.Height
|
||||
|
||||
n.bestHeight = uint32(startingPoint.Height)
|
||||
n.txNotifier = chainntnfs.NewTxNotifier(
|
||||
uint32(n.bestBlock.Height), chainntnfs.ReorgSafetyLimit,
|
||||
n.confirmHintCache, n.spendHintCache,
|
||||
)
|
||||
|
||||
// Next, we'll create our set of rescan options. Currently it's
|
||||
// required that a user MUST set an addr/outpoint/txid when creating a
|
||||
|
@ -147,24 +146,19 @@ func (n *NeutrinoNotifier) Start() error {
|
|||
rpcclient.NotificationHandlers{
|
||||
OnFilteredBlockConnected: n.onFilteredBlockConnected,
|
||||
OnFilteredBlockDisconnected: n.onFilteredBlockDisconnected,
|
||||
OnRedeemingTx: n.onRelevantTx,
|
||||
},
|
||||
),
|
||||
neutrino.WatchInputs(zeroInput),
|
||||
}
|
||||
|
||||
n.txNotifier = chainntnfs.NewTxNotifier(
|
||||
n.bestHeight, chainntnfs.ReorgSafetyLimit, n.confirmHintCache,
|
||||
n.spendHintCache,
|
||||
)
|
||||
|
||||
n.chainConn = &NeutrinoChainConn{n.p2pNode}
|
||||
|
||||
// Finally, we'll create our rescan struct, start it, and launch all
|
||||
// the goroutines we need to operate this ChainNotifier instance.
|
||||
n.chainView = n.p2pNode.NewRescan(rescanOptions...)
|
||||
n.rescanErr = n.chainView.Start()
|
||||
|
||||
n.chainUpdates.Start()
|
||||
n.txUpdates.Start()
|
||||
|
||||
n.wg.Add(1)
|
||||
go n.notificationDispatcher()
|
||||
|
@ -183,6 +177,7 @@ func (n *NeutrinoNotifier) Stop() error {
|
|||
n.wg.Wait()
|
||||
|
||||
n.chainUpdates.Stop()
|
||||
n.txUpdates.Stop()
|
||||
|
||||
// Notify all pending clients of our shutdown by closing the related
|
||||
// notification channels.
|
||||
|
@ -226,11 +221,14 @@ func (n *NeutrinoNotifier) onFilteredBlockConnected(height int32,
|
|||
|
||||
// Append this new chain update to the end of the queue of new chain
|
||||
// updates.
|
||||
n.chainUpdates.ChanIn() <- &filteredBlock{
|
||||
select {
|
||||
case n.chainUpdates.ChanIn() <- &filteredBlock{
|
||||
hash: header.BlockHash(),
|
||||
height: uint32(height),
|
||||
txns: txns,
|
||||
connect: true,
|
||||
}:
|
||||
case <-n.quit:
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,10 +239,29 @@ func (n *NeutrinoNotifier) onFilteredBlockDisconnected(height int32,
|
|||
|
||||
// Append this new chain update to the end of the queue of new chain
|
||||
// disconnects.
|
||||
n.chainUpdates.ChanIn() <- &filteredBlock{
|
||||
select {
|
||||
case n.chainUpdates.ChanIn() <- &filteredBlock{
|
||||
hash: header.BlockHash(),
|
||||
height: uint32(height),
|
||||
connect: false,
|
||||
}:
|
||||
case <-n.quit:
|
||||
}
|
||||
}
|
||||
|
||||
// relevantTx represents a relevant transaction to the notifier that fulfills
|
||||
// any outstanding spend requests.
|
||||
type relevantTx struct {
|
||||
tx *btcutil.Tx
|
||||
details *btcjson.BlockDetails
|
||||
}
|
||||
|
||||
// onRelevantTx is a callback that proxies relevant transaction notifications
|
||||
// from the backend to the notifier's main event handler.
|
||||
func (n *NeutrinoNotifier) onRelevantTx(tx *btcutil.Tx, details *btcjson.BlockDetails) {
|
||||
select {
|
||||
case n.txUpdates.ChanIn() <- &relevantTx{tx, details}:
|
||||
case <-n.quit:
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +310,7 @@ out:
|
|||
defer n.wg.Done()
|
||||
|
||||
confDetails, err := n.historicalConfDetails(
|
||||
msg.TxID, msg.PkScript,
|
||||
msg.ConfRequest,
|
||||
msg.StartHeight, msg.EndHeight,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -308,7 +325,7 @@ out:
|
|||
// cache at tip, since any pending
|
||||
// rescans have now completed.
|
||||
err = n.txNotifier.UpdateConfDetails(
|
||||
*msg.TxID, confDetails,
|
||||
msg.ConfRequest, confDetails,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
|
@ -317,25 +334,44 @@ out:
|
|||
|
||||
case *blockEpochRegistration:
|
||||
chainntnfs.Log.Infof("New block epoch subscription")
|
||||
|
||||
n.blockEpochClients[msg.epochID] = msg
|
||||
if msg.bestBlock != nil {
|
||||
n.heightMtx.Lock()
|
||||
bestHeight := int32(n.bestHeight)
|
||||
n.heightMtx.Unlock()
|
||||
missedBlocks, err :=
|
||||
chainntnfs.GetClientMissedBlocks(
|
||||
n.chainConn, msg.bestBlock,
|
||||
bestHeight, false,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
for _, block := range missedBlocks {
|
||||
n.notifyBlockEpochClient(msg,
|
||||
block.Height, block.Hash)
|
||||
}
|
||||
|
||||
// If the client did not provide their best
|
||||
// known block, then we'll immediately dispatch
|
||||
// a notification for the current tip.
|
||||
if msg.bestBlock == nil {
|
||||
n.notifyBlockEpochClient(
|
||||
msg, n.bestBlock.Height,
|
||||
n.bestBlock.Hash,
|
||||
)
|
||||
|
||||
msg.errorChan <- nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we'll attempt to deliver the
|
||||
// backlog of notifications from their best
|
||||
// known block.
|
||||
n.bestBlockMtx.Lock()
|
||||
bestHeight := n.bestBlock.Height
|
||||
n.bestBlockMtx.Unlock()
|
||||
|
||||
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
|
||||
n.chainConn, msg.bestBlock, bestHeight,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
msg.errorChan <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, block := range missedBlocks {
|
||||
n.notifyBlockEpochClient(
|
||||
msg, block.Height, block.Hash,
|
||||
)
|
||||
}
|
||||
|
||||
msg.errorChan <- nil
|
||||
|
||||
case *rescanFilterUpdate:
|
||||
|
@ -350,7 +386,7 @@ out:
|
|||
case item := <-n.chainUpdates.ChanOut():
|
||||
update := item.(*filteredBlock)
|
||||
if update.connect {
|
||||
n.heightMtx.Lock()
|
||||
n.bestBlockMtx.Lock()
|
||||
// Since neutrino has no way of knowing what
|
||||
// height to rewind to in the case of a reorged
|
||||
// best known height, there is no point in
|
||||
|
@ -359,27 +395,24 @@ out:
|
|||
// the other notifiers do when they receive
|
||||
// a new connected block. Therefore, we just
|
||||
// compare the heights.
|
||||
if update.height != n.bestHeight+1 {
|
||||
if update.height != uint32(n.bestBlock.Height+1) {
|
||||
// Handle the case where the notifier
|
||||
// missed some blocks from its chain
|
||||
// backend
|
||||
chainntnfs.Log.Infof("Missed blocks, " +
|
||||
"attempting to catch up")
|
||||
bestBlock := chainntnfs.BlockEpoch{
|
||||
Height: int32(n.bestHeight),
|
||||
Hash: nil,
|
||||
}
|
||||
|
||||
_, missedBlocks, err :=
|
||||
chainntnfs.HandleMissedBlocks(
|
||||
n.chainConn,
|
||||
n.txNotifier,
|
||||
bestBlock,
|
||||
n.bestBlock,
|
||||
int32(update.height),
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
n.heightMtx.Unlock()
|
||||
n.bestBlockMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -388,13 +421,13 @@ out:
|
|||
n.getFilteredBlock(block)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
n.heightMtx.Unlock()
|
||||
n.bestBlockMtx.Unlock()
|
||||
continue out
|
||||
}
|
||||
err = n.handleBlockConnected(filteredBlock)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
n.heightMtx.Unlock()
|
||||
n.bestBlockMtx.Unlock()
|
||||
continue out
|
||||
}
|
||||
}
|
||||
|
@ -405,42 +438,46 @@ out:
|
|||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
}
|
||||
n.heightMtx.Unlock()
|
||||
|
||||
n.bestBlockMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
n.heightMtx.Lock()
|
||||
if update.height != uint32(n.bestHeight) {
|
||||
n.bestBlockMtx.Lock()
|
||||
if update.height != uint32(n.bestBlock.Height) {
|
||||
chainntnfs.Log.Infof("Missed disconnected " +
|
||||
"blocks, attempting to catch up")
|
||||
}
|
||||
|
||||
hash, err := n.p2pNode.GetBlockHash(int64(n.bestHeight))
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to fetch block hash "+
|
||||
"for height %d: %v", n.bestHeight, err)
|
||||
n.heightMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
notifierBestBlock := chainntnfs.BlockEpoch{
|
||||
Height: int32(n.bestHeight),
|
||||
Hash: hash,
|
||||
}
|
||||
newBestBlock, err := chainntnfs.RewindChain(
|
||||
n.chainConn, n.txNotifier, notifierBestBlock,
|
||||
n.chainConn, n.txNotifier, n.bestBlock,
|
||||
int32(update.height-1),
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to rewind chain "+
|
||||
"from height %d to height %d: %v",
|
||||
n.bestHeight, update.height-1, err)
|
||||
n.bestBlock.Height, update.height-1, err)
|
||||
}
|
||||
|
||||
// Set the bestHeight here in case a chain rewind
|
||||
// partially completed.
|
||||
n.bestHeight = uint32(newBestBlock.Height)
|
||||
n.heightMtx.Unlock()
|
||||
n.bestBlock = newBestBlock
|
||||
n.bestBlockMtx.Unlock()
|
||||
|
||||
case txUpdate := <-n.txUpdates.ChanOut():
|
||||
// A new relevant transaction notification has been
|
||||
// received from the backend. We'll attempt to process
|
||||
// it to determine if it fulfills any outstanding
|
||||
// confirmation and/or spend requests and dispatch
|
||||
// notifications for them.
|
||||
update := txUpdate.(*relevantTx)
|
||||
err := n.txNotifier.ProcessRelevantSpendTx(
|
||||
update.tx, uint32(update.details.Height),
|
||||
)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to process "+
|
||||
"transaction %v: %v", update.tx.Hash(),
|
||||
err)
|
||||
}
|
||||
|
||||
case err := <-n.rescanErr:
|
||||
chainntnfs.Log.Errorf("Error during rescan: %v", err)
|
||||
|
@ -452,21 +489,20 @@ out:
|
|||
}
|
||||
}
|
||||
|
||||
// historicalConfDetails looks up whether a transaction is already included in
|
||||
// a block in the active chain and, if so, returns details about the
|
||||
// confirmation.
|
||||
func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
||||
pkScript []byte,
|
||||
// historicalConfDetails looks up whether a confirmation request (txid/output
|
||||
// script) has already been included in a block in the active chain and, if so,
|
||||
// returns details about said block.
|
||||
func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
|
||||
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation, error) {
|
||||
|
||||
// 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-- {
|
||||
// Ensure we haven't been requested to shut down before
|
||||
// processing the next height.
|
||||
select {
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -498,7 +534,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
|||
// In the case that the filter exists, we'll attempt to see if
|
||||
// any element in it matches our target public key script.
|
||||
key := builder.DeriveKey(blockHash)
|
||||
match, err := regFilter.Match(key, pkScript)
|
||||
match, err := regFilter.Match(key, confRequest.PkScript.Script())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to query filter: %v", err)
|
||||
}
|
||||
|
@ -516,16 +552,21 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get block from network: %v", err)
|
||||
}
|
||||
for j, tx := range block.Transactions() {
|
||||
txHash := tx.Hash()
|
||||
if txHash.IsEqual(targetHash) {
|
||||
confDetails := chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: scanHeight,
|
||||
TxIndex: uint32(j),
|
||||
}
|
||||
return &confDetails, nil
|
||||
|
||||
// For every transaction in the block, check which one matches
|
||||
// our request. If we find one that does, we can dispatch its
|
||||
// confirmation details.
|
||||
for i, tx := range block.Transactions() {
|
||||
if !confRequest.MatchesTx(tx.MsgTx()) {
|
||||
continue
|
||||
}
|
||||
|
||||
return &chainntnfs.TxConfirmation{
|
||||
Tx: tx.MsgTx(),
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: scanHeight,
|
||||
TxIndex: uint32(i),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,6 +576,8 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash,
|
|||
// handleBlockConnected applies a chain update for a new block. Any watched
|
||||
// transactions included this block will processed to either send notifications
|
||||
// now or after numConfirmations confs.
|
||||
//
|
||||
// NOTE: This method must be called with the bestBlockMtx lock held.
|
||||
func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error {
|
||||
// We'll extend the txNotifier's height with the information of this new
|
||||
// block, which will handle all of the notification logic for us.
|
||||
|
@ -553,7 +596,8 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error {
|
|||
// registered clients whom have had notifications fulfilled. Before
|
||||
// doing so, we'll make sure update our in memory state in order to
|
||||
// satisfy any client requests based upon the new block.
|
||||
n.bestHeight = newBlock.height
|
||||
n.bestBlock.Hash = &newBlock.hash
|
||||
n.bestBlock.Height = int32(newBlock.height)
|
||||
|
||||
n.notifyBlockEpochs(int32(newBlock.height), &newBlock.hash)
|
||||
return n.txNotifier.NotifyHeight(newBlock.height)
|
||||
|
@ -603,8 +647,12 @@ func (n *NeutrinoNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistr
|
|||
}
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint has been spent by a transaction on-chain. Once a spend of the
|
||||
// target outpoint has been detected, the details of the spending event will be
|
||||
// outpoint/output script has been spent by a transaction on-chain. When
|
||||
// intending to be notified of the spend of an output script, a nil outpoint
|
||||
// must be used. The heightHint should represent the earliest height in the
|
||||
// chain of the transaction that spent the outpoint/output script.
|
||||
//
|
||||
// Once a spend of has been detected, the details of the spending event will be
|
||||
// sent across the 'Spend' channel.
|
||||
func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
@ -612,26 +660,22 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
// First, we'll construct a spend notification request and hand it off
|
||||
// to the txNotifier.
|
||||
spendID := atomic.AddUint64(&n.spendClientCounter, 1)
|
||||
cancel := func() {
|
||||
n.txNotifier.CancelSpend(*outpoint, spendID)
|
||||
}
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
SpendID: spendID,
|
||||
OutPoint: *outpoint,
|
||||
Event: chainntnfs.NewSpendEvent(cancel),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
historicalDispatch, err := n.txNotifier.RegisterSpend(ntfn)
|
||||
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
SpendID: spendID,
|
||||
SpendRequest: spendRequest,
|
||||
Event: chainntnfs.NewSpendEvent(func() {
|
||||
n.txNotifier.CancelSpend(spendRequest, spendID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
// 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
|
||||
// for us to do.
|
||||
if historicalDispatch == nil {
|
||||
return ntfn.Event, nil
|
||||
historicalDispatch, txNotifierTip, err := n.txNotifier.RegisterSpend(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// To determine whether this outpoint has been spent on-chain, we'll
|
||||
|
@ -640,45 +684,64 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
// past.
|
||||
//
|
||||
// We'll update our filter first to ensure we can immediately detect the
|
||||
// spend at tip. To do so, we'll map the script into an address
|
||||
// type so we can instruct neutrino to match if the transaction
|
||||
// containing the script is found in a block.
|
||||
// spend at tip.
|
||||
inputToWatch := neutrino.InputWithScript{
|
||||
OutPoint: *outpoint,
|
||||
PkScript: pkScript,
|
||||
OutPoint: spendRequest.OutPoint,
|
||||
PkScript: spendRequest.PkScript.Script(),
|
||||
}
|
||||
updateOptions := []neutrino.UpdateOption{
|
||||
neutrino.AddInputs(inputToWatch),
|
||||
neutrino.DisableDisconnectedNtfns(true),
|
||||
}
|
||||
|
||||
// We'll use the txNotifier's tip as the starting point of our filter
|
||||
// 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
|
||||
// cannot do so with GetUtxo since it matches outpoints.
|
||||
rewindHeight := txNotifierTip
|
||||
if historicalDispatch != nil &&
|
||||
spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
rewindHeight = historicalDispatch.StartHeight
|
||||
}
|
||||
updateOptions = append(updateOptions, neutrino.Rewind(rewindHeight))
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
select {
|
||||
case n.notificationRegistry <- &rescanFilterUpdate{
|
||||
updateOptions: []neutrino.UpdateOption{
|
||||
neutrino.AddInputs(inputToWatch),
|
||||
neutrino.Rewind(historicalDispatch.EndHeight),
|
||||
neutrino.DisableDisconnectedNtfns(true),
|
||||
},
|
||||
errChan: errChan,
|
||||
updateOptions: updateOptions,
|
||||
errChan: errChan,
|
||||
}:
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
select {
|
||||
case err = <-errChan:
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to update filter: %v", err)
|
||||
}
|
||||
|
||||
// If the txNotifier didn't return any details to perform a historical
|
||||
// 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
|
||||
// nothing left for us to do.
|
||||
if historicalDispatch == nil ||
|
||||
spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// With the filter updated, we'll dispatch our historical rescan to
|
||||
// ensure we detect the spend if it happened in the past. We'll ensure
|
||||
// that neutrino is caught up to the starting height before we attempt
|
||||
// to fetch the UTXO from the chain. If we're behind, then we may miss a
|
||||
// notification dispatch.
|
||||
for {
|
||||
n.heightMtx.RLock()
|
||||
currentHeight := n.bestHeight
|
||||
n.heightMtx.RUnlock()
|
||||
n.bestBlockMtx.RLock()
|
||||
currentHeight := uint32(n.bestBlock.Height)
|
||||
n.bestBlockMtx.RUnlock()
|
||||
|
||||
if currentHeight >= historicalDispatch.StartHeight {
|
||||
break
|
||||
|
@ -706,7 +769,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
if spendReport != nil && spendReport.SpendingTx != nil {
|
||||
spendingTxHash := spendReport.SpendingTx.TxHash()
|
||||
spendDetails = &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: outpoint,
|
||||
SpentOutPoint: &spendRequest.OutPoint,
|
||||
SpenderTxHash: &spendingTxHash,
|
||||
SpendingTx: spendReport.SpendingTx,
|
||||
SpenderInputIndex: spendReport.SpendingInputIndex,
|
||||
|
@ -718,7 +781,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
// not, we'll mark our historical rescan as complete to ensure the
|
||||
// outpoint's spend hint gets updated upon connected/disconnected
|
||||
// blocks.
|
||||
err = n.txNotifier.UpdateSpendDetails(*outpoint, spendDetails)
|
||||
err = n.txNotifier.UpdateSpendDetails(spendRequest, spendDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -726,40 +789,48 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// RegisterConfirmationsNtfn registers a notification with NeutrinoNotifier
|
||||
// which will be triggered once the txid reaches numConfs number of
|
||||
// confirmations.
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once the target
|
||||
// txid/output script has reached numConfs confirmations on-chain. When
|
||||
// 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,
|
||||
pkScript []byte,
|
||||
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
|
||||
// Construct a notification request for the transaction and send it to
|
||||
// the main event loop.
|
||||
confID := atomic.AddUint64(&n.confClientCounter, 1)
|
||||
confRequest, err := chainntnfs.NewConfRequest(txid, pkScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntfn := &chainntnfs.ConfNtfn{
|
||||
ConfID: atomic.AddUint64(&n.confClientCounter, 1),
|
||||
TxID: txid,
|
||||
PkScript: pkScript,
|
||||
ConfID: confID,
|
||||
ConfRequest: confRequest,
|
||||
NumConfirmations: numConfs,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs),
|
||||
HeightHint: heightHint,
|
||||
Event: chainntnfs.NewConfirmationEvent(numConfs, func() {
|
||||
n.txNotifier.CancelConf(confRequest, confID)
|
||||
}),
|
||||
HeightHint: heightHint,
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New confirmation subscription: "+
|
||||
"txid=%v, numconfs=%v", txid, numConfs)
|
||||
chainntnfs.Log.Infof("New confirmation subscription: %v, num_confs=%v",
|
||||
confRequest, numConfs)
|
||||
|
||||
// Register the conf notification with the TxNotifier. A non-nil value
|
||||
// for `dispatch` will be returned if we are required to perform a
|
||||
// manual scan for the confirmation. Otherwise the notifier will begin
|
||||
// watching at tip for the transaction to confirm.
|
||||
dispatch, err := n.txNotifier.RegisterConf(ntfn)
|
||||
dispatch, txNotifierTip, err := n.txNotifier.RegisterConf(ntfn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dispatch == nil {
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// dispatch a historical rescan to determine if it has confirmed in the
|
||||
|
@ -770,7 +841,9 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||
// type so we can instruct neutrino to match if the transaction
|
||||
// containing the script is found in a block.
|
||||
params := n.p2pNode.ChainParams()
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ¶ms)
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
confRequest.PkScript.Script(), ¶ms,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract script: %v", err)
|
||||
}
|
||||
|
@ -782,30 +855,36 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||
case n.notificationRegistry <- &rescanFilterUpdate{
|
||||
updateOptions: []neutrino.UpdateOption{
|
||||
neutrino.AddAddrs(addrs...),
|
||||
neutrino.Rewind(dispatch.EndHeight),
|
||||
neutrino.Rewind(txNotifierTip),
|
||||
neutrino.DisableDisconnectedNtfns(true),
|
||||
},
|
||||
errChan: errChan,
|
||||
}:
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
select {
|
||||
case err = <-errChan:
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
if err != nil {
|
||||
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.
|
||||
select {
|
||||
case n.notificationRegistry <- dispatch:
|
||||
case <-n.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
return ntfn.Event, nil
|
||||
|
@ -838,7 +917,9 @@ type epochCancel struct {
|
|||
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
|
||||
// caller to receive notifications, of each new block connected to the main
|
||||
// chain. Clients have the option of passing in their best known block, which
|
||||
// the notifier uses to check if they are behind on blocks and catch them up.
|
||||
// the notifier uses to check if they are behind on blocks and catch them up. If
|
||||
// they do not provide one, then a notification will be dispatched immediately
|
||||
// for the current tip of the chain upon a successful registration.
|
||||
func (n *NeutrinoNotifier) RegisterBlockEpochNtfn(
|
||||
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
|
|
|
@ -53,14 +53,13 @@ func (n *NeutrinoNotifier) UnsafeStart(bestHeight int32,
|
|||
n.confirmHintCache, n.spendHintCache,
|
||||
)
|
||||
|
||||
n.chainConn = &NeutrinoChainConn{n.p2pNode}
|
||||
|
||||
// Finally, we'll create our rescan struct, start it, and launch all
|
||||
// the goroutines we need to operate this ChainNotifier instance.
|
||||
n.chainView = n.p2pNode.NewRescan(rescanOptions...)
|
||||
n.rescanErr = n.chainView.Start()
|
||||
|
||||
n.chainUpdates.Start()
|
||||
n.txUpdates.Start()
|
||||
|
||||
if generateBlocks != nil {
|
||||
// Ensure no block notifications are pending when we start the
|
||||
|
@ -90,7 +89,8 @@ func (n *NeutrinoNotifier) UnsafeStart(bestHeight int32,
|
|||
|
||||
// Run notificationDispatcher after setting the notifier's best height
|
||||
// to avoid a race condition.
|
||||
n.bestHeight = uint32(bestHeight)
|
||||
n.bestBlock.Hash = bestHash
|
||||
n.bestBlock.Height = bestHeight
|
||||
|
||||
n.wg.Add(1)
|
||||
go n.notificationDispatcher()
|
||||
|
|
|
@ -35,35 +35,44 @@ var (
|
|||
|
||||
var (
|
||||
NetParams = &chaincfg.RegressionNetParams
|
||||
|
||||
testPrivKey = []byte{
|
||||
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
|
||||
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
|
||||
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
|
||||
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
|
||||
}
|
||||
privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey)
|
||||
addrPk, _ = btcutil.NewAddressPubKey(
|
||||
pubKey.SerializeCompressed(), NetParams,
|
||||
)
|
||||
testAddr = addrPk.AddressPubKeyHash()
|
||||
)
|
||||
|
||||
// GetTestTxidAndScript generate a new test transaction and returns its txid and
|
||||
// the script of the output being generated.
|
||||
func GetTestTxidAndScript(h *rpctest.Harness) (*chainhash.Hash, []byte, error) {
|
||||
script, err := txscript.PayToAddrScript(testAddr)
|
||||
// randPubKeyHashScript generates a P2PKH script that pays to the public key of
|
||||
// a randomly-generated private key.
|
||||
func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) {
|
||||
privKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: script}
|
||||
pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||
addrScript, err := btcutil.NewAddressPubKeyHash(pubKeyHash, NetParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkScript, err := txscript.PayToAddrScript(addrScript)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pkScript, privKey, nil
|
||||
}
|
||||
|
||||
// GetTestTxidAndScript generate a new test transaction and returns its txid and
|
||||
// the script of the output being generated.
|
||||
func GetTestTxidAndScript(h *rpctest.Harness) (*chainhash.Hash, []byte, error) {
|
||||
pkScript, _, err := randPubKeyHashScript()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to generate pkScript: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: pkScript}
|
||||
txid, err := h.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return txid, script, nil
|
||||
return txid, pkScript, nil
|
||||
}
|
||||
|
||||
// WaitForMempoolTx waits for the txid to be seen in the miner's mempool.
|
||||
|
@ -107,16 +116,18 @@ func WaitForMempoolTx(miner *rpctest.Harness, txid *chainhash.Hash) error {
|
|||
|
||||
// CreateSpendableOutput creates and returns an output that can be spent later
|
||||
// on.
|
||||
func CreateSpendableOutput(t *testing.T, miner *rpctest.Harness) (*wire.OutPoint, []byte) {
|
||||
func CreateSpendableOutput(t *testing.T,
|
||||
miner *rpctest.Harness) (*wire.OutPoint, *wire.TxOut, *btcec.PrivateKey) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Create a transaction that only has one output, the one destined for
|
||||
// the recipient.
|
||||
script, err := txscript.PayToAddrScript(testAddr)
|
||||
pkScript, privKey, err := randPubKeyHashScript()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create p2pkh script: %v", err)
|
||||
t.Fatalf("unable to generate pkScript: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: script}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: pkScript}
|
||||
txid, err := miner.SendOutputsWithoutChange([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create tx: %v", err)
|
||||
|
@ -130,19 +141,22 @@ func CreateSpendableOutput(t *testing.T, miner *rpctest.Harness) (*wire.OutPoint
|
|||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
|
||||
return wire.NewOutPoint(txid, 0), script
|
||||
return wire.NewOutPoint(txid, 0), output, privKey
|
||||
}
|
||||
|
||||
// CreateSpendTx creates a transaction spending the specified output.
|
||||
func CreateSpendTx(t *testing.T, outpoint *wire.OutPoint, pkScript []byte) *wire.MsgTx {
|
||||
func CreateSpendTx(t *testing.T, prevOutPoint *wire.OutPoint,
|
||||
prevOutput *wire.TxOut, privKey *btcec.PrivateKey) *wire.MsgTx {
|
||||
|
||||
t.Helper()
|
||||
|
||||
spendingTx := wire.NewMsgTx(1)
|
||||
spendingTx.AddTxIn(&wire.TxIn{PreviousOutPoint: *outpoint})
|
||||
spendingTx.AddTxOut(&wire.TxOut{Value: 1e8, PkScript: pkScript})
|
||||
spendingTx.AddTxIn(&wire.TxIn{PreviousOutPoint: *prevOutPoint})
|
||||
spendingTx.AddTxOut(&wire.TxOut{Value: 1e8, PkScript: prevOutput.PkScript})
|
||||
|
||||
sigScript, err := txscript.SignatureScript(
|
||||
spendingTx, 0, pkScript, txscript.SigHashAll, privKey, true,
|
||||
spendingTx, 0, prevOutput.PkScript, txscript.SigHashAll,
|
||||
privKey, true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign tx: %v", err)
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -256,12 +256,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
|||
// Next we'll create the instances of the ChainNotifier and
|
||||
// FilteredChainView interface which is backed by the neutrino
|
||||
// light client.
|
||||
cc.chainNotifier, err = neutrinonotify.New(
|
||||
svc, hintCache, hintCache,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cc.chainNotifier = neutrinonotify.New(svc, hintCache, hintCache)
|
||||
cc.chainView, err = chainview.NewCfFilteredChainView(svc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -336,7 +331,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
|||
}
|
||||
|
||||
cc.chainNotifier = bitcoindnotify.New(
|
||||
bitcoindConn, hintCache, hintCache,
|
||||
bitcoindConn, activeNetParams.Params, hintCache, hintCache,
|
||||
)
|
||||
cc.chainView = chainview.NewBitcoindFilteredChainView(bitcoindConn)
|
||||
walletConfig.ChainSource = bitcoindConn.NewBitcoindClient()
|
||||
|
@ -446,7 +441,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
|||
DisableAutoReconnect: false,
|
||||
}
|
||||
cc.chainNotifier, err = btcdnotify.New(
|
||||
rpcConfig, hintCache, hintCache,
|
||||
rpcConfig, activeNetParams.Params, hintCache, hintCache,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
12
go.mod
12
go.mod
|
@ -7,19 +7,17 @@ require (
|
|||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/btcsuite/btcd v0.0.0-20180824064422-7d2daa5bfef28c5e282571bc06416516936115ee
|
||||
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60ae4834a1f57511e20c17b5f78be4b
|
||||
github.com/btcsuite/btcwallet v0.0.0-20181130221647-e59e51f8e13c
|
||||
github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589
|
||||
github.com/btcsuite/btcwallet v0.0.0-20190115024521-9ad115360b37
|
||||
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941
|
||||
github.com/btcsuite/goleveldb v1.0.0 // indirect
|
||||
github.com/coreos/bbolt v0.0.0-20180223184059-7ee3ded59d4835e10f3e7d0f7603c42aa5e83820
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fsnotify/fsnotify v1.4.7 // indirect
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/golang/protobuf v1.2.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/jackpal/gateway v1.0.4
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad
|
||||
github.com/jessevdk/go-flags v0.0.0-20170926144705-f88afde2fa19
|
||||
|
@ -32,7 +30,7 @@ require (
|
|||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect
|
||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
|
||||
github.com/lightninglabs/neutrino v0.0.0-20181130220745-8d09312ac266
|
||||
github.com/lightninglabs/neutrino v0.0.0-20190115022559-351f5f06c6af
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8
|
||||
|
@ -47,9 +45,7 @@ require (
|
|||
google.golang.org/genproto v0.0.0-20181127195345-31ac5d88444a
|
||||
google.golang.org/grpc v1.16.0
|
||||
gopkg.in/errgo.v1 v1.0.0 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1
|
||||
gopkg.in/macaroon.v2 v2.0.0
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
|
25
go.sum
25
go.sum
|
@ -14,16 +14,17 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA
|
|||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/btcsuite/btcd v0.0.0-20180823030728-d81d8877b8f3/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
|
||||
github.com/btcsuite/btcd v0.0.0-20180824064422-7d2daa5bfef28c5e282571bc06416516936115ee h1:YSiTy2Hn6a5TaPnmcyk0+OTc13E1rbmCCgo/J0LDxtA=
|
||||
github.com/btcsuite/btcd v0.0.0-20180824064422-7d2daa5bfef28c5e282571bc06416516936115ee/go.mod h1:Jr9bmNVGZ7TH2Ux1QuP0ec+yGgh0gE9FIlkzQiI5bR0=
|
||||
github.com/btcsuite/btcd v0.0.0-20180824064422-ed77733ec07dfc8a513741138419b8d9d3de9d2d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
|
||||
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw=
|
||||
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60ae4834a1f57511e20c17b5f78be4b h1:sa+k743hEDlacUmuzqYlpxx4gp61C9Cf531bPaOneWo=
|
||||
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60ae4834a1f57511e20c17b5f78be4b/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589 h1:9A5pe5iQS+ll6R1EVLFv/y92IjrymihwITCU81aCIBQ=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcwallet v0.0.0-20180904010540-284e2e0e696e33d5be388f7f3d9a26db703e0c06/go.mod h1:/d7QHZsfUAruXuBhyPITqoYOmJ+nq35qPsJjz/aSpCg=
|
||||
github.com/btcsuite/btcwallet v0.0.0-20181130221647-e59e51f8e13c h1:k39UlSgW6cR0cIEleF9Or3S4uSP7NgvRLPd/WNYScbU=
|
||||
github.com/btcsuite/btcwallet v0.0.0-20181130221647-e59e51f8e13c/go.mod h1:mHSlZHtkxbCzvqKCzkQOSDb7Lc5iqoD8+pj6qc0yDBU=
|
||||
github.com/btcsuite/btcwallet v0.0.0-20190115024521-9ad115360b37 h1:f8RY/x01F18bYrOvekIUDLqm9oGZFrim+wx5Z0ts/Kg=
|
||||
github.com/btcsuite/btcwallet v0.0.0-20190115024521-9ad115360b37/go.mod h1:+u1ftn+QOb9qHKwsLf7rBOr0PHCo9CGA7U1WFq7VLA4=
|
||||
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
|
||||
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
|
@ -95,9 +96,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/lightninglabs/gozmq v0.0.0-20180324010646-462a8a753885 h1:fTLuPUkaKIIV0+gA1IxiBDvDxtF8tzpSF6N6NfFGmsU=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20180324010646-462a8a753885/go.mod h1:KUh15naRlx/TmUMFS/p4JJrCrE6F7RGF7rsnvuu45E4=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20181017011010-4d6069299130/go.mod h1:KJq43Fu9ceitbJsSXMILcT4mGDNI/crKmPIkDOZXFyM=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20181017011010-8d09312ac266916a00d367abeaf05fbd8bccf5d8/go.mod h1:/ie0+o5CLo6NDM52EpoMAJsMPq25DqpK1APv6MzZj7U=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20181130220745-8d09312ac266 h1:2WHiUpriTMc4cLWvS4syzNHLS9+hPVLJjNeZuhq5HR4=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20181130220745-8d09312ac266/go.mod h1:/ie0+o5CLo6NDM52EpoMAJsMPq25DqpK1APv6MzZj7U=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20190115022559-351f5f06c6af h1:JzoYbWqwPb+PARU4LTtlohetdNa6/ocyQ0xidZQw4Hg=
|
||||
github.com/lightninglabs/neutrino v0.0.0-20190115022559-351f5f06c6af/go.mod h1:aR+E6cs+FTaIwIa/WLyvNsB8FZg8TiP3r0Led+4Q4gI=
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6 h1:ONLGrYJVQdbtP6CE/ff1KNWZtygRGEh12RzonTiCzPs=
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6/go.mod h1:8EgEt4a/NUOVQd+3kk6n9aZCJ1Ssj96Pb6lCrci+6oc=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
||||
|
@ -108,8 +108,12 @@ github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KV
|
|||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
||||
|
@ -126,6 +130,7 @@ golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
golang.org/x/net v0.0.0-20180821023952-922f4815f713/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953 h1:LuZIitY8waaxUfNIdtajyE/YzA/zyf0YxXG27VpLrkg=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -135,6 +140,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTm
|
|||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
|
|
459
lnrpc/chainrpc/chainnotifer_server.go
Normal file
459
lnrpc/chainrpc/chainnotifer_server.go
Normal file
|
@ -0,0 +1,459 @@
|
|||
// +build chainrpc
|
||||
|
||||
package chainrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"google.golang.org/grpc"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
const (
|
||||
// subServerName is the name of the RPC sub-server. We'll use this name
|
||||
// to register ourselves, and we also require that the main
|
||||
// SubServerConfigDispatcher instance recognize this as the name of the
|
||||
// config file that we need.
|
||||
subServerName = "ChainRPC"
|
||||
)
|
||||
|
||||
var (
|
||||
// macaroonOps are the set of capabilities that our minted macaroon (if
|
||||
// it doesn't already exist) will have.
|
||||
macaroonOps = []bakery.Op{
|
||||
{
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
},
|
||||
}
|
||||
|
||||
// macPermissions maps RPC calls to the permissions they require.
|
||||
macPermissions = map[string][]bakery.Op{
|
||||
"/chainrpc.ChainNotifier/RegisterConfirmationsNtfn": {{
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/chainrpc.ChainNotifier/RegisterSpendNtfn": {{
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/chainrpc.ChainNotifier/RegisterBlockEpochNtfn": {{
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
|
||||
// DefaultChainNotifierMacFilename is the default name of the chain
|
||||
// notifier macaroon that we expect to find via a file handle within the
|
||||
// main configuration file in this package.
|
||||
DefaultChainNotifierMacFilename = "chainnotifier.macaroon"
|
||||
|
||||
// ErrChainNotifierServerShuttingDown is an error returned when we are
|
||||
// waiting for a notification to arrive but the chain notifier server
|
||||
// has been shut down.
|
||||
ErrChainNotifierServerShuttingDown = errors.New("chain notifier RPC " +
|
||||
"subserver shutting down")
|
||||
)
|
||||
|
||||
// fileExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Server is a sub-server of the main RPC server: the chain notifier RPC. This
|
||||
// RPC sub-server allows external callers to access the full chain notifier
|
||||
// capabilities of lnd. This allows callers to create custom protocols, external
|
||||
// to lnd, even backed by multiple distinct lnd across independent failure
|
||||
// domains.
|
||||
type Server struct {
|
||||
started uint32
|
||||
stopped uint32
|
||||
|
||||
cfg Config
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// New returns a new instance of the chainrpc ChainNotifier sub-server. We also
|
||||
// return the set of permissions for the macaroons that we may create within
|
||||
// this method. If the macaroons we need aren't found in the filepath, then
|
||||
// we'll create them on start up. If we're unable to locate, or create the
|
||||
// macaroons we need, then we'll return with an error.
|
||||
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
||||
// If the path of the chain notifier macaroon wasn't generated, then
|
||||
// we'll assume that it's found at the default network directory.
|
||||
if cfg.ChainNotifierMacPath == "" {
|
||||
cfg.ChainNotifierMacPath = filepath.Join(
|
||||
cfg.NetworkDir, DefaultChainNotifierMacFilename,
|
||||
)
|
||||
}
|
||||
|
||||
// Now that we know the full path of the chain notifier macaroon, we can
|
||||
// check to see if we need to create it or not.
|
||||
macFilePath := cfg.ChainNotifierMacPath
|
||||
if cfg.MacService != nil && !fileExists(macFilePath) {
|
||||
log.Infof("Baking macaroons for ChainNotifier RPC Server at: %v",
|
||||
macFilePath)
|
||||
|
||||
// At this point, we know that the chain notifier macaroon
|
||||
// doesn't yet, exist, so we need to create it with the help of
|
||||
// the main macaroon service.
|
||||
chainNotifierMac, err := cfg.MacService.Oven.NewMacaroon(
|
||||
context.Background(), bakery.LatestVersion, nil,
|
||||
macaroonOps...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
chainNotifierMacBytes, err := chainNotifierMac.M().MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = ioutil.WriteFile(macFilePath, chainNotifierMacBytes, 0644)
|
||||
if err != nil {
|
||||
os.Remove(macFilePath)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Server{
|
||||
cfg: *cfg,
|
||||
quit: make(chan struct{}),
|
||||
}, macPermissions, nil
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure that Server fully implements the
|
||||
// ChainNotifierServer gRPC service and lnrpc.SubServer interface.
|
||||
var _ ChainNotifierServer = (*Server)(nil)
|
||||
var _ lnrpc.SubServer = (*Server)(nil)
|
||||
|
||||
// Start launches any helper goroutines required for the server to function.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Start() error {
|
||||
if !atomic.CompareAndSwapUint32(&s.started, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop signals any active goroutines for a graceful closure.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Stop() error {
|
||||
if !atomic.CompareAndSwapUint32(&s.stopped, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
close(s.quit)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns a unique string representation of the sub-server. This can be
|
||||
// used to identify the sub-server and also de-duplicate them.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Name() string {
|
||||
return subServerName
|
||||
}
|
||||
|
||||
// RegisterWithRootServer will be called by the root gRPC server to direct a RPC
|
||||
// sub-server to register itself with the main gRPC root server. Until this is
|
||||
// called, each sub-server won't be able to have requests routed towards it.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
|
||||
// We make sure that we register it with the main gRPC server to ensure
|
||||
// all our methods are routed properly.
|
||||
RegisterChainNotifierServer(grpcServer, s)
|
||||
|
||||
log.Debug("ChainNotifier RPC server successfully register with root " +
|
||||
"gRPC server")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
// registers an intent for a client to be notified once a confirmation request
|
||||
// has reached its required number of confirmations on-chain.
|
||||
//
|
||||
// A client can specify whether the confirmation request should be for a
|
||||
// particular transaction by its hash or for an output script by specifying a
|
||||
// zero hash.
|
||||
//
|
||||
// NOTE: This is part of the chainrpc.ChainNotifierService interface.
|
||||
func (s *Server) RegisterConfirmationsNtfn(in *ConfRequest,
|
||||
confStream ChainNotifier_RegisterConfirmationsNtfnServer) error {
|
||||
|
||||
// We'll start by reconstructing the RPC request into what the
|
||||
// underlying ChainNotifier expects.
|
||||
var txid chainhash.Hash
|
||||
copy(txid[:], in.Txid)
|
||||
|
||||
// We'll then register for the spend notification of the request.
|
||||
confEvent, err := s.cfg.ChainNotifier.RegisterConfirmationsNtfn(
|
||||
&txid, in.Script, in.NumConfs, in.HeightHint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer confEvent.Cancel()
|
||||
|
||||
// With the request registered, we'll wait for its spend notification to
|
||||
// be dispatched.
|
||||
for {
|
||||
select {
|
||||
// The transaction satisfying the request has confirmed on-chain
|
||||
// and reached its required number of confirmations. We'll
|
||||
// dispatch an event to the caller indicating so.
|
||||
case details, ok := <-confEvent.Confirmed:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
var rawTxBuf bytes.Buffer
|
||||
err := details.Tx.Serialize(&rawTxBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcConfDetails := &ConfDetails{
|
||||
RawTx: rawTxBuf.Bytes(),
|
||||
BlockHash: details.BlockHash[:],
|
||||
BlockHeight: details.BlockHeight,
|
||||
TxIndex: details.TxIndex,
|
||||
}
|
||||
|
||||
conf := &ConfEvent{
|
||||
Event: &ConfEvent_Conf{
|
||||
Conf: rpcConfDetails,
|
||||
},
|
||||
}
|
||||
if err := confStream.Send(conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The transaction satisfying the request has been reorged out
|
||||
// of the chain, so we'll send an event describing so.
|
||||
case _, ok := <-confEvent.NegativeConf:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
reorg := &ConfEvent{
|
||||
Event: &ConfEvent_Reorg{Reorg: &Reorg{}},
|
||||
}
|
||||
if err := confStream.Send(reorg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The transaction satisfying the request has confirmed and is
|
||||
// no longer under the risk of being reorged out of the chain,
|
||||
// so we can safely exit.
|
||||
case _, ok := <-confEvent.Done:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
// The response stream's context for whatever reason has been
|
||||
// closed. We'll return the error indicated by the context
|
||||
// itself to the caller.
|
||||
case <-confStream.Context().Done():
|
||||
return confStream.Context().Err()
|
||||
|
||||
// The server has been requested to shut down.
|
||||
case <-s.quit:
|
||||
return ErrChainNotifierServerShuttingDown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
// intent for a client to be notification once a spend request has been spent by
|
||||
// a transaction that has confirmed on-chain.
|
||||
//
|
||||
// A client can specify whether the spend request should be for a particular
|
||||
// outpoint or for an output script by specifying a zero outpoint.
|
||||
//
|
||||
// NOTE: This is part of the chainrpc.ChainNotifierService interface.
|
||||
func (s *Server) RegisterSpendNtfn(in *SpendRequest,
|
||||
spendStream ChainNotifier_RegisterSpendNtfnServer) error {
|
||||
|
||||
// We'll start by reconstructing the RPC request into what the
|
||||
// underlying ChainNotifier expects.
|
||||
var op *wire.OutPoint
|
||||
if in.Outpoint != nil {
|
||||
var txid chainhash.Hash
|
||||
copy(txid[:], in.Outpoint.Hash)
|
||||
op = &wire.OutPoint{Hash: txid, Index: in.Outpoint.Index}
|
||||
}
|
||||
|
||||
// We'll then register for the spend notification of the request.
|
||||
spendEvent, err := s.cfg.ChainNotifier.RegisterSpendNtfn(
|
||||
op, in.Script, in.HeightHint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer spendEvent.Cancel()
|
||||
|
||||
// With the request registered, we'll wait for its spend notification to
|
||||
// be dispatched.
|
||||
for {
|
||||
select {
|
||||
// A transaction that spends the given has confirmed on-chain.
|
||||
// We'll return an event to the caller indicating so that
|
||||
// includes the details of the spending transaction.
|
||||
case details, ok := <-spendEvent.Spend:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
var rawSpendingTxBuf bytes.Buffer
|
||||
err := details.SpendingTx.Serialize(&rawSpendingTxBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcSpendDetails := &SpendDetails{
|
||||
SpendingOutpoint: &Outpoint{
|
||||
Hash: details.SpentOutPoint.Hash[:],
|
||||
Index: details.SpentOutPoint.Index,
|
||||
},
|
||||
RawSpendingTx: rawSpendingTxBuf.Bytes(),
|
||||
SpendingTxHash: details.SpenderTxHash[:],
|
||||
SpendingInputIndex: details.SpenderInputIndex,
|
||||
SpendingHeight: uint32(details.SpendingHeight),
|
||||
}
|
||||
|
||||
spend := &SpendEvent{
|
||||
Event: &SpendEvent_Spend{
|
||||
Spend: rpcSpendDetails,
|
||||
},
|
||||
}
|
||||
if err := spendStream.Send(spend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The spending transaction of the request has been reorged of
|
||||
// the chain. We'll return an event to the caller indicating so.
|
||||
case _, ok := <-spendEvent.Reorg:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
reorg := &SpendEvent{
|
||||
Event: &SpendEvent_Reorg{Reorg: &Reorg{}},
|
||||
}
|
||||
if err := spendStream.Send(reorg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The spending transaction of the requests has confirmed
|
||||
// on-chain and is no longer under the risk of being reorged out
|
||||
// of the chain, so we can safely exit.
|
||||
case _, ok := <-spendEvent.Done:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
// The response stream's context for whatever reason has been
|
||||
// closed. We'll return the error indicated by the context
|
||||
// itself to the caller.
|
||||
case <-spendStream.Context().Done():
|
||||
return spendStream.Context().Err()
|
||||
|
||||
// The server has been requested to shut down.
|
||||
case <-s.quit:
|
||||
return ErrChainNotifierServerShuttingDown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn is a synchronous response-streaming RPC that registers
|
||||
// an intent for a client to be notified of blocks in the chain. The stream will
|
||||
// return a hash and height tuple of a block for each new/stale block in the
|
||||
// chain. It is the client's responsibility to determine whether the tuple
|
||||
// returned is for a new or stale block in the chain.
|
||||
//
|
||||
// A client can also request a historical backlog of blocks from a particular
|
||||
// point. This allows clients to be idempotent by ensuring that they do not
|
||||
// missing processing a single block within the chain.
|
||||
//
|
||||
// NOTE: This is part of the chainrpc.ChainNotifierService interface.
|
||||
func (s *Server) RegisterBlockEpochNtfn(in *BlockEpoch,
|
||||
epochStream ChainNotifier_RegisterBlockEpochNtfnServer) error {
|
||||
|
||||
// We'll start by reconstructing the RPC request into what the
|
||||
// underlying ChainNotifier expects.
|
||||
var hash chainhash.Hash
|
||||
copy(hash[:], in.Hash)
|
||||
|
||||
// If the request isn't for a zero hash and a zero height, then we
|
||||
// should deliver a backlog of notifications from the given block
|
||||
// (hash/height tuple) until tip, and continue delivering epochs for
|
||||
// new blocks.
|
||||
var blockEpoch *chainntnfs.BlockEpoch
|
||||
if hash != chainntnfs.ZeroHash && in.Height != 0 {
|
||||
blockEpoch = &chainntnfs.BlockEpoch{
|
||||
Hash: &hash,
|
||||
Height: int32(in.Height),
|
||||
}
|
||||
}
|
||||
|
||||
epochEvent, err := s.cfg.ChainNotifier.RegisterBlockEpochNtfn(blockEpoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer epochEvent.Cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
// A notification for a block has been received. This block can
|
||||
// either be a new block or stale.
|
||||
case blockEpoch, ok := <-epochEvent.Epochs:
|
||||
if !ok {
|
||||
return chainntnfs.ErrChainNotifierShuttingDown
|
||||
}
|
||||
|
||||
epoch := &BlockEpoch{
|
||||
Hash: blockEpoch.Hash[:],
|
||||
Height: uint32(blockEpoch.Height),
|
||||
}
|
||||
if err := epochStream.Send(epoch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The response stream's context for whatever reason has been
|
||||
// closed. We'll return the error indicated by the context
|
||||
// itself to the caller.
|
||||
case <-epochStream.Context().Done():
|
||||
return epochStream.Context().Err()
|
||||
|
||||
// The server has been requested to shut down.
|
||||
case <-s.quit:
|
||||
return ErrChainNotifierServerShuttingDown
|
||||
}
|
||||
}
|
||||
}
|
1050
lnrpc/chainrpc/chainnotifier.pb.go
Normal file
1050
lnrpc/chainrpc/chainnotifier.pb.go
Normal file
File diff suppressed because it is too large
Load diff
177
lnrpc/chainrpc/chainnotifier.proto
Normal file
177
lnrpc/chainrpc/chainnotifier.proto
Normal file
|
@ -0,0 +1,177 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chainrpc;
|
||||
|
||||
message ConfRequest {
|
||||
/*
|
||||
The transaction hash for which we should request a confirmation notification
|
||||
for. If set to a hash of all zeros, then the confirmation notification will
|
||||
be requested for the script instead.
|
||||
*/
|
||||
bytes txid = 1;
|
||||
|
||||
/*
|
||||
An output script within a transaction with the hash above which will be used
|
||||
by light clients to match block filters. If the transaction hash is set to a
|
||||
hash of all zeros, then a confirmation notification will be requested for
|
||||
this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The number of desired confirmations the transaction/output script should
|
||||
reach before dispatching a confirmation notification.
|
||||
*/
|
||||
uint32 num_confs = 3;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the transaction/output script
|
||||
could have been included in a block. This should in most cases be set to the
|
||||
broadcast height of the transaction/output script.
|
||||
*/
|
||||
uint32 height_hint = 4;
|
||||
}
|
||||
|
||||
message ConfDetails {
|
||||
// The raw bytes of the confirmed transaction.
|
||||
bytes raw_tx = 1;
|
||||
|
||||
// The hash of the block in which the confirmed transaction was included in.
|
||||
bytes block_hash = 2;
|
||||
|
||||
// The height of the block in which the confirmed transaction was included in.
|
||||
uint32 block_height = 3;
|
||||
|
||||
// The index of the confirmed transaction within the transaction.
|
||||
uint32 tx_index = 4;
|
||||
}
|
||||
|
||||
message Reorg {
|
||||
// TODO(wilmer): need to know how the client will use this first.
|
||||
}
|
||||
|
||||
message ConfEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the confirmation details of the request
|
||||
(txid/ouput script).
|
||||
*/
|
||||
ConfDetails conf = 1;
|
||||
|
||||
/*
|
||||
An event send when the transaction of the request is reorged out of the
|
||||
chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Outpoint {
|
||||
// The hash of the transaction.
|
||||
bytes hash = 1;
|
||||
|
||||
// The index of the output within the transaction.
|
||||
uint32 index = 2;
|
||||
}
|
||||
|
||||
message SpendRequest {
|
||||
/*
|
||||
The outpoint for which we should request a spend notification for. If set to
|
||||
a zero outpoint, then the spend notification will be requested for the
|
||||
script instead.
|
||||
*/
|
||||
Outpoint outpoint = 1;
|
||||
|
||||
/*
|
||||
The output script for the outpoint above. This will be used by light clients
|
||||
to match block filters. If the outpoint is set to a zero outpoint, then a
|
||||
spend notification will be requested for this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the outpoint/output script could
|
||||
have been spent. This should in most cases be set to the broadcast height of
|
||||
the outpoint/output script.
|
||||
*/
|
||||
uint32 height_hint = 3;
|
||||
|
||||
// TODO(wilmer): extend to support num confs on spending tx.
|
||||
}
|
||||
|
||||
message SpendDetails {
|
||||
// The outpoint was that spent.
|
||||
Outpoint spending_outpoint = 1;
|
||||
|
||||
// The raw bytes of the spending transaction.
|
||||
bytes raw_spending_tx = 2;
|
||||
|
||||
// The hash of the spending transaction.
|
||||
bytes spending_tx_hash = 3;
|
||||
|
||||
// The input of the spending transaction that fulfilled the spend request.
|
||||
uint32 spending_input_index = 4;
|
||||
|
||||
// The height at which the spending transaction was included in a block.
|
||||
uint32 spending_height = 5;
|
||||
}
|
||||
|
||||
message SpendEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the details of the spending transaction of the
|
||||
request (outpoint/output script).
|
||||
*/
|
||||
SpendDetails spend = 1;
|
||||
|
||||
/*
|
||||
An event sent when the spending transaction of the request was
|
||||
reorged out of the chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message BlockEpoch {
|
||||
// The hash of the block.
|
||||
bytes hash = 1;
|
||||
|
||||
// The height of the block.
|
||||
uint32 height = 2;
|
||||
}
|
||||
|
||||
service ChainNotifier {
|
||||
/*
|
||||
RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified once a confirmation request
|
||||
has reached its required number of confirmations on-chain.
|
||||
|
||||
A client can specify whether the confirmation request should be for a
|
||||
particular transaction by its hash or for an output script by specifying a
|
||||
zero hash.
|
||||
*/
|
||||
rpc RegisterConfirmationsNtfn(ConfRequest) returns (stream ConfEvent);
|
||||
|
||||
/*
|
||||
RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
intent for a client to be notification once a spend request has been spent
|
||||
by a transaction that has confirmed on-chain.
|
||||
|
||||
A client can specify whether the spend request should be for a particular
|
||||
outpoint or for an output script by specifying a zero outpoint.
|
||||
*/
|
||||
rpc RegisterSpendNtfn(SpendRequest) returns (stream SpendEvent);
|
||||
|
||||
/*
|
||||
RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified of blocks in the chain. The
|
||||
stream will return a hash and height tuple of a block for each new/stale
|
||||
block in the chain. It is the client's responsibility to determine whether
|
||||
the tuple returned is for a new or stale block in the chain.
|
||||
|
||||
A client can also request a historical backlog of blocks from a particular
|
||||
point. This allows clients to be idempotent by ensuring that they do not
|
||||
missing processing a single block within the chain.
|
||||
*/
|
||||
rpc RegisterBlockEpochNtfn(BlockEpoch) returns (stream BlockEpoch);
|
||||
}
|
34
lnrpc/chainrpc/config_active.go
Normal file
34
lnrpc/chainrpc/config_active.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
// +build chainrpc
|
||||
|
||||
package chainrpc
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
)
|
||||
|
||||
// Config is the primary configuration struct for the chain notifier RPC server.
|
||||
// It contains all the items required for the server to carry out its duties.
|
||||
// The fields with struct tags are meant to be parsed as normal configuration
|
||||
// options, while if able to be populated, the latter fields MUST also be
|
||||
// specified.
|
||||
type Config struct {
|
||||
// ChainNotifierMacPath is the path for the chain notifier macaroon. If
|
||||
// unspecified then we assume that the macaroon will be found under the
|
||||
// network directory, named DefaultChainNotifierMacFilename.
|
||||
ChainNotifierMacPath string `long:"notifiermacaroonpath" description:"Path to the chain notifier macaroon"`
|
||||
|
||||
// NetworkDir is the main network directory wherein the chain notifier
|
||||
// RPC server will find the macaroon named
|
||||
// DefaultChainNotifierMacFilename.
|
||||
NetworkDir string
|
||||
|
||||
// MacService is the main macaroon service that we'll use to handle
|
||||
// authentication for the chain notifier RPC server.
|
||||
MacService *macaroons.Service
|
||||
|
||||
// ChainNotifier is the chain notifier instance that backs the chain
|
||||
// notifier RPC server. The job of the chain notifier RPC server is
|
||||
// simply to proxy valid requests to the active chain notifier instance.
|
||||
ChainNotifier chainntnfs.ChainNotifier
|
||||
}
|
6
lnrpc/chainrpc/config_default.go
Normal file
6
lnrpc/chainrpc/config_default.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
// +build !chainrpc
|
||||
|
||||
package chainrpc
|
||||
|
||||
// Config is empty for non-chainrpc builds.
|
||||
type Config struct{}
|
71
lnrpc/chainrpc/driver.go
Normal file
71
lnrpc/chainrpc/driver.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
// +build chainrpc
|
||||
|
||||
package chainrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
)
|
||||
|
||||
// createNewSubServer is a helper method that will create the new chain notifier
|
||||
// sub server given the main config dispatcher method. If we're unable to find
|
||||
// the config that is meant for us in the config dispatcher, then we'll exit
|
||||
// with an error.
|
||||
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
|
||||
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
|
||||
|
||||
// We'll attempt to look up the config that we expect, according to our
|
||||
// subServerName name. If we can't find this, then we'll exit with an
|
||||
// error, as we're unable to properly initialize ourselves without this
|
||||
// config.
|
||||
chainNotifierServerConf, ok := configRegistry.FetchConfig(subServerName)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("unable to find config for "+
|
||||
"subserver type %s", subServerName)
|
||||
}
|
||||
|
||||
// Now that we've found an object mapping to our service name, we'll
|
||||
// ensure that it's the type we need.
|
||||
config, ok := chainNotifierServerConf.(*Config)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("wrong type of config for "+
|
||||
"subserver %s, expected %T got %T", subServerName,
|
||||
&Config{}, chainNotifierServerConf)
|
||||
}
|
||||
|
||||
// Before we try to make the new chain notifier service instance, we'll
|
||||
// perform some sanity checks on the arguments to ensure that they're
|
||||
// usable.
|
||||
switch {
|
||||
// If the macaroon service is set (we should use macaroons), then
|
||||
// ensure that we know where to look for them, or create them if not
|
||||
// found.
|
||||
case config.MacService != nil && config.NetworkDir == "":
|
||||
return nil, nil, fmt.Errorf("NetworkDir must be set to create " +
|
||||
"chainrpc")
|
||||
case config.ChainNotifier == nil:
|
||||
return nil, nil, fmt.Errorf("ChainNotifier must be set to " +
|
||||
"create chainrpc")
|
||||
}
|
||||
|
||||
return New(config)
|
||||
}
|
||||
|
||||
func init() {
|
||||
subServer := &lnrpc.SubServerDriver{
|
||||
SubServerName: subServerName,
|
||||
New: func(c lnrpc.SubServerConfigDispatcher) (
|
||||
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
|
||||
|
||||
return createNewSubServer(c)
|
||||
},
|
||||
}
|
||||
|
||||
// If the build tag is active, then we'll register ourselves as a
|
||||
// sub-RPC server within the global lnrpc package namespace.
|
||||
if err := lnrpc.RegisterSubServer(subServer); err != nil {
|
||||
panic(fmt.Sprintf("failed to register subserver driver %s: %v",
|
||||
subServerName, err))
|
||||
}
|
||||
}
|
45
lnrpc/chainrpc/log.go
Normal file
45
lnrpc/chainrpc/log.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package chainrpc
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger("NTFR", nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
// logClosure is used to provide a closure over expensive logging operations so
|
||||
// don't have to be performed when the logging level doesn't warrant it.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the underlying function and returns the result.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over a function that returns a string
|
||||
// which itself provides a Stringer interface so that it can be used with the
|
||||
// logging system.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
|
@ -2362,7 +2362,9 @@ func TestLightningWallet(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("unable to create height hint cache: %v", err)
|
||||
}
|
||||
chainNotifier, err := btcdnotify.New(&rpcConfig, hintCache, hintCache)
|
||||
chainNotifier, err := btcdnotify.New(
|
||||
&rpcConfig, netParams, hintCache, hintCache,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create notifier: %v", err)
|
||||
}
|
||||
|
|
5
log.go
5
log.go
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/btcsuite/btclog"
|
||||
"github.com/jrick/logrotate/rotator"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
|
||||
"github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
|
@ -21,6 +20,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
@ -77,6 +77,7 @@ var (
|
|||
invcLog = build.NewSubLogger("INVC", backendLog.Logger)
|
||||
nannLog = build.NewSubLogger("NANN", backendLog.Logger)
|
||||
wtwrLog = build.NewSubLogger("WTWR", backendLog.Logger)
|
||||
ntfrLog = build.NewSubLogger("NTFR", backendLog.Logger)
|
||||
)
|
||||
|
||||
// Initialize package-global logger variables.
|
||||
|
@ -100,6 +101,7 @@ func init() {
|
|||
invoices.UseLogger(invcLog)
|
||||
netann.UseLogger(nannLog)
|
||||
watchtower.UseLogger(wtwrLog)
|
||||
chainrpc.UseLogger(ntfrLog)
|
||||
}
|
||||
|
||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||
|
@ -129,6 +131,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
|||
"INVC": invcLog,
|
||||
"NANN": nannLog,
|
||||
"WTWR": wtwrLog,
|
||||
"NTFR": ntfnLog,
|
||||
}
|
||||
|
||||
// initLogRotator initializes the logging rotator to write logs to logFile and
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
|
@ -31,6 +32,11 @@ type subRPCServerConfigs struct {
|
|||
// AutopilotRPC is a sub-RPC server that exposes methods on the running
|
||||
// autopilot as a gRPC service.
|
||||
AutopilotRPC *autopilotrpc.Config `group:"autopilotrpc" namespace:"autopilotrpc"`
|
||||
|
||||
// ChainRPC is a sub-RPC server that exposes functionality allowing a
|
||||
// client to be notified of certain on-chain events (new blocks,
|
||||
// confirmations, spends).
|
||||
ChainRPC *chainrpc.Config `group:"chainrpc" namespace:"chainrpc"`
|
||||
}
|
||||
|
||||
// PopulateDependencies attempts to iterate through all the sub-server configs
|
||||
|
@ -106,6 +112,19 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
|
|||
reflect.ValueOf(atpl),
|
||||
)
|
||||
|
||||
case *chainrpc.Config:
|
||||
subCfgValue := extractReflectValue(cfg)
|
||||
|
||||
subCfgValue.FieldByName("NetworkDir").Set(
|
||||
reflect.ValueOf(networkDir),
|
||||
)
|
||||
subCfgValue.FieldByName("MacService").Set(
|
||||
reflect.ValueOf(macService),
|
||||
)
|
||||
subCfgValue.FieldByName("ChainNotifier").Set(
|
||||
reflect.ValueOf(cc.chainNotifier),
|
||||
)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown field: %v, %T", fieldName,
|
||||
cfg)
|
||||
|
|
Loading…
Add table
Reference in a new issue