1b83c6ae16
This commit updates two interface-level tests for confirmation notifications to check the txIndex and blockHeight advertised to serve as regression tests for the recent bug fix related to properly setting these two fields.
932 lines
27 KiB
Go
932 lines
27 KiB
Go
package chainntnfs_test
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
_ "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/chaincfg"
|
|
"github.com/roasbeef/btcd/rpctest"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
var (
|
|
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,
|
|
}
|
|
|
|
netParams = &chaincfg.SimNetParams
|
|
privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey)
|
|
addrPk, _ = btcutil.NewAddressPubKey(pubKey.SerializeCompressed(),
|
|
netParams)
|
|
testAddr = addrPk.AddressPubKeyHash()
|
|
)
|
|
|
|
func getTestTxId(miner *rpctest.Harness) (*chainhash.Hash, error) {
|
|
script, err := txscript.PayToAddrScript(testAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputs := []*wire.TxOut{
|
|
{
|
|
Value: 2e8,
|
|
PkScript: script,
|
|
},
|
|
}
|
|
return miner.SendOutputs(outputs, 10)
|
|
}
|
|
|
|
func testSingleConfirmationNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing single conf notification")
|
|
|
|
// 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", obtainig a txid.
|
|
// We're spending from a coinbase output here, so we use the dedicated
|
|
// function.
|
|
|
|
txid, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test tx: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err := miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
// Now that we have a txid, register a confirmation notiication with
|
|
// the chainntfn source.
|
|
numConfs := uint32(1)
|
|
confIntent, err := notifier.RegisterConfirmationsNtfn(txid, 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.Node.Generate(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
confSent := make(chan *chainntnfs.TxConfirmation)
|
|
go func() {
|
|
confSent <- <-confIntent.Confirmed
|
|
}()
|
|
|
|
select {
|
|
case confInfo := <-confSent:
|
|
// 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.Node.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(2 * time.Second):
|
|
t.Fatalf("confirmation notification never received")
|
|
}
|
|
}
|
|
|
|
func testMultiConfirmationNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing mulit-conf notification")
|
|
|
|
// 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, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test addr: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err := miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
numConfs := uint32(6)
|
|
confIntent, err := notifier.RegisterConfirmationsNtfn(txid, 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.Node.Generate(6); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
confSent := make(chan *chainntnfs.TxConfirmation)
|
|
go func() {
|
|
confSent <- <-confIntent.Confirmed
|
|
}()
|
|
|
|
select {
|
|
case <-confSent:
|
|
break
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("confirmation notification never received")
|
|
}
|
|
}
|
|
|
|
func testBatchConfirmationNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing batch mulit-conf notification")
|
|
|
|
// We'd like to test a case of serving notifiations 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.Node.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, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test addr: %v", err)
|
|
}
|
|
confIntent, err := notifier.RegisterConfirmationsNtfn(txid,
|
|
numConfs, uint32(currentHeight))
|
|
if err != nil {
|
|
t.Fatalf("unable to register ntfn: %v", err)
|
|
}
|
|
confIntents[i] = confIntent
|
|
}
|
|
|
|
// 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.Node.Generate(blocksToGen); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
confSent := make(chan *chainntnfs.TxConfirmation)
|
|
go func() {
|
|
confSent <- <-confIntents[i].Confirmed
|
|
}()
|
|
|
|
select {
|
|
case <-confSent:
|
|
continue
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("confirmation notification never received: %v", numConfs)
|
|
}
|
|
}
|
|
}
|
|
|
|
func createSpendableOutput(miner *rpctest.Harness,
|
|
t *testing.T) (*wire.OutPoint, []byte) {
|
|
|
|
txid, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test addr: %v", err)
|
|
}
|
|
|
|
// Mine a single block which should include that txid above.
|
|
if _, err := miner.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
// Now that we have the txid, fetch the transaction itself.
|
|
wrappedTx, err := miner.Node.GetRawTransaction(txid)
|
|
if err != nil {
|
|
t.Fatalf("unable to get new tx: %v", err)
|
|
}
|
|
tx := wrappedTx.MsgTx()
|
|
|
|
// Locate the output index sent to us. We need this so we can construct
|
|
// a spending txn below.
|
|
outIndex := -1
|
|
var pkScript []byte
|
|
for i, txOut := range tx.TxOut {
|
|
if bytes.Contains(txOut.PkScript, testAddr.ScriptAddress()) {
|
|
pkScript = txOut.PkScript
|
|
outIndex = i
|
|
break
|
|
}
|
|
}
|
|
if outIndex == -1 {
|
|
t.Fatalf("unable to locate new output")
|
|
}
|
|
|
|
return wire.NewOutPoint(txid, uint32(outIndex)), pkScript
|
|
}
|
|
|
|
func createSpendTx(outpoint *wire.OutPoint, pkScript []byte,
|
|
t *testing.T) *wire.MsgTx {
|
|
|
|
spendingTx := wire.NewMsgTx(1)
|
|
spendingTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: *outpoint,
|
|
})
|
|
spendingTx.AddTxOut(&wire.TxOut{
|
|
Value: 1e8,
|
|
PkScript: pkScript,
|
|
})
|
|
sigScript, err := txscript.SignatureScript(spendingTx, 0, pkScript,
|
|
txscript.SigHashAll, privKey, true)
|
|
if err != nil {
|
|
t.Fatalf("unable to sign tx: %v", err)
|
|
}
|
|
spendingTx.TxIn[0].SignatureScript = sigScript
|
|
|
|
return spendingTx
|
|
}
|
|
|
|
func testSpendNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing multi-client spend notification")
|
|
|
|
// 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 := createSpendableOutput(miner, t)
|
|
|
|
_, currentHeight, err := miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
// Now that we have a 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++ {
|
|
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
|
|
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 := createSpendTx(outpoint, pkScript, t)
|
|
|
|
// Broadcast our spending transaction.
|
|
spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true)
|
|
if err != nil {
|
|
t.Fatalf("unable to brodacst tx: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err = miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
// Now we mine a single block, which should include our spend. The
|
|
// notification should also be sent off.
|
|
if _, err := miner.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
// For each event we registered for above, we create a goroutine which
|
|
// will listen on the event channel, passing it proxying each
|
|
// notification into a single which will be examined below..
|
|
spentNtfn := make(chan *chainntnfs.SpendDetail, numClients)
|
|
for i := 0; i < numClients; i++ {
|
|
go func(c *chainntnfs.SpendEvent) {
|
|
spentNtfn <- <-c.Spend
|
|
}(spendClients[i])
|
|
}
|
|
|
|
for i := 0; i < numClients; i++ {
|
|
select {
|
|
case ntfn := <-spentNtfn:
|
|
// 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 intead 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 != currentHeight {
|
|
t.Fatalf("ntfn has wrong spending height: "+
|
|
"expected %v, got %v", currentHeight,
|
|
ntfn.SpendingHeight)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("spend ntfn never received")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testBlockEpochNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing block epoch notification")
|
|
|
|
// We'd like to test the case of multiple registered clients receiving
|
|
// block epoch notifications.
|
|
|
|
const numBlocks = 10
|
|
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.
|
|
for i := 0; i < numClients; i++ {
|
|
epochClient, err := notifier.RegisterBlockEpochNtfn()
|
|
if err != nil {
|
|
t.Fatalf("unable to register for epoch notification")
|
|
}
|
|
|
|
wg.Add(numBlocks)
|
|
go func() {
|
|
for i := 0; i < numBlocks; 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.Node.Generate(numBlocks); err != nil {
|
|
t.Fatalf("unable to generate blocks: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-epochsSent:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("all notifications not sent")
|
|
}
|
|
}
|
|
|
|
func testMultiClientConfirmationNotification(miner *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing multi-client multi-conf notification")
|
|
|
|
// We'd like to test the case of a multiple clients registered to
|
|
// receive a confirmation notification for the same transaction.
|
|
|
|
txid, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test tx: %v", err)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
const (
|
|
numConfsClients = 5
|
|
numConfs = 1
|
|
)
|
|
|
|
_, currentHeight, err := miner.Node.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++ {
|
|
confClient, err := notifier.RegisterConfirmationsNtfn(txid,
|
|
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.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate block: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-confsSent:
|
|
case <-time.After(2 * 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.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing transaction confirmed before notification registration")
|
|
|
|
// 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, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test tx: %v", err)
|
|
}
|
|
|
|
// Now generate one block. 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.
|
|
blockHash, err := miner.Node.Generate(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate two blocks: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err := miner.Node.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)
|
|
confIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs,
|
|
uint32(currentHeight))
|
|
if err != nil {
|
|
t.Fatalf("unable to register ntfn: %v", err)
|
|
}
|
|
|
|
confSent := make(chan *chainntnfs.TxConfirmation)
|
|
go func() {
|
|
confSent <- <-confIntent.Confirmed
|
|
}()
|
|
|
|
select {
|
|
case confInfo := <-confSent:
|
|
// 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.Node.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)
|
|
}
|
|
|
|
// We'll also ensure that the block height has been set
|
|
// properly.
|
|
if confInfo.BlockHeight != uint32(currentHeight) {
|
|
t.Fatalf("incorrect block height: expected %v, got %v",
|
|
confInfo.BlockHeight, currentHeight)
|
|
}
|
|
break
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("confirmation notification never received")
|
|
}
|
|
|
|
// Next, we want to test fully dispatching the notification for a
|
|
// transaction that has been *partially* confirmed. So we'll create
|
|
// another test txid.
|
|
txid, err = getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test tx: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err = miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
// We'll request 6 confirmations for the above generated txid, but we
|
|
// will generate the confirmations in chunks.
|
|
numConfs = 6
|
|
|
|
// First, generate 2 confirmations.
|
|
if _, err := miner.Node.Generate(2); err != nil {
|
|
t.Fatalf("unable to generate blocks: %v", err)
|
|
}
|
|
|
|
// Next, register for the notification *after* the transition has
|
|
// already been partially confirmed.
|
|
confIntent, err = notifier.RegisterConfirmationsNtfn(txid, numConfs,
|
|
uint32(currentHeight))
|
|
if err != nil {
|
|
t.Fatalf("unable to register ntfn: %v", err)
|
|
}
|
|
|
|
// With the notification registered, generate another 4 blocks, this
|
|
// should dispatch the notification.
|
|
if _, err := miner.Node.Generate(4); err != nil {
|
|
t.Fatalf("unable to generate blocks: %v", err)
|
|
}
|
|
|
|
confSent = make(chan *chainntnfs.TxConfirmation)
|
|
go func() {
|
|
confSent <- <-confIntent.Confirmed
|
|
}()
|
|
|
|
select {
|
|
case <-confSent:
|
|
break
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("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.ChainNotifier, t *testing.T) {
|
|
|
|
t.Logf("testing spend broadcast before notification registration")
|
|
|
|
// 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.
|
|
txid, err := getTestTxId(miner)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test addr: %v", err)
|
|
}
|
|
|
|
// Mine a single block which should include that txid above.
|
|
if _, err := miner.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
// Now that we have the txid, fetch the transaction itself.
|
|
wrappedTx, err := miner.Node.GetRawTransaction(txid)
|
|
if err != nil {
|
|
t.Fatalf("unable to get new tx: %v", err)
|
|
}
|
|
tx := wrappedTx.MsgTx()
|
|
|
|
// Locate the output index sent to us. We need this so we can construct
|
|
// a spending txn below.
|
|
outIndex := -1
|
|
var pkScript []byte
|
|
for i, txOut := range tx.TxOut {
|
|
if bytes.Contains(txOut.PkScript, testAddr.ScriptAddress()) {
|
|
pkScript = txOut.PkScript
|
|
outIndex = i
|
|
break
|
|
}
|
|
}
|
|
if outIndex == -1 {
|
|
t.Fatalf("unable to locate new output")
|
|
}
|
|
|
|
// Now that we've found the output index, register for a spentness
|
|
// notification for the newly created output.
|
|
outpoint := wire.NewOutPoint(txid, uint32(outIndex))
|
|
|
|
// Next, create a new transaction spending that output.
|
|
spendingTx := wire.NewMsgTx(1)
|
|
spendingTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: *outpoint,
|
|
})
|
|
spendingTx.AddTxOut(&wire.TxOut{
|
|
Value: 1e8,
|
|
PkScript: pkScript,
|
|
})
|
|
sigScript, err := txscript.SignatureScript(spendingTx, 0, pkScript,
|
|
txscript.SigHashAll, privKey, true)
|
|
if err != nil {
|
|
t.Fatalf("unable to sign tx: %v", err)
|
|
}
|
|
spendingTx.TxIn[0].SignatureScript = sigScript
|
|
|
|
// Broadcast our spending transaction.
|
|
spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true)
|
|
if err != nil {
|
|
t.Fatalf("unable to brodacst tx: %v", err)
|
|
}
|
|
|
|
// Now we mine an additional block, which should include our spend.
|
|
if _, err := miner.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
_, currentHeight, err := miner.Node.GetBestBlock()
|
|
if err != nil {
|
|
t.Fatalf("unable to get current height: %v", err)
|
|
}
|
|
|
|
// Now, we register to be notified of a spend that has already
|
|
// happened. The notifier should dispatch a spend notification
|
|
// immediately.
|
|
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
|
|
uint32(currentHeight))
|
|
if err != nil {
|
|
t.Fatalf("unable to register for spend ntfn: %v", err)
|
|
}
|
|
|
|
spentNtfn := make(chan *chainntnfs.SpendDetail)
|
|
go func() {
|
|
spentNtfn <- <-spentIntent.Spend
|
|
}()
|
|
|
|
select {
|
|
case ntfn := <-spentNtfn:
|
|
// 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 intead 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(2 * time.Second):
|
|
t.Fatalf("spend ntfn never received")
|
|
}
|
|
}
|
|
|
|
func testCancelSpendNtfn(node *rpctest.Harness,
|
|
notifier chainntnfs.ChainNotifier, 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 := createSpendableOutput(node, t)
|
|
|
|
_, currentHeight, err := node.Node.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++ {
|
|
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
|
|
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 := createSpendTx(outpoint, pkScript, t)
|
|
|
|
// 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.Node.SendRawTransaction(spendingTx, true)
|
|
if err != nil {
|
|
t.Fatalf("unable to brodacst tx: %v", err)
|
|
}
|
|
|
|
// Now we mine a single block, which should include our spend. The
|
|
// notification should also be sent off.
|
|
if _, err := node.Node.Generate(1); err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
|
|
// However, 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 intead 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(2 * 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 cancelled")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("spend ntfn never cancelled")
|
|
}
|
|
}
|
|
|
|
func testCancelEpochNtfn(node *rpctest.Harness, notifier chainntnfs.ChainNotifier,
|
|
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()
|
|
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.Node.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've been cancelled")
|
|
}
|
|
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 cancelled")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("epoch notification not sent")
|
|
}
|
|
}
|
|
|
|
var ntfnTests = []func(node *rpctest.Harness, notifier chainntnfs.ChainNotifier, t *testing.T){
|
|
testSingleConfirmationNotification,
|
|
testMultiConfirmationNotification,
|
|
testBatchConfirmationNotification,
|
|
testMultiClientConfirmationNotification,
|
|
testSpendNotification,
|
|
testBlockEpochNotification,
|
|
testTxConfirmedBeforeNtfnRegistration,
|
|
testSpendBeforeNtfnRegistration,
|
|
testCancelSpendNtfn,
|
|
testCancelEpochNtfn,
|
|
}
|
|
|
|
// 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) {
|
|
// 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 plentyyy of BTC to
|
|
// play around with.
|
|
miner, err := rpctest.New(netParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to create mining node: %v", err)
|
|
}
|
|
defer miner.TearDown()
|
|
if err := miner.SetUp(true, 25); err != nil {
|
|
t.Fatalf("unable to set up mining node: %v", err)
|
|
}
|
|
|
|
rpcConfig := miner.RPCConfig()
|
|
|
|
log.Printf("Running %v ChainNotifier interface tests\n", len(ntfnTests))
|
|
var notifier chainntnfs.ChainNotifier
|
|
for _, notifierDriver := range chainntnfs.RegisteredNotifiers() {
|
|
notifierType := notifierDriver.NotifierType
|
|
|
|
switch notifierType {
|
|
case "btcd":
|
|
notifier, err = notifierDriver.New(&rpcConfig)
|
|
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 _, ntfnTest := range ntfnTests {
|
|
ntfnTest(miner, notifier, t)
|
|
}
|
|
|
|
notifier.Stop()
|
|
}
|
|
}
|