From d3e4bca772beea5f662b9df88c271edc89807d13 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sun, 13 Aug 2023 13:44:18 -0700 Subject: [PATCH] itest: add taproot support to revocation itests --- itest/lnd_revocation_test.go | 220 +++++++++++++++++++++++++++-------- 1 file changed, 172 insertions(+), 48 deletions(-) diff --git a/itest/lnd_revocation_test.go b/itest/lnd_revocation_test.go index 1ca9ee35e..96a7e89f8 100644 --- a/itest/lnd_revocation_test.go +++ b/itest/lnd_revocation_test.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -17,10 +18,9 @@ import ( "github.com/stretchr/testify/require" ) -// testRevokedCloseRetribution tests that Carol is able carry out -// retribution in the event that she fails immediately after detecting Bob's -// breach txn in the mempool. -func testRevokedCloseRetribution(ht *lntest.HarnessTest) { +func breachRetributionTestCase(ht *lntest.HarnessTest, + commitType lnrpc.CommitmentType) { + const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -32,14 +32,18 @@ func testRevokedCloseRetribution(ht *lntest.HarnessTest) { // protection logic automatically. We also can't have Carol // automatically re-connect too early, otherwise DLP would be initiated // instead of the breach we want to provoke. + nodeArgs := lntest.NodeArgsForCommitType(commitType) carol := ht.NewNode( "Carol", - []string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"}, + append( + nodeArgs, + []string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"}..., + ), ) // We must let Bob communicate with Carol before they are able to open // channel, so we connect Bob and Carol, - bob := ht.Bob + bob := ht.NewNode("Bob", nodeArgs) ht.ConnectNodes(carol, bob) // Before we make a channel, we'll load up Carol with some coins sent @@ -49,8 +53,13 @@ func testRevokedCloseRetribution(ht *lntest.HarnessTest) { // In order to test Carol's response to an uncooperative channel // closure by Bob, we'll first open up a channel between them with a // 0.5 BTC value. + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( - carol, bob, lntest.OpenChannelParams{Amt: chanAmt}, + carol, bob, lntest.OpenChannelParams{ + CommitmentType: commitType, + Amt: chanAmt, + Private: privateChan, + }, ) // With the channel open, we'll create a few invoices for Bob that @@ -116,17 +125,34 @@ func testRevokedCloseRetribution(ht *lntest.HarnessTest) { block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] ht.Miner.AssertTxInBlock(block, breachTXID) + // Construct to_remote output which pays to Bob. Based on the output + // ordering, the first output in this breach tx is the to_remote + // output. + toRemoteOp := wire.OutPoint{ + Hash: *breachTXID, + Index: 0, + } + + // If this is an anchor-enabled channel, the first two outputs are + // anchors, so the to_remote output is the third one. + if lntest.CommitTypeHasAnchors(commitType) { + toRemoteOp.Index = 2 + } + // Query the mempool for Carol's justice transaction, this should be // broadcast as Bob's contract breaching transaction gets confirmed // above. - justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0] + // + // NOTE: For channels with anchors, we will also see the anchor + // sweeping transactions in the mempool. Thus we directly assert that + // the breach transaction's outpoint is seen in the mempool instead of + // checking the number of transactions. + justiceTx := ht.Miner.AssertOutpointInMempool(toRemoteOp) - // Query for the mempool transaction found above. Then assert that all - // the inputs of this transaction are spending outputs generated by - // Bob's breach transaction above. - justiceTx := ht.Miner.GetRawTransaction(justiceTXID) - for _, txIn := range justiceTx.MsgTx().TxIn { - require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:], + // Assert that all the inputs of this transaction are spending outputs + // generated by Bob's breach transaction above. + for _, txIn := range justiceTx.TxIn { + require.Equal(ht, *breachTXID, txIn.PreviousOutPoint.Hash, "justice tx not spending commitment utxo") } @@ -140,15 +166,16 @@ func testRevokedCloseRetribution(ht *lntest.HarnessTest) { // Now mine a block, this transaction should include Carol's justice // transaction which was just accepted into the mempool. - block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] + expectedNumTxes := 1 - // The block should have exactly *two* transactions, one of which is - // the justice transaction. - require.Len(ht, block.Transactions, 2, "transaction wasn't mined") + // For anchor channels, we'd also create the sweeping transaction. + if lntest.CommitTypeHasAnchors(commitType) { + expectedNumTxes = 2 + } - justiceSha := block.Transactions[1].TxHash() - require.Equal(ht, justiceTx.Hash()[:], justiceSha[:], - "justice tx wasn't mined") + block = ht.MineBlocksAndAssertNumTxes(1, expectedNumTxes)[0] + justiceTxid := justiceTx.TxHash() + ht.Miner.AssertTxInBlock(block, &justiceTxid) ht.AssertNodeNumChannels(carol, 0) @@ -162,10 +189,25 @@ func testRevokedCloseRetribution(ht *lntest.HarnessTest) { ht.AssertNumPendingForceClose(bob, 0) } -// testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able -// carry out retribution in the event that he fails in state where the remote -// commitment output has zero-value. -func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { +// testRevokedCloseRetribution tests that Carol is able carry out retribution +// in the event that she fails immediately after detecting Bob's breach txn in +// the mempool. +func testRevokedCloseRetribution(ht *lntest.HarnessTest) { + for _, commitType := range []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_SIMPLE_TAPROOT, + } { + testName := fmt.Sprintf("%v", commitType.String()) + ht.Run(testName, func(t *testing.T) { + st := ht.Subtest(t) + breachRetributionTestCase(st, commitType) + }) + } +} + +func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, + commitType lnrpc.CommitmentType) { + const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -174,7 +216,11 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { // Since we'd like to test some multi-hop failure scenarios, we'll // introduce another node into our test network: Carol. - carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"}) + nodeArgs := lntest.NodeArgsForCommitType(commitType) + carol := ht.NewNode( + "Carol", + append(nodeArgs, []string{"--hodl.exit-settle"}...), + ) // Dave will be the breached party. We set --nolisten to ensure Carol // won't be able to connect to him and trigger the channel data @@ -183,7 +229,10 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { // breach we want to provoke. dave := ht.NewNode( "Dave", - []string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"}, + append( + nodeArgs, + []string{"--hodl.exit-settle", "--nolisten", + "--minbackoff=1h"}...), ) // We must let Dave have an open channel before he can send a node @@ -197,8 +246,12 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { // In order to test Dave's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a // 0.5 BTC value. + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( - dave, carol, lntest.OpenChannelParams{Amt: chanAmt}, + dave, carol, lntest.OpenChannelParams{ + Amt: chanAmt, + Private: privateChan, + }, ) // With the channel open, we'll create a few invoices for Carol that @@ -262,16 +315,35 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { breachTXID := ht.WaitForChannelCloseEvent(stream) require.EqualValues(ht, breachTXID, closeTxID) + // Construct to_local output which pays to Dave. Based on the output + // ordering, the first output in this breach tx is the to_local + // output. + toLocalOp := wire.OutPoint{ + Hash: *breachTXID, + Index: 0, + } + + // If this is an anchor-enabled channel, we usaually have two anchors, + // one for local and one for remote. However, since the to_remote + // balance is zero, the remote anchor won't be created, thus the + // to_local output is the second output. + if lntest.CommitTypeHasAnchors(commitType) { + toLocalOp.Index = 1 + } + // Query the mempool for Dave's justice transaction, this should be // broadcast as Carol's contract breaching transaction gets confirmed // above. - justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0] + // + // NOTE: For channels with anchors, we will also see the anchor + // sweeping transactions in the mempool. Thus we directly assert that + // the breach transaction's outpoint is seen in the mempool instead of + // checking the number of transactions. + justiceTx := ht.Miner.AssertOutpointInMempool(toLocalOp) - // Query for the mempool transaction found above. Then assert that all - // the inputs of this transaction are spending outputs generated by - // Carol's breach transaction above. - justiceTx := ht.Miner.GetRawTransaction(justiceTXID) - for _, txIn := range justiceTx.MsgTx().TxIn { + // Assert that all the inputs of this transaction are spending outputs + // generated by Carol's breach transaction above. + for _, txIn := range justiceTx.TxIn { require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:], "justice tx not spending commitment utxo ") } @@ -285,22 +357,41 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. - block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] + expectedNumTxes := 1 - // The block should have exactly *two* transactions, one of which is - // the justice transaction. - require.Len(ht, block.Transactions, 2, "transaction wasn't mined") - justiceSha := block.Transactions[1].TxHash() - require.Equal(ht, justiceTx.Hash()[:], justiceSha[:], - "justice tx wasn't mined") + // For anchor channels, we'd also create the sweeping transaction. + if lntest.CommitTypeHasAnchors(commitType) { + expectedNumTxes = 2 + } + + block := ht.MineBlocksAndAssertNumTxes(1, expectedNumTxes)[0] + justiceTxid := justiceTx.TxHash() + ht.Miner.AssertTxInBlock(block, &justiceTxid) ht.AssertNodeNumChannels(dave, 0) } -// testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a -// channel breach made by the remote party, specifically in the case that the -// remote party breaches before settling extended HTLCs. -func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { +// testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able +// carry out retribution in the event that he fails in state where the remote +// commitment output has zero-value. +func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { + for _, commitType := range []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_SIMPLE_TAPROOT, + } { + testName := fmt.Sprintf("%v", commitType.String()) + ht.Run(testName, func(t *testing.T) { + st := ht.Subtest(t) + revokedCloseRetributionZeroValueRemoteOutputCase( + st, commitType, + ) + }) + } +} + +func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, + commitType lnrpc.CommitmentType) { + const ( chanAmt = funding.MaxBtcFundingAmount pushAmt = 200000 @@ -311,7 +402,11 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { // Since this test will result in the counterparty being left in a // weird state, we will introduce another node into our test network: // Carol. - carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"}) + nodeArgs := lntest.NodeArgsForCommitType(commitType) + carol := ht.NewNode( + "Carol", + append(nodeArgs, []string{"--hodl.exit-settle"}...), + ) // We'll also create a new node Dave, who will have a channel with // Carol, and also use similar settings so we can broadcast a commit @@ -320,7 +415,10 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { // trigger the channel data protection logic automatically. dave := ht.NewNode( "Dave", - []string{"--hodl.exit-settle", "--nolisten"}, + append( + nodeArgs, + []string{"--hodl.exit-settle", "--nolisten"}..., + ), ) // We must let Dave communicate with Carol before they are able to open @@ -334,10 +432,12 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { // In order to test Dave's response to an uncooperative channel closure // by Carol, we'll first open up a channel between them with a // funding.MaxBtcFundingAmount (2^24) satoshis value. + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( dave, carol, lntest.OpenChannelParams{ Amt: chanAmt, PushAmt: pushAmt, + Private: privateChan, }, ) @@ -491,6 +591,7 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { return txid, nil } } + return nil, errNotFound } @@ -578,12 +679,35 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. - ht.MineBlocksAndAssertNumTxes(1, 1) + expectedNumTxes := 1 + + // For anchor channels, we'd also create the sweeping transaction. + if lntest.CommitTypeHasAnchors(commitType) { + expectedNumTxes = 2 + } + + ht.MineBlocksAndAssertNumTxes(1, expectedNumTxes) // Dave should have no open channels. ht.AssertNodeNumChannels(dave, 0) } +// testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a +// channel breach made by the remote party, specifically in the case that the +// remote party breaches before settling extended HTLCs. +func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { + for _, commitType := range []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_SIMPLE_TAPROOT, + } { + testName := fmt.Sprintf("%v", commitType.String()) + ht.Run(testName, func(t *testing.T) { + st := ht.Subtest(t) + revokedCloseRetributionRemoteHodlCase(st, commitType) + }) + } +} + // testRevokedCloseRetributionAltruistWatchtower establishes a channel between // Carol and Dave, where Carol is using a third node Willy as her watchtower. // After sending some payments, Dave reverts his state and force closes to