lnd.xprv/chainntfs/btcdnotify/btcdnotify_test.go

321 lines
9.3 KiB
Go
Raw Normal View History

package btcdnotify
import (
"bytes"
"testing"
"time"
"github.com/lightningnetwork/lnd/chainntfs"
"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) (*wire.ShaHash, error) {
script, err := txscript.PayToAddrScript(testAddr)
if err != nil {
return nil, err
}
outputs := []*wire.TxOut{&wire.TxOut{2e8, script}}
return miner.CoinbaseSpend(outputs)
}
func testSingleConfirmationNotification(miner *rpctest.Harness,
notifier chainntnfs.ChainNotifier, 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", 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 addr: %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)
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.
if _, err := miner.Node.Generate(1); err != nil {
t.Fatalf("unable to generate single block: %v", err)
}
confSent := make(chan int32)
go func() {
confSent <- <-confIntent.Confirmed
}()
select {
case <-confSent:
break
case <-time.After(2 * time.Second):
t.Fatalf("confirmation notification never received")
}
}
func testMultiConfirmationNotification(miner *rpctest.Harness,
notifier chainntnfs.ChainNotifier, 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, err := getTestTxId(miner)
if err != nil {
t.Fatalf("unable to create test addr: %v", err)
}
numConfs := uint32(6)
confIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs)
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 int32)
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) {
// 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))
// 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)
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 int32)
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 testSpendNotification(miner *rpctest.Harness,
notifier chainntnfs.ChainNotifier, t *testing.T) {
// We'd like to test the spend notifiations for all
// chainntnfs.ChainNotifier concrete implemenations.
//
// 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))
spentIntent, err := notifier.RegisterSpendNtfn(outpoint)
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
// Next, create a new transaction spending that output.
spendingTx := wire.NewMsgTx()
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 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)
}
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.Bytes(), spenderSha.Bytes()) {
t.Fatalf("ntfn includes wrong spender tx sha, reports %v intead of %v",
ntfn.SpenderTxHash.Bytes(), spenderSha.Bytes())
}
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")
}
}
var ntfnTests = []func(node *rpctest.Harness, notifier chainntnfs.ChainNotifier, t *testing.T){
testSingleConfirmationNotification,
testMultiConfirmationNotification,
testBatchConfirmationNotification,
testSpendNotification,
}
// TODO(roasbeef): make test generic across all interfaces?
// * indeed!
// * requires interface implementation registration
func TestBtcdNotifier(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)
}
nodeConfig := miner.RPCConfig()
notifier, err := NewBtcdNotifier(&nodeConfig)
if err != nil {
t.Fatalf("unable to create notifier: %v", err)
}
if err := notifier.Start(); err != nil {
t.Fatalf("unable to start notifier: %v", err)
}
defer notifier.Stop()
for _, ntfnTest := range ntfnTests {
ntfnTest(miner, notifier, t)
}
}