You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2040 lines
60 KiB
2040 lines
60 KiB
// +build dev |
|
|
|
package chainntnfstest |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io/ioutil" |
|
"log" |
|
"sync" |
|
"testing" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/integration/rpctest" |
|
"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/chainntnfs/bitcoindnotify" |
|
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" |
|
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" |
|
"github.com/lightningnetwork/lnd/channeldb" |
|
) |
|
|
|
func testSingleConfirmationNotification(miner *rpctest.Harness, |
|
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. |
|
// |
|
// So first, let's send some coins to "ourself", obtaining a txid. |
|
// We're spending from a coinbase output here, so we use the dedicated |
|
// function. |
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Now that we have a txid, register a confirmation notification with |
|
// the chainntfn source. |
|
numConfs := uint32(1) |
|
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) |
|
} |
|
|
|
// Now generate a single block, the transaction should be included which |
|
// should trigger a notification event. |
|
blockHash, err := miner.Client.Generate(1) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
select { |
|
case confInfo := <-confIntent.Confirmed: |
|
if !confInfo.BlockHash.IsEqual(blockHash[0]) { |
|
t.Fatalf("mismatched block hashes: expected %v, got %v", |
|
blockHash[0], confInfo.BlockHash) |
|
} |
|
|
|
// Finally, we'll verify that the tx index returned is the exact same |
|
// as the tx index of the transaction within the block itself. |
|
msgBlock, err := miner.Client.GetBlock(blockHash[0]) |
|
if err != nil { |
|
t.Fatalf("unable to fetch block: %v", err) |
|
} |
|
|
|
block := btcutil.NewBlock(msgBlock) |
|
specifiedTxHash, err := block.TxHash(int(confInfo.TxIndex)) |
|
if err != nil { |
|
t.Fatalf("unable to index into block: %v", err) |
|
} |
|
|
|
if !specifiedTxHash.IsEqual(txid) { |
|
t.Fatalf("mismatched tx indexes: expected %v, got %v", |
|
txid, specifiedTxHash) |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
} |
|
|
|
func testMultiConfirmationNotification(miner *rpctest.Harness, |
|
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. |
|
// |
|
// Again, we'll begin by creating a fresh transaction, so we can obtain |
|
// a fresh txid. |
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test addr: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
numConfs := uint32(6) |
|
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) |
|
} |
|
|
|
// Now generate a six blocks. The transaction should be included in the |
|
// first block, which will be built upon by the other 5 blocks. |
|
if _, err := miner.Client.Generate(6); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// TODO(roasbeef): reduce all timeouts after neutrino sync tightended |
|
// up |
|
|
|
select { |
|
case <-confIntent.Confirmed: |
|
break |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
} |
|
|
|
func testBatchConfirmationNotification(miner *rpctest.Harness, |
|
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 |
|
// various numbers of confirmations. |
|
confSpread := [6]uint32{1, 2, 3, 6, 20, 22} |
|
confIntents := make([]*chainntnfs.ConfirmationEvent, len(confSpread)) |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Create a new txid spending miner coins for each confirmation entry |
|
// in confSpread, we collect each conf intent into a slice so we can |
|
// verify they're each notified at the proper number of confirmations |
|
// below. |
|
for i, numConfs := range confSpread { |
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test addr: %v", err) |
|
} |
|
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) |
|
} |
|
confIntents[i] = confIntent |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
} |
|
|
|
initialConfHeight := uint32(currentHeight + 1) |
|
|
|
// Now, for each confirmation intent, generate the delta number of blocks |
|
// needed to trigger the confirmation notification. A goroutine is |
|
// spawned in order to verify the proper notification is triggered. |
|
for i, numConfs := range confSpread { |
|
var blocksToGen uint32 |
|
|
|
// If this is the last instance, manually index to generate the |
|
// proper block delta in order to avoid a panic. |
|
if i == len(confSpread)-1 { |
|
blocksToGen = confSpread[len(confSpread)-1] - confSpread[len(confSpread)-2] |
|
} else { |
|
blocksToGen = confSpread[i+1] - confSpread[i] |
|
} |
|
|
|
// Generate the number of blocks necessary to trigger this |
|
// current confirmation notification. |
|
if _, err := miner.Client.Generate(blocksToGen); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
select { |
|
case conf := <-confIntents[i].Confirmed: |
|
// All of the notifications above were originally |
|
// confirmed in the same block. The returned |
|
// notification should list the initial confirmation |
|
// height rather than the height they were _fully_ |
|
// confirmed. |
|
if conf.BlockHeight != initialConfHeight { |
|
t.Fatalf("notification has incorrect initial "+ |
|
"conf height: expected %v, got %v", |
|
initialConfHeight, conf.BlockHeight) |
|
} |
|
continue |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("confirmation notification never received: %v", numConfs) |
|
} |
|
} |
|
} |
|
|
|
func checkNotificationFields(ntfn *chainntnfs.SpendDetail, |
|
outpoint *wire.OutPoint, spenderSha *chainhash.Hash, |
|
height int32, t *testing.T) { |
|
|
|
t.Helper() |
|
|
|
if *ntfn.SpentOutPoint != *outpoint { |
|
t.Fatalf("ntfn includes wrong output, reports "+ |
|
"%v instead of %v", |
|
ntfn.SpentOutPoint, outpoint) |
|
} |
|
if !bytes.Equal(ntfn.SpenderTxHash[:], spenderSha[:]) { |
|
t.Fatalf("ntfn includes wrong spender tx sha, "+ |
|
"reports %v instead of %v", |
|
ntfn.SpenderTxHash[:], spenderSha[:]) |
|
} |
|
if ntfn.SpenderInputIndex != 0 { |
|
t.Fatalf("ntfn includes wrong spending input "+ |
|
"index, reports %v, should be %v", |
|
ntfn.SpenderInputIndex, 0) |
|
} |
|
if ntfn.SpendingHeight != height { |
|
t.Fatalf("ntfn has wrong spending height: "+ |
|
"expected %v, got %v", height, |
|
ntfn.SpendingHeight) |
|
} |
|
} |
|
|
|
func testSpendNotification(miner *rpctest.Harness, |
|
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, output, privKey := chainntnfs.CreateSpendableOutput(t, miner) |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Now that we have an output index and the pkScript, register for a |
|
// spentness notification for the newly created output with multiple |
|
// clients in order to ensure the implementation can support |
|
// multi-client spend notifications. |
|
const numClients = 5 |
|
spendClients := make([]*chainntnfs.SpendEvent, numClients) |
|
for i := 0; i < numClients; i++ { |
|
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) |
|
} |
|
|
|
spendClients[i] = spentIntent |
|
} |
|
|
|
// Next, create a new transaction spending that output. |
|
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey) |
|
|
|
// Broadcast our spending transaction. |
|
spenderSha, err := miner.Client.SendRawTransaction(spendingTx, true) |
|
if err != nil { |
|
t.Fatalf("unable to broadcast tx: %v", err) |
|
} |
|
|
|
if err := chainntnfs.WaitForMempoolTx(miner, spenderSha); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
// Make sure notifications are not yet sent. We launch a go routine for |
|
// all the spend clients, such that we can wait for them all in |
|
// parallel. |
|
mempoolSpendTimeout := 2 * chainntnfs.TrickleInterval |
|
mempoolSpends := make(chan *chainntnfs.SpendDetail, numClients) |
|
for _, c := range spendClients { |
|
go func(client *chainntnfs.SpendEvent) { |
|
select { |
|
case s := <-client.Spend: |
|
mempoolSpends <- s |
|
case <-time.After(mempoolSpendTimeout): |
|
} |
|
}(c) |
|
} |
|
|
|
select { |
|
case <-mempoolSpends: |
|
t.Fatalf("did not expect to get notification before " + |
|
"block was mined") |
|
case <-time.After(mempoolSpendTimeout): |
|
} |
|
|
|
// Make sure registering a client after the tx is in the mempool still |
|
// doesn't trigger a notification. |
|
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) |
|
} |
|
|
|
select { |
|
case <-spentIntent.Spend: |
|
t.Fatalf("did not expect to get notification before " + |
|
"block was mined") |
|
case <-time.After(mempoolSpendTimeout): |
|
} |
|
spendClients = append(spendClients, spentIntent) |
|
|
|
// Now we mine a single block, which should include our spend. The |
|
// notification should also be sent off. |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
_, currentHeight, err = miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
for _, c := range spendClients { |
|
select { |
|
case ntfn := <-c.Spend: |
|
// We've received the spend nftn. So now verify all the |
|
// fields have been set properly. |
|
checkNotificationFields(ntfn, outpoint, spenderSha, |
|
currentHeight, t) |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("spend ntfn never received") |
|
} |
|
} |
|
} |
|
|
|
func testBlockEpochNotification(miner *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, t *testing.T) { |
|
|
|
// We'd like to test the case of multiple registered clients receiving |
|
// 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 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(numNtfns) |
|
go func() { |
|
for i := 0; i < numNtfns; i++ { |
|
<-epochClient.Epochs |
|
wg.Done() |
|
} |
|
}() |
|
} |
|
|
|
epochsSent := make(chan struct{}) |
|
go func() { |
|
wg.Wait() |
|
close(epochsSent) |
|
}() |
|
|
|
// Now generate 10 blocks, the clients above should each receive 10 |
|
// notifications, thereby unblocking the goroutine above. |
|
if _, err := miner.Client.Generate(numBlocks); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
select { |
|
case <-epochsSent: |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("all notifications not sent") |
|
} |
|
} |
|
|
|
func testMultiClientConfirmationNotification(miner *rpctest.Harness, |
|
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. |
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
var wg sync.WaitGroup |
|
const ( |
|
numConfsClients = 5 |
|
numConfs = 1 |
|
) |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Register for a conf notification for the above generated txid with |
|
// numConfsClients distinct clients. |
|
for i := 0; i < numConfsClients; i++ { |
|
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) |
|
} |
|
|
|
wg.Add(1) |
|
go func() { |
|
<-confClient.Confirmed |
|
wg.Done() |
|
}() |
|
} |
|
|
|
confsSent := make(chan struct{}) |
|
go func() { |
|
wg.Wait() |
|
close(confsSent) |
|
}() |
|
|
|
// Finally, generate a single block which should trigger the unblocking |
|
// of all numConfsClients blocked on the channel read above. |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate block: %v", err) |
|
} |
|
|
|
select { |
|
case <-confsSent: |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("all confirmation notifications not sent") |
|
} |
|
} |
|
|
|
// Tests the case in which a confirmation notification is requested for a |
|
// 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, 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 |
|
// function. |
|
txid3, pkScript3, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid3); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
// Generate another block containing tx 3, but we won't register conf |
|
// notifications for this tx until much later. The notifier must check |
|
// older blocks when the confirmation event is registered below to ensure |
|
// that the TXID hasn't already been included in the chain, otherwise the |
|
// notification will never be sent. |
|
_, err = miner.Client.Generate(1) |
|
if err != nil { |
|
t.Fatalf("unable to generate block: %v", err) |
|
} |
|
|
|
txid1, pkScript1, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid1); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
txid2, pkScript2, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid2); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Now generate another block containing txs 1 & 2. |
|
blockHash, err := miner.Client.Generate(1) |
|
if err != nil { |
|
t.Fatalf("unable to generate block: %v", err) |
|
} |
|
|
|
// Register a confirmation notification with the chainntfn source for tx2, |
|
// 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. |
|
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) |
|
} |
|
|
|
select { |
|
case confInfo := <-ntfn1.Confirmed: |
|
// Finally, we'll verify that the tx index returned is the exact same |
|
// as the tx index of the transaction within the block itself. |
|
msgBlock, err := miner.Client.GetBlock(blockHash[0]) |
|
if err != nil { |
|
t.Fatalf("unable to fetch block: %v", err) |
|
} |
|
block := btcutil.NewBlock(msgBlock) |
|
specifiedTxHash, err := block.TxHash(int(confInfo.TxIndex)) |
|
if err != nil { |
|
t.Fatalf("unable to index into block: %v", err) |
|
} |
|
if !specifiedTxHash.IsEqual(txid1) { |
|
t.Fatalf("mismatched tx indexes: expected %v, got %v", |
|
txid1, specifiedTxHash) |
|
} |
|
|
|
// We'll also ensure that the block height has been set |
|
// properly. |
|
if confInfo.BlockHeight != uint32(currentHeight+1) { |
|
t.Fatalf("incorrect block height: expected %v, got %v", |
|
confInfo.BlockHeight, currentHeight) |
|
} |
|
break |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
|
|
// Register a confirmation notification for tx2, requiring 3 confirmations. |
|
// This transaction is only partially confirmed, so the notification should |
|
// not fire yet. |
|
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) |
|
} |
|
|
|
// Fully confirm tx3. |
|
_, err = miner.Client.Generate(2) |
|
if err != nil { |
|
t.Fatalf("unable to generate block: %v", err) |
|
} |
|
|
|
select { |
|
case <-ntfn2.Confirmed: |
|
case <-time.After(10 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
|
|
select { |
|
case <-ntfn1.Confirmed: |
|
t.Fatalf("received multiple confirmations for tx") |
|
case <-time.After(1 * time.Second): |
|
} |
|
|
|
// Finally register a confirmation notification for tx3, requiring 1 |
|
// confirmation. Ensure that conf notifications do not refire on txs |
|
// 1 or 2. |
|
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) |
|
} |
|
|
|
// We'll also register for a confirmation notification with the pkscript |
|
// of a different transaction. This notification shouldn't fire since we |
|
// match on both txid and pkscript. |
|
var ntfn4 *chainntnfs.ConfirmationEvent |
|
ntfn4, err = notifier.RegisterConfirmationsNtfn( |
|
txid3, pkScript2, 1, uint32(currentHeight-1), |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to register ntfn: %v", err) |
|
} |
|
|
|
select { |
|
case <-ntfn3.Confirmed: |
|
case <-time.After(10 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
|
|
select { |
|
case <-ntfn4.Confirmed: |
|
t.Fatalf("confirmation notification received") |
|
case <-time.After(5 * time.Second): |
|
} |
|
|
|
time.Sleep(1 * time.Second) |
|
|
|
select { |
|
case <-ntfn1.Confirmed: |
|
t.Fatalf("received multiple confirmations for tx") |
|
default: |
|
} |
|
|
|
select { |
|
case <-ntfn2.Confirmed: |
|
t.Fatalf("received multiple confirmations for tx") |
|
default: |
|
} |
|
} |
|
|
|
// Test the case of a notification consumer having forget or being delayed in |
|
// checking for a confirmation. This should not cause the notifier to stop |
|
// working |
|
func testLazyNtfnConsumer(miner *rpctest.Harness, |
|
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 |
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
numConfs := uint32(3) |
|
|
|
// Add a block right before registering, this makes race conditions |
|
// between the historical dispatcher and the normal dispatcher more obvious |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
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) |
|
} |
|
|
|
// Generate another 2 blocks, this should dispatch the confirm notification |
|
if _, err := miner.Client.Generate(2); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
// Now make another transaction, just because we haven't checked to see |
|
// if the first transaction has confirmed doesn't mean that we shouldn't |
|
// be able to see if this transaction confirms first |
|
txid, pkScript, err = chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err = miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
numConfs = 1 |
|
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) |
|
} |
|
|
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
select { |
|
case <-secondConfIntent.Confirmed: |
|
// Successfully receive the second notification |
|
break |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("Second confirmation notification never received") |
|
} |
|
|
|
// Make sure the first tx confirmed successfully |
|
select { |
|
case <-firstConfIntent.Confirmed: |
|
break |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("First confirmation notification never received") |
|
} |
|
} |
|
|
|
// Tests the case in which a spend notification is requested for a spend that |
|
// 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, 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, output, privKey := chainntnfs.CreateSpendableOutput(t, miner) |
|
|
|
_, heightHint, err := miner.Client.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, output, privKey) |
|
spenderSha, err := miner.Client.SendRawTransaction(spendingTx, true) |
|
if err != nil { |
|
t.Fatalf("unable to broadcast tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, spenderSha); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
// We create an epoch client we can use to make sure the notifier is |
|
// caught up to the mining node's chain. |
|
epochClient, err := notifier.RegisterBlockEpochNtfn(nil) |
|
if err != nil { |
|
t.Fatalf("unable to register for block epoch: %v", err) |
|
} |
|
|
|
// Now we mine an additional block, which should include our spend. |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
_, spendHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// checkSpends registers two clients to be notified of a spend that has |
|
// already happened. The notifier should dispatch a spend notification |
|
// immediately. |
|
checkSpends := func() { |
|
t.Helper() |
|
|
|
const numClients = 2 |
|
spendClients := make([]*chainntnfs.SpendEvent, numClients) |
|
for i := 0; i < numClients; i++ { |
|
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) |
|
} |
|
|
|
spendClients[i] = spentIntent |
|
} |
|
|
|
for _, client := range spendClients { |
|
select { |
|
case ntfn := <-client.Spend: |
|
// We've received the spend nftn. So now verify |
|
// all the fields have been set properly. |
|
checkNotificationFields( |
|
ntfn, outpoint, spenderSha, spendHeight, t, |
|
) |
|
case <-time.After(30 * time.Second): |
|
t.Fatalf("spend ntfn never received") |
|
} |
|
} |
|
} |
|
|
|
// Wait for the notifier to have caught up to the mined block. |
|
select { |
|
case _, ok := <-epochClient.Epochs: |
|
if !ok { |
|
t.Fatalf("epoch channel was closed") |
|
} |
|
case <-time.After(15 * time.Second): |
|
t.Fatalf("did not receive block epoch") |
|
} |
|
|
|
// Check that the spend clients gets immediately notified for the spend |
|
// in the previous block. |
|
checkSpends() |
|
|
|
// Bury the spend even deeper, and do the same check. |
|
const numBlocks = 10 |
|
if _, err := miner.Client.Generate(numBlocks); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// Wait for the notifier to have caught up with the new blocks. |
|
for i := 0; i < numBlocks; i++ { |
|
select { |
|
case _, ok := <-epochClient.Epochs: |
|
if !ok { |
|
t.Fatalf("epoch channel was closed") |
|
} |
|
case <-time.After(15 * time.Second): |
|
t.Fatalf("did not receive block epoch") |
|
} |
|
} |
|
|
|
// The clients should still be notified immediately. |
|
checkSpends() |
|
} |
|
|
|
func testCancelSpendNtfn(node *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) { |
|
|
|
// We'd like to test that once a spend notification is registered, it |
|
// can be canceled before the notification is dispatched. |
|
|
|
// First, we'll start by creating a new output that we can spend |
|
// ourselves. |
|
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, node) |
|
|
|
_, currentHeight, err := node.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Create two clients that each registered to the spend notification. |
|
// We'll cancel the notification for the first client and leave the |
|
// notification for the second client enabled. |
|
const numClients = 2 |
|
spendClients := make([]*chainntnfs.SpendEvent, numClients) |
|
for i := 0; i < numClients; i++ { |
|
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) |
|
} |
|
|
|
spendClients[i] = spentIntent |
|
} |
|
|
|
// Next, create a new transaction spending that output. |
|
spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey) |
|
|
|
// Before we broadcast the spending transaction, we'll cancel the |
|
// notification of the first client. |
|
spendClients[1].Cancel() |
|
|
|
// Broadcast our spending transaction. |
|
spenderSha, err := node.Client.SendRawTransaction(spendingTx, true) |
|
if err != nil { |
|
t.Fatalf("unable to broadcast tx: %v", err) |
|
} |
|
|
|
if err := chainntnfs.WaitForMempoolTx(node, spenderSha); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
// Now we mine a single block, which should include our spend. The |
|
// notification should also be sent off. |
|
if _, err := node.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// 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 |
|
// fields have been set properly. |
|
if *ntfn.SpentOutPoint != *outpoint { |
|
t.Fatalf("ntfn includes wrong output, reports "+ |
|
"%v instead of %v", |
|
ntfn.SpentOutPoint, outpoint) |
|
} |
|
if !bytes.Equal(ntfn.SpenderTxHash[:], spenderSha[:]) { |
|
t.Fatalf("ntfn includes wrong spender tx sha, "+ |
|
"reports %v instead of %v", |
|
ntfn.SpenderTxHash[:], spenderSha[:]) |
|
} |
|
if ntfn.SpenderInputIndex != 0 { |
|
t.Fatalf("ntfn includes wrong spending input "+ |
|
"index, reports %v, should be %v", |
|
ntfn.SpenderInputIndex, 0) |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("spend ntfn never received") |
|
} |
|
|
|
// However, the spend notification of the second client should NOT have |
|
// been dispatched. |
|
select { |
|
case _, ok := <-spendClients[1].Spend: |
|
if ok { |
|
t.Fatalf("spend ntfn should have been canceled") |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("spend ntfn never canceled") |
|
} |
|
} |
|
|
|
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 |
|
// if/when new blocks come in. |
|
const numClients = 2 |
|
|
|
epochClients := make([]*chainntnfs.BlockEpochEvent, numClients) |
|
for i := 0; i < numClients; i++ { |
|
epochClient, err := notifier.RegisterBlockEpochNtfn(nil) |
|
if err != nil { |
|
t.Fatalf("unable to register for epoch notification") |
|
} |
|
epochClients[i] = epochClient |
|
} |
|
|
|
// Now before we mine any blocks, cancel the notification for the first |
|
// epoch client. |
|
epochClients[0].Cancel() |
|
|
|
// Now mine a single block, this should trigger the logic to dispatch |
|
// epoch notifications. |
|
if _, err := node.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
// The epoch notification for the first client shouldn't have been |
|
// dispatched. |
|
select { |
|
case _, ok := <-epochClients[0].Epochs: |
|
if ok { |
|
t.Fatalf("epoch notification should have been canceled") |
|
} |
|
case <-time.After(2 * time.Second): |
|
t.Fatalf("epoch notification not sent") |
|
} |
|
|
|
// However, the epoch notification for the second client should have |
|
// been dispatched as normal. |
|
select { |
|
case _, ok := <-epochClients[1].Epochs: |
|
if !ok { |
|
t.Fatalf("epoch was canceled") |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("epoch notification not sent") |
|
} |
|
} |
|
|
|
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"}, "", |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to create mining node: %v", err) |
|
} |
|
if err := miner2.SetUp(false, 0); err != nil { |
|
t.Fatalf("unable to set up mining node: %v", err) |
|
} |
|
defer miner2.TearDown() |
|
|
|
// We start by connecting the new miner to our original miner, |
|
// such that it will sync to our original chain. |
|
if err := rpctest.ConnectNode(miner, miner2); err != nil { |
|
t.Fatalf("unable to connect harnesses: %v", err) |
|
} |
|
nodeSlice := []*rpctest.Harness{miner, miner2} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to join node on blocks: %v", err) |
|
} |
|
|
|
// The two should be on the same blockheight. |
|
_, nodeHeight1, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
_, nodeHeight2, err := miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
if nodeHeight1 != nodeHeight2 { |
|
t.Fatalf("expected both miners to be on the same height: %v vs %v", |
|
nodeHeight1, nodeHeight2) |
|
} |
|
|
|
// We disconnect the two nodes, such that we can start mining on them |
|
// individually without the other one learning about the new blocks. |
|
err = miner.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove) |
|
if err != nil { |
|
t.Fatalf("unable to remove node: %v", err) |
|
} |
|
|
|
txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) |
|
if err != nil { |
|
t.Fatalf("unable to create test tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, currentHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current height: %v", err) |
|
} |
|
|
|
// Now that we have a txid, register a confirmation notification with |
|
// the chainntfn source. |
|
numConfs := uint32(2) |
|
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) |
|
} |
|
|
|
// Now generate a single block, the transaction should be included. |
|
_, err = miner.Client.Generate(1) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// Transaction only has one confirmation, and the notification is registered |
|
// with 2 confirmations, so we should not be notified yet. |
|
select { |
|
case <-confIntent.Confirmed: |
|
t.Fatal("tx was confirmed unexpectedly") |
|
case <-time.After(1 * time.Second): |
|
} |
|
|
|
// Reorganize transaction out of the chain by generating a longer fork |
|
// from the other miner. The transaction is not included in this fork. |
|
miner2.Client.Generate(2) |
|
|
|
// Reconnect nodes to reach consensus on the longest chain. miner2's chain |
|
// should win and become active on miner1. |
|
if err := rpctest.ConnectNode(miner, miner2); err != nil { |
|
t.Fatalf("unable to connect harnesses: %v", err) |
|
} |
|
nodeSlice = []*rpctest.Harness{miner, miner2} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to join node on blocks: %v", err) |
|
} |
|
|
|
_, nodeHeight1, err = miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
_, nodeHeight2, err = miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
if nodeHeight1 != nodeHeight2 { |
|
t.Fatalf("expected both miners to be on the same height: %v vs %v", |
|
nodeHeight1, nodeHeight2) |
|
} |
|
|
|
// Even though there is one block above the height of the block that the |
|
// transaction was included in, it is not the active chain so the |
|
// notification should not be sent. |
|
select { |
|
case <-confIntent.Confirmed: |
|
t.Fatal("tx was confirmed unexpectedly") |
|
case <-time.After(1 * time.Second): |
|
} |
|
|
|
// Now confirm the transaction on the longest chain and verify that we |
|
// receive the notification. |
|
tx, err := miner.Client.GetRawTransaction(txid) |
|
if err != nil { |
|
t.Fatalf("unable to get raw tx: %v", err) |
|
} |
|
|
|
txid, err = miner2.Client.SendRawTransaction(tx.MsgTx(), false) |
|
if err != nil { |
|
t.Fatalf("unable to get send tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
|
|
_, err = miner.Client.Generate(3) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
select { |
|
case <-confIntent.Confirmed: |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("confirmation notification never received") |
|
} |
|
} |
|
|
|
// testReorgSpend ensures that the different ChainNotifier implementations |
|
// correctly handle outpoints whose spending transaction has been reorged out of |
|
// the chain. |
|
func testReorgSpend(miner *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) { |
|
|
|
// We'll start by creating an output and registering a spend |
|
// notification for it. |
|
outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner) |
|
_, heightHint, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to retrieve current height: %v", err) |
|
} |
|
|
|
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) |
|
} |
|
|
|
// Set up a new miner that we can use to cause a reorg. |
|
miner2, err := rpctest.New( |
|
chainntnfs.NetParams, nil, []string{"--txindex"}, "", |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to create mining node: %v", err) |
|
} |
|
if err := miner2.SetUp(false, 0); err != nil { |
|
t.Fatalf("unable to set up mining node: %v", err) |
|
} |
|
defer miner2.TearDown() |
|
|
|
// We start by connecting the new miner to our original miner, in order |
|
// to have a consistent view of the chain from both miners. They should |
|
// be on the same block height. |
|
if err := rpctest.ConnectNode(miner, miner2); err != nil { |
|
t.Fatalf("unable to connect miners: %v", err) |
|
} |
|
nodeSlice := []*rpctest.Harness{miner, miner2} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to sync miners: %v", err) |
|
} |
|
_, minerHeight1, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get miner1's current height: %v", err) |
|
} |
|
_, minerHeight2, err := miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get miner2's current height: %v", err) |
|
} |
|
if minerHeight1 != minerHeight2 { |
|
t.Fatalf("expected both miners to be on the same height: "+ |
|
"%v vs %v", minerHeight1, minerHeight2) |
|
} |
|
|
|
// We disconnect the two nodes, such that we can start mining on them |
|
// individually without the other one learning about the new blocks. |
|
err = miner.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove) |
|
if err != nil { |
|
t.Fatalf("unable to disconnect miners: %v", err) |
|
} |
|
|
|
// Craft the spending transaction for the outpoint created above and |
|
// confirm it under the chain of the original miner. |
|
spendTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey) |
|
spendTxHash, err := miner.Client.SendRawTransaction(spendTx, true) |
|
if err != nil { |
|
t.Fatalf("unable to broadcast spend tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, spendTxHash); err != nil { |
|
t.Fatalf("spend tx not relayed to miner: %v", err) |
|
} |
|
const numBlocks = 1 |
|
if _, err := miner.Client.Generate(numBlocks); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
_, spendHeight, err := miner.Client.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, spendHeight, t, |
|
) |
|
case <-time.After(5 * time.Second): |
|
t.Fatal("expected spend notification to be dispatched") |
|
} |
|
|
|
// Now, with the other miner, we'll generate one more block than the |
|
// other miner and connect them to cause a reorg. |
|
if _, err := miner2.Client.Generate(numBlocks + 1); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
if err := rpctest.ConnectNode(miner, miner2); err != nil { |
|
t.Fatalf("unable to connect miners: %v", err) |
|
} |
|
nodeSlice = []*rpctest.Harness{miner2, miner} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to sync miners: %v", err) |
|
} |
|
_, minerHeight1, err = miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get miner1's current height: %v", err) |
|
} |
|
_, minerHeight2, err = miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get miner2's current height: %v", err) |
|
} |
|
if minerHeight1 != minerHeight2 { |
|
t.Fatalf("expected both miners to be on the same height: "+ |
|
"%v vs %v", minerHeight1, minerHeight2) |
|
} |
|
|
|
// We should receive a reorg notification. |
|
select { |
|
case _, ok := <-spendIntent.Reorg: |
|
if !ok { |
|
t.Fatal("unexpected reorg channel closed") |
|
} |
|
case <-time.After(5 * time.Second): |
|
t.Fatal("expected to receive reorg notification") |
|
} |
|
|
|
// Now that both miners are on the same chain, we'll confirm the |
|
// spending transaction of the outpoint and receive a notification for |
|
// it. |
|
if _, err = miner2.Client.SendRawTransaction(spendTx, true); err != nil { |
|
t.Fatalf("unable to broadcast spend tx: %v", err) |
|
} |
|
if err := chainntnfs.WaitForMempoolTx(miner, spendTxHash); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
if _, err := miner.Client.Generate(numBlocks); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
_, spendHeight, err = miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to retrieve current height: %v", err) |
|
} |
|
|
|
select { |
|
case spendDetails := <-spendIntent.Spend: |
|
checkNotificationFields( |
|
spendDetails, outpoint, spendTxHash, spendHeight, t, |
|
) |
|
case <-time.After(5 * time.Second): |
|
t.Fatal("expected spend notification to be dispatched") |
|
} |
|
} |
|
|
|
// testCatchUpClientOnMissedBlocks tests the case of multiple registered client |
|
// receiving historical block epoch notifications due to their best known block |
|
// being out of date. |
|
func testCatchUpClientOnMissedBlocks(miner *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, t *testing.T) { |
|
|
|
const numBlocks = 10 |
|
const numClients = 5 |
|
var wg sync.WaitGroup |
|
|
|
outdatedHash, outdatedHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to retrieve current height: %v", err) |
|
} |
|
|
|
// This function is used by UnsafeStart to ensure all notifications |
|
// are fully drained before clients register for notifications. |
|
generateBlocks := func() error { |
|
_, err = miner.Client.Generate(numBlocks) |
|
return err |
|
} |
|
|
|
// We want to ensure that when a client registers for block notifications, |
|
// the notifier's best block is at the tip of the chain. If it isn't, the |
|
// client may not receive all historical notifications. |
|
bestHeight := outdatedHeight + numBlocks |
|
err = notifier.UnsafeStart(bestHeight, nil, bestHeight, generateBlocks) |
|
if err != nil { |
|
t.Fatalf("unable to unsafe start the notifier: %v", err) |
|
} |
|
defer notifier.Stop() |
|
|
|
// Create numClients clients whose best known block is 10 blocks behind |
|
// the tip of the chain. We expect each client to receive numBlocks |
|
// notifications, 1 for each block they're behind. |
|
clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients) |
|
outdatedBlock := &chainntnfs.BlockEpoch{ |
|
Height: outdatedHeight, Hash: outdatedHash, |
|
} |
|
for i := 0; i < numClients; i++ { |
|
epochClient, err := notifier.RegisterBlockEpochNtfn(outdatedBlock) |
|
if err != nil { |
|
t.Fatalf("unable to register for epoch notification: %v", err) |
|
} |
|
clients = append(clients, epochClient) |
|
} |
|
for expectedHeight := outdatedHeight + 1; expectedHeight <= |
|
bestHeight; expectedHeight++ { |
|
|
|
for _, epochClient := range clients { |
|
select { |
|
case block := <-epochClient.Epochs: |
|
if block.Height != expectedHeight { |
|
t.Fatalf("received block of height: %d, "+ |
|
"expected: %d", block.Height, |
|
expectedHeight) |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("did not receive historical notification "+ |
|
"for height %d", expectedHeight) |
|
} |
|
|
|
} |
|
} |
|
|
|
// Finally, ensure that an extra block notification wasn't received. |
|
anyExtras := make(chan struct{}, len(clients)) |
|
for _, epochClient := range clients { |
|
wg.Add(1) |
|
go func(epochClient *chainntnfs.BlockEpochEvent) { |
|
defer wg.Done() |
|
select { |
|
case <-epochClient.Epochs: |
|
anyExtras <- struct{}{} |
|
case <-time.After(5 * time.Second): |
|
} |
|
}(epochClient) |
|
} |
|
|
|
wg.Wait() |
|
close(anyExtras) |
|
|
|
var extraCount int |
|
for range anyExtras { |
|
extraCount++ |
|
} |
|
|
|
if extraCount > 0 { |
|
t.Fatalf("received %d unexpected block notification", extraCount) |
|
} |
|
} |
|
|
|
// testCatchUpOnMissedBlocks the case of multiple registered clients receiving |
|
// historical block epoch notifications due to the notifier's best known block |
|
// being out of date. |
|
func testCatchUpOnMissedBlocks(miner *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, t *testing.T) { |
|
|
|
const numBlocks = 10 |
|
const numClients = 5 |
|
var wg sync.WaitGroup |
|
|
|
_, bestHeight, err := miner.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
// This function is used by UnsafeStart to ensure all notifications |
|
// are fully drained before clients register for notifications. |
|
generateBlocks := func() error { |
|
_, err = miner.Client.Generate(numBlocks) |
|
return err |
|
} |
|
|
|
// Next, start the notifier with outdated best block information. |
|
err = notifier.UnsafeStart( |
|
bestHeight, nil, bestHeight+numBlocks, generateBlocks, |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to unsafe start the notifier: %v", err) |
|
} |
|
defer notifier.Stop() |
|
|
|
// Create numClients clients who will listen for block notifications. |
|
clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients) |
|
for i := 0; i < numClients; i++ { |
|
epochClient, err := notifier.RegisterBlockEpochNtfn(nil) |
|
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) |
|
} |
|
|
|
// Generate a single block to trigger the backlog of historical |
|
// notifications for the previously mined blocks. |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate blocks: %v", err) |
|
} |
|
|
|
// We expect each client to receive numBlocks + 1 notifications, 1 for |
|
// each block that the notifier has missed out on. |
|
for expectedHeight := bestHeight + 1; expectedHeight <= |
|
bestHeight+numBlocks+1; expectedHeight++ { |
|
|
|
for _, epochClient := range clients { |
|
select { |
|
case block := <-epochClient.Epochs: |
|
if block.Height != expectedHeight { |
|
t.Fatalf("received block of height: %d, "+ |
|
"expected: %d", block.Height, |
|
expectedHeight) |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("did not receive historical notification "+ |
|
"for height %d", expectedHeight) |
|
} |
|
} |
|
} |
|
|
|
// Finally, ensure that an extra block notification wasn't received. |
|
anyExtras := make(chan struct{}, len(clients)) |
|
for _, epochClient := range clients { |
|
wg.Add(1) |
|
go func(epochClient *chainntnfs.BlockEpochEvent) { |
|
defer wg.Done() |
|
select { |
|
case <-epochClient.Epochs: |
|
anyExtras <- struct{}{} |
|
case <-time.After(5 * time.Second): |
|
} |
|
}(epochClient) |
|
} |
|
|
|
wg.Wait() |
|
close(anyExtras) |
|
|
|
var extraCount int |
|
for range anyExtras { |
|
extraCount++ |
|
} |
|
|
|
if extraCount > 0 { |
|
t.Fatalf("received %d unexpected block notification", extraCount) |
|
} |
|
} |
|
|
|
// testCatchUpOnMissedBlocks tests that a client will still receive all valid |
|
// block notifications in the case where a notifier's best block has been reorged |
|
// out of the chain. |
|
func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness, |
|
notifier chainntnfs.TestChainNotifier, t *testing.T) { |
|
|
|
// If this is the neutrino notifier, then we'll skip this test for now |
|
// as we're missing functionality required to ensure the test passes |
|
// reliably. |
|
if _, ok := notifier.(*neutrinonotify.NeutrinoNotifier); ok { |
|
t.Skip("skipping re-org test for neutrino") |
|
} |
|
|
|
const numBlocks = 10 |
|
const numClients = 5 |
|
var wg sync.WaitGroup |
|
|
|
// Set up a new miner that we can use to cause a reorg. |
|
miner2, err := rpctest.New( |
|
chainntnfs.NetParams, nil, []string{"--txindex"}, "", |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to create mining node: %v", err) |
|
} |
|
if err := miner2.SetUp(false, 0); err != nil { |
|
t.Fatalf("unable to set up mining node: %v", err) |
|
} |
|
defer miner2.TearDown() |
|
|
|
// We start by connecting the new miner to our original miner, |
|
// such that it will sync to our original chain. |
|
if err := rpctest.ConnectNode(miner1, miner2); err != nil { |
|
t.Fatalf("unable to connect harnesses: %v", err) |
|
} |
|
nodeSlice := []*rpctest.Harness{miner1, miner2} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to join node on blocks: %v", err) |
|
} |
|
|
|
// The two should be on the same blockheight. |
|
_, nodeHeight1, err := miner1.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
_, nodeHeight2, err := miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current blockheight %v", err) |
|
} |
|
|
|
if nodeHeight1 != nodeHeight2 { |
|
t.Fatalf("expected both miners to be on the same height: %v vs %v", |
|
nodeHeight1, nodeHeight2) |
|
} |
|
|
|
// We disconnect the two nodes, such that we can start mining on them |
|
// individually without the other one learning about the new blocks. |
|
err = miner1.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove) |
|
if err != nil { |
|
t.Fatalf("unable to remove node: %v", err) |
|
} |
|
|
|
// Now mine on each chain separately |
|
blocks, err := miner1.Client.Generate(numBlocks) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// We generate an extra block on miner 2's chain to ensure it is the |
|
// longer chain. |
|
_, err = miner2.Client.Generate(numBlocks + 1) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// Sync the two chains to ensure they will sync to miner2's chain. |
|
if err := rpctest.ConnectNode(miner1, miner2); err != nil { |
|
t.Fatalf("unable to connect harnesses: %v", err) |
|
} |
|
nodeSlice = []*rpctest.Harness{miner1, miner2} |
|
if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { |
|
t.Fatalf("unable to join node on blocks: %v", err) |
|
} |
|
|
|
// The two should be on the same block hash. |
|
timeout := time.After(10 * time.Second) |
|
for { |
|
nodeHash1, _, err := miner1.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current block hash: %v", err) |
|
} |
|
|
|
nodeHash2, _, err := miner2.Client.GetBestBlock() |
|
if err != nil { |
|
t.Fatalf("unable to get current block hash: %v", err) |
|
} |
|
|
|
if *nodeHash1 == *nodeHash2 { |
|
break |
|
} |
|
select { |
|
case <-timeout: |
|
t.Fatalf("Unable to sync two chains") |
|
case <-time.After(50 * time.Millisecond): |
|
continue |
|
} |
|
} |
|
|
|
// Next, start the notifier with outdated best block information. |
|
// We set the notifier's best block to be the last block mined on the |
|
// shorter chain, to test that the notifier correctly rewinds to |
|
// the common ancestor between the two chains. |
|
syncHeight := nodeHeight1 + numBlocks + 1 |
|
err = notifier.UnsafeStart( |
|
nodeHeight1+numBlocks, blocks[numBlocks-1], syncHeight, nil, |
|
) |
|
if err != nil { |
|
t.Fatalf("Unable to unsafe start the notifier: %v", err) |
|
} |
|
defer notifier.Stop() |
|
|
|
// Create numClients clients who will listen for block notifications. |
|
clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients) |
|
for i := 0; i < numClients; i++ { |
|
epochClient, err := notifier.RegisterBlockEpochNtfn(nil) |
|
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) |
|
} |
|
|
|
// Generate a single block, which should trigger the notifier to rewind |
|
// to the common ancestor and dispatch notifications from there. |
|
_, err = miner2.Client.Generate(1) |
|
if err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
// If the chain backend to the notifier stores information about reorged |
|
// blocks, the notifier is able to rewind the chain to the common |
|
// ancestor between the chain tip and its outdated best known block. |
|
// In this case, the client is expected to receive numBlocks + 2 |
|
// notifications, 1 for each block the notifier has missed out on from |
|
// the longer chain. |
|
// |
|
// If the chain backend does not store information about reorged blocks, |
|
// the notifier has no way of knowing where to rewind to and therefore |
|
// the client is only expected to receive notifications for blocks |
|
// whose height is greater than the notifier's best known height: 2 |
|
// notifications, in this case. |
|
var startingHeight int32 |
|
switch notifier.(type) { |
|
case *neutrinonotify.NeutrinoNotifier: |
|
startingHeight = nodeHeight1 + numBlocks + 1 |
|
default: |
|
startingHeight = nodeHeight1 + 1 |
|
} |
|
|
|
for expectedHeight := startingHeight; expectedHeight <= |
|
nodeHeight1+numBlocks+2; expectedHeight++ { |
|
|
|
for _, epochClient := range clients { |
|
select { |
|
case block := <-epochClient.Epochs: |
|
if block.Height != expectedHeight { |
|
t.Fatalf("received block of height: %d, "+ |
|
"expected: %d", block.Height, |
|
expectedHeight) |
|
} |
|
case <-time.After(20 * time.Second): |
|
t.Fatalf("did not receive historical notification "+ |
|
"for height %d", expectedHeight) |
|
} |
|
} |
|
} |
|
|
|
// Finally, ensure that an extra block notification wasn't received. |
|
anyExtras := make(chan struct{}, len(clients)) |
|
for _, epochClient := range clients { |
|
wg.Add(1) |
|
go func(epochClient *chainntnfs.BlockEpochEvent) { |
|
defer wg.Done() |
|
select { |
|
case <-epochClient.Epochs: |
|
anyExtras <- struct{}{} |
|
case <-time.After(5 * time.Second): |
|
} |
|
}(epochClient) |
|
} |
|
|
|
wg.Wait() |
|
close(anyExtras) |
|
|
|
var extraCount int |
|
for range anyExtras { |
|
extraCount++ |
|
} |
|
|
|
if extraCount > 0 { |
|
t.Fatalf("received %d unexpected block notification", extraCount) |
|
} |
|
} |
|
|
|
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) |
|
} |
|
|
|
type blockCatchupTestCase struct { |
|
name string |
|
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier, |
|
t *testing.T) |
|
} |
|
|
|
var txNtfnTests = []txNtfnTestCase{ |
|
{ |
|
name: "single conf ntfn", |
|
test: testSingleConfirmationNotification, |
|
}, |
|
{ |
|
name: "multi conf ntfn", |
|
test: testMultiConfirmationNotification, |
|
}, |
|
{ |
|
name: "batch conf ntfn", |
|
test: testBatchConfirmationNotification, |
|
}, |
|
{ |
|
name: "multi client conf", |
|
test: testMultiClientConfirmationNotification, |
|
}, |
|
{ |
|
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, |
|
}, |
|
} |
|
|
|
var blockCatchupTests = []blockCatchupTestCase{ |
|
{ |
|
name: "catch up client on historical block epoch ntfns", |
|
test: testCatchUpClientOnMissedBlocks, |
|
}, |
|
{ |
|
name: "test catch up on missed blocks", |
|
test: testCatchUpOnMissedBlocks, |
|
}, |
|
{ |
|
name: "test catch up on missed blocks w/ reorged best block", |
|
test: testCatchUpOnMissedBlocksWithReorg, |
|
}, |
|
} |
|
|
|
// TestInterfaces tests all registered interfaces with a unified set of tests |
|
// which exercise each of the required methods found within the ChainNotifier |
|
// interface. |
|
// |
|
// NOTE: In the future, when additional implementations of the ChainNotifier |
|
// interface have been implemented, in order to ensure the new concrete |
|
// implementation is automatically tested, two steps must be undertaken. First, |
|
// one needs add a "non-captured" (_) import from the new sub-package. This |
|
// import should trigger an init() method within the package which registers |
|
// the interface. Second, an additional case in the switch within the main loop |
|
// below needs to be added which properly initializes the interface. |
|
func TestInterfaces(t *testing.T, targetBackEnd string) { |
|
// Initialize the harness around a btcd node which will serve as our |
|
// dedicated miner to generate blocks, cause re-orgs, etc. We'll set up |
|
// this node with a chain length of 125, so we have plenty of BTC to |
|
// play around with. |
|
miner, tearDown := chainntnfs.NewMiner(t, nil, true, 25) |
|
defer tearDown() |
|
|
|
rpcConfig := miner.RPCConfig() |
|
p2pAddr := miner.P2PAddress() |
|
|
|
log.Printf("Running %v ChainNotifier interface tests", |
|
2*len(txNtfnTests)+len(blockNtfnTests)+len(blockCatchupTests)) |
|
|
|
for _, notifierDriver := range chainntnfs.RegisteredNotifiers() { |
|
notifierType := notifierDriver.NotifierType |
|
if notifierType != targetBackEnd { |
|
continue |
|
} |
|
|
|
// Initialize a height hint cache for each notifier. |
|
tempDir, err := ioutil.TempDir("", "channeldb") |
|
if err != nil { |
|
t.Fatalf("unable to create temp dir: %v", err) |
|
} |
|
db, err := channeldb.Open(tempDir) |
|
if err != nil { |
|
t.Fatalf("unable to create db: %v", err) |
|
} |
|
testCfg := chainntnfs.CacheConfig{ |
|
QueryDisable: false, |
|
} |
|
hintCache, err := chainntnfs.NewHeightHintCache(testCfg, db) |
|
if err != nil { |
|
t.Fatalf("unable to create height hint cache: %v", err) |
|
} |
|
|
|
var ( |
|
cleanUp func() |
|
newNotifier func() (chainntnfs.TestChainNotifier, error) |
|
) |
|
|
|
switch notifierType { |
|
case "bitcoind": |
|
var bitcoindConn *chain.BitcoindConn |
|
bitcoindConn, cleanUp = chainntnfs.NewBitcoindBackend( |
|
t, p2pAddr, true, |
|
) |
|
newNotifier = func() (chainntnfs.TestChainNotifier, error) { |
|
return bitcoindnotify.New( |
|
bitcoindConn, chainntnfs.NetParams, |
|
hintCache, hintCache, |
|
), nil |
|
} |
|
|
|
case "btcd": |
|
newNotifier = func() (chainntnfs.TestChainNotifier, error) { |
|
return btcdnotify.New( |
|
&rpcConfig, chainntnfs.NetParams, |
|
hintCache, hintCache, |
|
) |
|
} |
|
|
|
case "neutrino": |
|
var spvNode *neutrino.ChainService |
|
spvNode, cleanUp = chainntnfs.NewNeutrinoBackend( |
|
t, p2pAddr, |
|
) |
|
newNotifier = func() (chainntnfs.TestChainNotifier, error) { |
|
return neutrinonotify.New( |
|
spvNode, hintCache, hintCache, |
|
), nil |
|
} |
|
} |
|
|
|
log.Printf("Running ChainNotifier interface tests for: %v", |
|
notifierType) |
|
|
|
notifier, err := newNotifier() |
|
if err != nil { |
|
t.Fatalf("unable to create %v notifier: %v", |
|
notifierType, err) |
|
} |
|
if err := notifier.Start(); err != nil { |
|
t.Fatalf("unable to start notifier %v: %v", |
|
notifierType, err) |
|
} |
|
|
|
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) { |
|
blockNtfnTest.test(miner, notifier, t) |
|
}) |
|
if !success { |
|
break |
|
} |
|
} |
|
|
|
notifier.Stop() |
|
|
|
// Run catchup tests separately since they require restarting |
|
// the notifier every time. |
|
for _, blockCatchupTest := range blockCatchupTests { |
|
notifier, err = newNotifier() |
|
if err != nil { |
|
t.Fatalf("unable to create %v notifier: %v", |
|
notifierType, err) |
|
} |
|
|
|
testName := fmt.Sprintf("%v %v", notifierType, |
|
blockCatchupTest.name) |
|
|
|
success := t.Run(testName, func(t *testing.T) { |
|
blockCatchupTest.test(miner, notifier, t) |
|
}) |
|
if !success { |
|
break |
|
} |
|
} |
|
|
|
if cleanUp != nil { |
|
cleanUp() |
|
} |
|
} |
|
}
|
|
|