chainntnfs/interface_test: add spend reorg test
This commit is contained in:
parent
deca4cfe44
commit
6458868ba0
@ -232,6 +232,8 @@ 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",
|
||||
@ -756,6 +758,8 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
// 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++ {
|
||||
@ -1108,6 +1112,156 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.TestChainNotifier
|
||||
}
|
||||
}
|
||||
|
||||
// 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, t *testing.T) {
|
||||
|
||||
// We'll start by creating an output and registering a spend
|
||||
// notification for it.
|
||||
outpoint, pkScript := chainntnfs.CreateSpendableOutput(t, miner)
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
spendIntent, err := notifier.RegisterSpendNtfn(
|
||||
outpoint, pkScript, uint32(currentHeight),
|
||||
)
|
||||
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.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get miner1's current height: %v", err)
|
||||
}
|
||||
_, minerHeight2, err := miner2.Node.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.Node.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, pkScript)
|
||||
spendTxHash, err := miner.Node.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.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
}
|
||||
|
||||
// We should see a spend notification dispatched with the correct spend
|
||||
// details.
|
||||
select {
|
||||
case spendDetails := <-spendIntent.Spend:
|
||||
checkNotificationFields(
|
||||
spendDetails, outpoint, spendTxHash,
|
||||
currentHeight+numBlocks, t,
|
||||
)
|
||||
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.Node.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.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get miner1's current height: %v", err)
|
||||
}
|
||||
_, minerHeight2, err = miner2.Node.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.Node.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)
|
||||
}
|
||||
_, currentHeight, err = miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve current height: %v", err)
|
||||
}
|
||||
if _, err := miner.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case spendDetails := <-spendIntent.Spend:
|
||||
checkNotificationFields(
|
||||
spendDetails, outpoint, spendTxHash,
|
||||
currentHeight+numBlocks, 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.
|
||||
@ -1549,6 +1703,10 @@ var ntfnTests = []testCase{
|
||||
name: "reorg conf",
|
||||
test: testReorgConf,
|
||||
},
|
||||
{
|
||||
name: "reorg spend",
|
||||
test: testReorgSpend,
|
||||
},
|
||||
}
|
||||
|
||||
var blockCatchupTests = []blockCatchupTestCase{
|
||||
|
Loading…
Reference in New Issue
Block a user