From 78de9dae0548d996deef7c5cfa77a8ed3bd0ac00 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 15 Aug 2022 19:03:14 +0800 Subject: [PATCH] itest: break `testMultiHopHtlcClaims` into smaller test cases This commit breaks the large `testMultiHopHtlcClaims` into independent test cases to take the advantage of parallel testing. The old setup will put the stress of creating and syncing new blocks into a single core, which causes a much longer test time and sometimes times out. --- lntemp/harness.go | 3 + lntemp/node/config.go | 4 + lntest/itest/list_on_test.go | 28 +- lntest/itest/lnd_multi-hop_test.go | 564 ++++++++++++++--------------- 4 files changed, 295 insertions(+), 304 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 36f5c64a1..99dbc4047 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -298,6 +298,9 @@ func (h *HarnessTest) Subtest(t *testing.T) (*HarnessTest, func()) { // Reset the standby nodes. st.resetStandbyNodes(t) + // Reset fee estimator. + st.SetFeeEstimate(DefaultFeeRateSatPerKw) + // Record block height. _, startHeight := h.Miner.GetBestBlock() diff --git a/lntemp/node/config.go b/lntemp/node/config.go index 1134851d9..e076a8438 100644 --- a/lntemp/node/config.go +++ b/lntemp/node/config.go @@ -181,6 +181,10 @@ func (cfg *BaseNodeConfig) GenArgs() []string { fmt.Sprintf("--trickledelay=%v", trickleDelay), fmt.Sprintf("--profile=%d", cfg.ProfilePort), fmt.Sprintf("--caches.rpc-graph-cache-duration=%d", 0), + + // Use a small batch window so we can broadcast our sweep + // transactions faster. + "--sweeper.batchwindowduration=5s", } args = append(args, nodeArgs...) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 2e611f222..48871bb7d 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -16,8 +16,32 @@ var allTestCasesTemp = []*lntemp.TestCase{ TestFunc: testBasicChannelFunding, }, { - Name: "test multi-hop htlc", - TestFunc: testMultiHopHtlcClaims, + Name: "multi hop htlc local timeout", + TestFunc: testMultiHopHtlcLocalTimeout, + }, + { + Name: "multi hop receiver chain claim", + TestFunc: testMultiHopReceiverChainClaim, + }, + { + Name: "multi hop local force close on-chain htlc timeout", + TestFunc: testMultiHopLocalForceCloseOnChainHtlcTimeout, + }, + { + Name: "multi hop remote force close on-chain htlc timeout", + TestFunc: testMultiHopRemoteForceCloseOnChainHtlcTimeout, + }, + { + Name: "multi hop htlc local chain claim", + TestFunc: testMultiHopHtlcLocalChainClaim, + }, + { + Name: "multi hop htlc remote chain claim", + TestFunc: testMultiHopHtlcRemoteChainClaim, + }, + { + Name: "multi hop htlc aggregation", + TestFunc: testMultiHopHtlcAggregation, }, { Name: "external channel funding", diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index 948edd801..a71d7f609 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -26,140 +26,30 @@ const ( thawHeightDelta = finalCltvDelta * 2 // 36. ) -func testMultiHopHtlcClaims(ht *lntemp.HarnessTest) { - type testCase struct { - name string - test func(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, - c lnrpc.CommitmentType, zeroConf bool) - commitType lnrpc.CommitmentType - } - - subTests := []testCase{ - { - // bob: outgoing our commit timeout - // carol: incoming their commit watch and see timeout - name: "local force close immediate expiry", - test: testMultiHopHtlcLocalTimeout, - }, - { - // bob: outgoing watch and see, they sweep on chain - // carol: incoming our commit, know preimage - name: "receiver chain claim", - test: testMultiHopReceiverChainClaim, - }, - { - // bob: outgoing our commit watch and see timeout - // carol: incoming their commit watch and see timeout - name: "local force close on-chain htlc timeout", - test: testMultiHopLocalForceCloseOnChainHtlcTimeout, - }, - { - // bob: outgoing their commit watch and see timeout - // carol: incoming our commit watch and see timeout - name: "remote force close on-chain htlc timeout", - test: testMultiHopRemoteForceCloseOnChainHtlcTimeout, - }, - { - // bob: outgoing our commit watch and see, they sweep - // on chain - // bob: incoming our commit watch and learn preimage - // carol: incoming their commit know preimage - name: "local chain claim", - test: testMultiHopHtlcLocalChainClaim, - }, - { - // bob: outgoing their commit watch and see, they sweep - // on chain - // bob: incoming their commit watch and learn preimage - // carol: incoming our commit know preimage - name: "remote chain claim", - test: testMultiHopHtlcRemoteChainClaim, - }, - { - // bob: outgoing and incoming, sweep all on chain - name: "local htlc aggregation", - test: testMultiHopHtlcAggregation, - }, - } - - commitWithZeroConf := []struct { - commitType lnrpc.CommitmentType - zeroConf bool - }{ - { - commitType: lnrpc.CommitmentType_LEGACY, - zeroConf: false, - }, - { - commitType: lnrpc.CommitmentType_ANCHORS, - zeroConf: false, - }, - { - commitType: lnrpc.CommitmentType_ANCHORS, - zeroConf: true, - }, - { - commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, - zeroConf: false, - }, - { - commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, - zeroConf: true, - }, - } - - runTestCase := func(st *lntemp.HarnessTest, name string, tc testCase, - zeroConf bool) { - - // Create the nodes here so that separate logs will be created - // for Alice and Bob. - args := nodeArgsForCommitType(tc.commitType) - if zeroConf { - args = append( - args, "--protocol.option-scid-alias", - "--protocol.zero-conf", - ) - } - - alice := st.NewNode("Alice", args) - bob := st.NewNode("Bob", args) - st.ConnectNodes(alice, bob) - - // Start each test with the default static fee estimate. - st.SetFeeEstimate(12500) - - // Add test name to the logs. - alice.AddToLogf("Running test case: %s", name) - bob.AddToLogf("Running test case: %s", name) - - tc.test(st, alice, bob, tc.commitType, zeroConf) - } - - for _, subTest := range subTests { - subTest := subTest - - for _, typeAndConf := range commitWithZeroConf { - typeAndConf := typeAndConf - - subTest.commitType = typeAndConf.commitType - - name := fmt.Sprintf("%s/zeroconf=%v/committype=%v", - subTest.name, typeAndConf.zeroConf, - typeAndConf.commitType.String()) - - s := ht.Run(name, func(t1 *testing.T) { - st, cleanup := ht.Subtest(t1) - defer cleanup() - - runTestCase( - st, name, subTest, typeAndConf.zeroConf, - ) - }) - if !s { - return - } - } - } +var commitWithZeroConf = []struct { + commitType lnrpc.CommitmentType + zeroConf bool +}{ + { + commitType: lnrpc.CommitmentType_LEGACY, + zeroConf: false, + }, + { + commitType: lnrpc.CommitmentType_ANCHORS, + zeroConf: false, + }, + { + commitType: lnrpc.CommitmentType_ANCHORS, + zeroConf: true, + }, + { + commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, + zeroConf: false, + }, + { + commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, + zeroConf: true, + }, } // waitForInvoiceAccepted waits until the specified invoice moved to the @@ -258,116 +148,52 @@ func assertAllTxesSpendFrom(t *harnessTest, txes []*wire.MsgTx, } } -// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`. -func createThreeHopNetwork(ht *lntemp.HarnessTest, - alice, bob *node.HarnessNode, carolHodl bool, c lnrpc.CommitmentType, - zeroConf bool) (*lnrpc.ChannelPoint, - *lnrpc.ChannelPoint, *node.HarnessNode) { +// caseRunner defines a single test case runner. +type caseRunner func(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, + c lnrpc.CommitmentType, zeroConf bool) - ht.EnsureConnected(alice, bob) +// runMultiHopHtlcClaimTest is a helper method to build test cases based on +// different commitment types and zero-conf config and run them. +func runMultiHopHtlcClaimTest(ht *lntemp.HarnessTest, tester caseRunner) { + for _, typeAndConf := range commitWithZeroConf { + typeAndConf := typeAndConf + name := fmt.Sprintf("zeroconf=%v/committype=%v", + typeAndConf.zeroConf, typeAndConf.commitType.String()) - // We'll create a new node "carol" and have Bob connect to her. - // If the carolHodl flag is set, we'll make carol always hold onto the - // HTLC, this way it'll force Bob to go to chain to resolve the HTLC. - carolFlags := nodeArgsForCommitType(c) - if carolHodl { - carolFlags = append(carolFlags, "--hodl.exit-settle") + // Create the nodes here so that separate logs will be created + // for Alice and Bob. + args := nodeArgsForCommitType(typeAndConf.commitType) + if typeAndConf.zeroConf { + args = append( + args, "--protocol.option-scid-alias", + "--protocol.zero-conf", + ) + } + + s := ht.Run(name, func(t1 *testing.T) { + st, cleanup := ht.Subtest(t1) + defer cleanup() + + alice := st.NewNode("Alice", args) + bob := st.NewNode("Bob", args) + st.ConnectNodes(alice, bob) + + // Start each test with the default static fee estimate. + st.SetFeeEstimate(12500) + + // Add test name to the logs. + alice.AddToLogf("Running test case: %s", name) + bob.AddToLogf("Running test case: %s", name) + + tester( + st, alice, bob, + typeAndConf.commitType, typeAndConf.zeroConf, + ) + }) + if !s { + return + } } - - if zeroConf { - carolFlags = append( - carolFlags, "--protocol.option-scid-alias", - "--protocol.zero-conf", - ) - } - carol := ht.NewNode("Carol", carolFlags) - - ht.ConnectNodes(bob, carol) - - // Make sure there are enough utxos for anchoring. Because the anchor - // by itself often doesn't meet the dust limit, a utxo from the wallet - // needs to be attached as an additional input. This can still lead to - // a positively-yielding transaction. - for i := 0; i < 2; i++ { - ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice) - ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob) - ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol) - - // Mine 1 block to get the above coins confirmed. - ht.MineBlocksAssertNodesSync(1) - } - - // We'll start the test by creating a channel between Alice and Bob, - // which will act as the first leg for out multi-hop HTLC. - const chanAmt = 1000000 - var aliceFundingShim *lnrpc.FundingShim - var thawHeight uint32 - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - _, minerHeight := ht.Miner.GetBestBlock() - thawHeight = uint32(minerHeight + thawHeightDelta) - aliceFundingShim, _, _ = deriveFundingShim( - ht, alice, bob, chanAmt, thawHeight, true, - ) - } - - var ( - cancel context.CancelFunc - acceptStream rpc.AcceptorClient - ) - // If a zero-conf channel is being opened, the nodes are signalling the - // zero-conf feature bit. Setup a ChannelAcceptor for the fundee. - if zeroConf { - acceptStream, cancel = bob.RPC.ChannelAcceptor() - go acceptChannel(ht.T, true, acceptStream) - } - - aliceParams := lntemp.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - FundingShim: aliceFundingShim, - ZeroConf: zeroConf, - } - aliceChanPoint := ht.OpenChannel(alice, bob, aliceParams) - - // Remove the ChannelAcceptor for Bob. - if zeroConf { - cancel() - } - - // We'll then create a channel from Bob to Carol. After this channel is - // open, our topology looks like: A -> B -> C. - var bobFundingShim *lnrpc.FundingShim - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - bobFundingShim, _, _ = deriveFundingShim( - ht, bob, carol, chanAmt, thawHeight, true, - ) - } - - // Setup a ChannelAcceptor for Carol if a zero-conf channel open is - // being attempted. - if zeroConf { - acceptStream, cancel = carol.RPC.ChannelAcceptor() - go acceptChannel(ht.T, true, acceptStream) - } - - bobParams := lntemp.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - FundingShim: bobFundingShim, - ZeroConf: zeroConf, - } - bobChanPoint := ht.OpenChannel(bob, carol, bobParams) - - // Remove the ChannelAcceptor for Carol. - if zeroConf { - cancel() - } - - // Make sure alice and carol know each other's channels. - ht.AssertTopologyChannelOpen(alice, bobChanPoint) - ht.AssertTopologyChannelOpen(carol, aliceChanPoint) - - return aliceChanPoint, bobChanPoint, carol } // testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the @@ -375,7 +201,11 @@ func createThreeHopNetwork(ht *lntemp.HarnessTest, // it using the HTLC timeout transaction. Any dust HTLC's should be immediately // canceled backwards. Once the timeout has been reached, then we should sweep // it on-chain, and cancel the HTLC backwards. -func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest, +func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalTimeout) +} + +func runMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -540,7 +370,11 @@ func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest, // transaction. In this scenario, the node that sent the outgoing HTLC should // extract the preimage from the sweep transaction, and finish settling the // HTLC backwards into the route. -func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, +func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest(ht, runMultiHopReceiverChainClaim) +} + +func runMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -603,7 +437,6 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, // Now we'll mine enough blocks to prompt carol to actually go to the // chain in order to sweep her HTLC since the value is high enough. - // TODO(roasbeef): modify once go to chain policy changes numBlocks := padCLTV(uint32( invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta, )) @@ -666,8 +499,6 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, // second layer transactions. ht.MineBlocksAssertNodesSync(1) - // TODO(roasbeef): assert bob pending state as well - // Carol's pending channel report should now show two outputs under // limbo: her commitment output, as well as the second-layer claim // output, and the pending HTLC should also now be in stage 2. @@ -737,7 +568,13 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, // commitment on-chain early, then it eventually recognizes this HTLC as one // that's timed out. At this point, the node should timeout the HTLC using the // HTLC timeout transaction, then cancel it backwards as normal. -func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, +func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest( + ht, runMultiHopLocalForceCloseOnChainHtlcTimeout, + ) +} + +func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -784,6 +621,10 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, bob, bobChanPoint, hasAnchors, stream, ) + // Record how many blocks have mined. At this step + // AssertStreamChannelForceClosed mines one block. + blocksMined := uint32(1) + // If the channel closed has anchors, we should expect to see a sweep // transaction for Carol's anchor. htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0} @@ -801,9 +642,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { // The sweep is broadcast on the block immediately before the // CSV expires and the commitment was already mined inside - // closeChannelAndAssertType(), so mine one block less than - // defaultCSV in order to perform mempool assertions. - ht.MineBlocksAssertNodesSync(defaultCSV - 1) + // AssertStreamChannelForceClosed(), so mine one block less + // than defaultCSV in order to perform mempool assertions. + ht.MineBlocksAssertNodesSync(defaultCSV - blocksMined) commitSweepTx := ht.Miner.AssertOutpointInMempool( bobCommitOutpoint, @@ -811,19 +652,14 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, txid := commitSweepTx.TxHash() block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] ht.Miner.AssertTxInBlock(block, &txid) + + blocksMined = defaultCSV + 1 } // We'll now mine enough blocks for the HTLC to expire. After this, Bob - // should hand off the now expired HTLC output to the utxo nursery. We - // mine finalCltvDelta-1 blocks here as we've already mined 1 block - // while confirming the closing tx. - numBlocks := padCLTV(finalCltvDelta - 1) - if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - // Subtract the number of blocks already mined to confirm Bob's - // commit sweep. - numBlocks -= defaultCSV - } - ht.MineBlocksAssertNodesSync(numBlocks) + // should hand off the now expired HTLC output to the utxo nursery. + numBlocks := padCLTV(finalCltvDelta) + ht.MineBlocksAssertNodesSync(numBlocks - blocksMined) // Bob's pending channel report should show that he has a single HTLC // that's now in stage one. @@ -888,7 +724,13 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, // channel, then we properly timeout the HTLC directly on *their* commitment // transaction once the timeout has expired. Once we sweep the transaction, we // should also cancel back the initial HTLC. -func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, +func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest( + ht, runMultiHopRemoteForceCloseOnChainHtlcTimeout, + ) +} + +func runMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -989,18 +831,6 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, // Bob's sweeping transaction should now be found in the mempool at // this point. sweepTx := ht.Miner.AssertNumTxsInMempool(1)[0] - // The following issue is believed to have been resolved. Keep the - // original comments here for future reference in case anything goes - // wrong. - // - // If Bob's transaction isn't yet in the mempool, then due to - // internal message passing and the low period between blocks - // being mined, it may have been detected as a late - // registration. As a result, we'll mine another block and - // repeat the check. If it doesn't go through this time, then - // we'll fail. - // TODO(halseth): can we use waitForChannelPendingForceClose to - // avoid this hack? // If we mine an additional block, then this should confirm Bob's // transaction which sweeps the direct HTLC output. @@ -1050,7 +880,11 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, // we force close a channel with an incoming HTLC, and later find out the // preimage via the witness beacon, we properly settle the HTLC on-chain using // the HTLC success transaction in order to ensure we don't lose any funds. -func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest, +func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalChainClaim) +} + +func runMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -1147,7 +981,7 @@ func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest, // We'll now mine enough blocks so Carol decides that she needs to go // on-chain to claim the HTLC as Bob has been inactive. numBlocks := padCLTV(uint32(invoiceReq.CltvExpiry- - lncfg.DefaultIncomingBroadcastDelta) - 1) + lncfg.DefaultIncomingBroadcastDelta)) - 1 ht.MineBlocksAssertNodesSync(numBlocks) // Carol's commitment transaction should now be in the mempool. If @@ -1340,7 +1174,11 @@ func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest, // we found out the preimage via the witness beacon, we properly settle the // HTLC directly on-chain using the preimage in order to ensure that we don't // lose any funds. -func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, +func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest(ht, runMultiHopHtlcRemoteChainClaim) +} + +func runMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with @@ -1403,24 +1241,29 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, alice, aliceChanPoint, hasAnchors, closeStream, ) + // Record how many blocks have mined. At this step + // AssertStreamChannelForceClosed mines one block. + blocksMined := uint32(1) + // Wait for the channel to be marked pending force close. ht.AssertChannelPendingForceClose(alice, aliceChanPoint) - // After closeChannelAndAssertType returns, it has mined a block so now - // bob will attempt to redeem his anchor commitment (if the channel - // type is of that type). + // After AssertStreamChannelForceClosed returns, it has mined a block + // so now bob will attempt to redeem his anchor commitment (if the + // channel type is of that type). if hasAnchors { ht.Miner.AssertNumTxsInMempool(1) } if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { // Mine enough blocks for Alice to sweep her funds from the - // force closed channel. closeChannelAndAssertType() already - // mined a block containing the commitment tx and the commit - // sweep tx will be broadcast immediately before it can be - // included in a block, so mine one less than defaultCSV in + // force closed channel. AssertStreamChannelForceClosed() + // already mined a block containing the commitment tx and the + // commit sweep tx will be broadcast immediately before it can + // be included in a block, so mine one less than defaultCSV in // order to perform mempool assertions. - ht.MineBlocksAssertNodesSync(defaultCSV - 1) + ht.MineBlocksAssertNodesSync(defaultCSV - blocksMined) + blocksMined = defaultCSV // Alice should now sweep her funds. ht.Miner.AssertNumTxsInMempool(1) @@ -1439,12 +1282,9 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, // We'll now mine enough blocks so Carol decides that she needs to go // on-chain to claim the HTLC as Bob has been inactive. numBlocks := padCLTV(uint32( - invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta - 1, + invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta, )) - if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - numBlocks -= defaultCSV - } - ht.MineBlocksAssertNodesSync(numBlocks) + ht.MineBlocksAssertNodesSync(numBlocks - blocksMined) expectedTxes := 1 if hasAnchors { @@ -1604,7 +1444,11 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, // resolve them using the second level timeout and success transactions. In // case of anchor channels, the second-level spends can also be aggregated and // properly feebumped, so we'll check that as well. -func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, +func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest) { + runMultiHopHtlcClaimTest(ht, runMultiHopHtlcAggregation) +} + +func runMultiHopHtlcAggregation(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol. @@ -1623,14 +1467,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, const reBalanceAmt = 500_000 invoice := &lnrpc.Invoice{Value: reBalanceAmt} resp := carol.RPC.AddInvoice(invoice) - - sendReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: resp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - stream := alice.RPC.SendPayment(sendReq) - ht.AssertPaymentStatusFromStream(stream, lnrpc.Payment_SUCCEEDED) + ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest}) // With the network active, we'll now add a new hodl invoices at both // Alice's and Carol's end. Make sure the cltv expiry delta is large @@ -1675,7 +1512,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, payHash := preimage.Hash() invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ Value: invoiceAmt, - CltvExpiry: 2 * finalCltvDelta, + CltvExpiry: thawHeightDelta - 4, Hash: payHash[:], } aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq) @@ -1762,6 +1599,12 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, ) closeTxid := closeTx.TxHash() + // Restart Bob to increase the batch window duration so the sweeper + // will aggregate all the pending inputs. + ht.RestartNodeWithExtraArgs( + bob, []string{"--sweeper.batchwindowduration=15s"}, + ) + // Go through the closing transaction outputs, and make an index for // the HTLC outputs. successOuts := make(map[wire.OutPoint]struct{}) @@ -1866,8 +1709,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, // Mine a block to confirm the all the transactions, including Carol's // commitment tx, anchor tx(optional), and the second-level timeout and // success txes. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] - require.Len(ht, block.Transactions, expectedTxes+1) + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) // At this point, Bob should have broadcast his second layer success // transaction, and should have sent it to the nursery for incubation, @@ -1903,6 +1745,12 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, } } + // We now restart Bob with a much larger batch window duration since it + // takes some time to aggregate all the 10 inputs below. + ht.RestartNodeWithExtraArgs( + bob, []string{"--sweeper.batchwindowduration=45s"}, + ) + switch c { // In case this is a non-anchor channel type, we must mine 2 blocks, as // the nursery waits an extra block before sweeping. Before the blocks @@ -1962,7 +1810,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, // When we mine one additional block, that will confirm Bob's second // level sweep. Now Bob should have no pending channels anymore, as // this just resolved it by the confirmation of the sweep transaction. - block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] ht.Miner.AssertTxInBlock(block, &bobSweep) ht.AssertNumPendingForceClose(bob, 0) @@ -1976,3 +1824,115 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, // Coop close, no anchors. ht.CloseChannel(alice, aliceChanPoint) } + +// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`. +func createThreeHopNetwork(ht *lntemp.HarnessTest, + alice, bob *node.HarnessNode, carolHodl bool, c lnrpc.CommitmentType, + zeroConf bool) (*lnrpc.ChannelPoint, + *lnrpc.ChannelPoint, *node.HarnessNode) { + + ht.EnsureConnected(alice, bob) + + // We'll create a new node "carol" and have Bob connect to her. + // If the carolHodl flag is set, we'll make carol always hold onto the + // HTLC, this way it'll force Bob to go to chain to resolve the HTLC. + carolFlags := nodeArgsForCommitType(c) + if carolHodl { + carolFlags = append(carolFlags, "--hodl.exit-settle") + } + + if zeroConf { + carolFlags = append( + carolFlags, "--protocol.option-scid-alias", + "--protocol.zero-conf", + ) + } + carol := ht.NewNode("Carol", carolFlags) + + ht.ConnectNodes(bob, carol) + + // Make sure there are enough utxos for anchoring. Because the anchor + // by itself often doesn't meet the dust limit, a utxo from the wallet + // needs to be attached as an additional input. This can still lead to + // a positively-yielding transaction. + for i := 0; i < 2; i++ { + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice) + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob) + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol) + + // Mine 1 block to get the above coins confirmed. + ht.MineBlocksAssertNodesSync(1) + } + + // We'll start the test by creating a channel between Alice and Bob, + // which will act as the first leg for out multi-hop HTLC. + const chanAmt = 1000000 + var aliceFundingShim *lnrpc.FundingShim + var thawHeight uint32 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + _, minerHeight := ht.Miner.GetBestBlock() + thawHeight = uint32(minerHeight + thawHeightDelta) + aliceFundingShim, _, _ = deriveFundingShim( + ht, alice, bob, chanAmt, thawHeight, true, + ) + } + + var ( + cancel context.CancelFunc + acceptStream rpc.AcceptorClient + ) + // If a zero-conf channel is being opened, the nodes are signalling the + // zero-conf feature bit. Setup a ChannelAcceptor for the fundee. + if zeroConf { + acceptStream, cancel = bob.RPC.ChannelAcceptor() + go acceptChannel(ht.T, true, acceptStream) + } + + aliceParams := lntemp.OpenChannelParams{ + Amt: chanAmt, + CommitmentType: c, + FundingShim: aliceFundingShim, + ZeroConf: zeroConf, + } + aliceChanPoint := ht.OpenChannel(alice, bob, aliceParams) + + // Remove the ChannelAcceptor for Bob. + if zeroConf { + cancel() + } + + // We'll then create a channel from Bob to Carol. After this channel is + // open, our topology looks like: A -> B -> C. + var bobFundingShim *lnrpc.FundingShim + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + bobFundingShim, _, _ = deriveFundingShim( + ht, bob, carol, chanAmt, thawHeight, true, + ) + } + + // Setup a ChannelAcceptor for Carol if a zero-conf channel open is + // being attempted. + if zeroConf { + acceptStream, cancel = carol.RPC.ChannelAcceptor() + go acceptChannel(ht.T, true, acceptStream) + } + + bobParams := lntemp.OpenChannelParams{ + Amt: chanAmt, + CommitmentType: c, + FundingShim: bobFundingShim, + ZeroConf: zeroConf, + } + bobChanPoint := ht.OpenChannel(bob, carol, bobParams) + + // Remove the ChannelAcceptor for Carol. + if zeroConf { + cancel() + } + + // Make sure alice and carol know each other's channels. + ht.AssertTopologyChannelOpen(alice, bobChanPoint) + ht.AssertTopologyChannelOpen(carol, aliceChanPoint) + + return aliceChanPoint, bobChanPoint, carol +}