diff --git a/lntemp/harness.go b/lntemp/harness.go index 9072f0306..92253be1c 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -809,7 +809,7 @@ func (h *HarnessTest) CloseChannel(hn *node.HarnessNode, stream, _ := h.CloseChannelAssertPending(hn, cp, false) - return h.assertChannelCoopClosed(hn, cp, false, stream) + return h.AssertStreamChannelCoopClosed(hn, cp, false, stream) } // ForceCloseChannel attempts to force close a non-anchored channel identified diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 6966b24c2..289186adb 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -454,9 +454,15 @@ func (h *HarnessTest) AssertNumPendingForceClose(hn *node.HarnessNode, return channels } -// assertChannelCoopClosed asserts that the channel is properly cleaned up -// after initiating a cooperative close. -func (h *HarnessTest) assertChannelCoopClosed(hn *node.HarnessNode, +// AssertStreamChannelCoopClosed reads an update from the close channel client +// stream and asserts that the mempool state and node's topology match a coop +// close. In specific, +// - assert the channel is waiting close and has the expected ChanStatusFlags. +// - assert the mempool has the closing txes and anchor sweeps. +// - mine a block and assert the closing txid is mined. +// - assert the node has zero waiting close channels. +// - assert the node has seen the channel close update. +func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, anchors bool, stream rpc.CloseChanClient) *chainhash.Hash { diff --git a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go index 2faf8a9ac..a97b6e125 100644 --- a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go @@ -1,15 +1,12 @@ package itest import ( - "context" - "fmt" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) @@ -18,22 +15,16 @@ import ( // 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(net *lntest.NetworkHarness, - t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType, - zeroConf bool) { - - ctxb := context.Background() +func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, + alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { // First, we'll create a three hop network: Alice -> Bob -> Carol, with // Carol refusing to actually settle or directly cancel any HTLC's // self. - aliceChanPoint, bobChanPoint, carol := createThreeHopNetworkOld( - t, net, alice, bob, true, c, zeroConf, + aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork( + ht, alice, bob, true, c, zeroConf, ) - // Clean up carol's node when the test finishes. - defer shutdownAndAssert(net, t, carol) - // With our channels set up, we'll then send a single HTLC from Alice // to Carol. As Carol is in hodl mode, she won't settle this HTLC which // opens up the base for out tests. @@ -41,59 +32,39 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, finalCltvDelta = 40 htlcAmt = btcutil.Amount(300_000) ) - ctx, cancel := context.WithCancel(ctxb) - defer cancel() // We'll now send a single HTLC across our multi-hop network. carolPubKey := carol.PubKey[:] - payHash := makeFakePayHash(t) - _, err := alice.RouterClient.SendPaymentV2( - ctx, &routerrpc.SendPaymentRequest{ - Dest: carolPubKey, - Amt: int64(htlcAmt), - PaymentHash: payHash, - FinalCltvDelta: finalCltvDelta, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - require.NoError(t.t, err) + payHash := ht.Random32Bytes() + req := &routerrpc.SendPaymentRequest{ + Dest: carolPubKey, + Amt: int64(htlcAmt), + PaymentHash: payHash, + FinalCltvDelta: finalCltvDelta, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + alice.RPC.SendPayment(req) // Once the HTLC has cleared, all channels in our mini network should // have the it locked in. - nodes := []*lntest.HarnessNode{alice, bob, carol} - err = wait.NoError(func() error { - return assertActiveHtlcs(nodes, payHash) - }, defaultTimeout) - require.NoError(t.t, err) + ht.AssertActiveHtlcs(alice, payHash) + ht.AssertActiveHtlcs(bob, payHash) + ht.AssertActiveHtlcs(carol, payHash) // Increase the fee estimate so that the following force close tx will // be cpfp'ed. - net.SetFeeEstimate(30000) + ht.SetFeeEstimate(30000) // Now that all parties have the HTLC locked in, we'll immediately // force close the Bob -> Carol channel. This should trigger contract // resolution mode for both of them. hasAnchors := commitTypeHasAnchors(c) - closeTx := closeChannelAndAssertType( - t, net, bob, bobChanPoint, hasAnchors, true, + stream, _ := ht.CloseChannelAssertPending(bob, bobChanPoint, true) + closeTx := ht.AssertStreamChannelForceClosed( + bob, bobChanPoint, hasAnchors, stream, ) - // At this point, Bob should have a pending force close channel as he - // just went to chain. - err = waitForNumChannelPendingForceClose( - bob, 1, func(c *lnrpcForceCloseChannel) error { - if c.LimboBalance == 0 { - return fmt.Errorf("bob should have nonzero "+ - "limbo balance instead has: %v", - c.LimboBalance) - } - - return nil - }, - ) - require.NoError(t.t, err) - // 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} @@ -101,8 +72,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, if hasAnchors { htlcOutpoint.Index = 2 bobCommitOutpoint.Index = 3 - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) + ht.Miner.AssertNumTxsInMempool(1) } // Before the HTLC times out, we'll need to assert that Bob broadcasts a @@ -114,15 +84,14 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // CSV expires and the commitment was already mined inside // closeChannelAndAssertType(), so mine one block less than // defaultCSV in order to perform mempool assertions. - _, err = net.Miner.Client.Generate(defaultCSV - 1) - require.NoError(t.t, err) + ht.MineBlocksAssertNodesSync(defaultCSV - 1) - commitSweepTx := assertSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, + commitSweepTx := ht.Miner.AssertOutpointInMempool( bobCommitOutpoint, ) - blocks := mineBlocks(t, net, 1, 1) - assertTxInBlock(t, blocks[0], &commitSweepTx) + txid := commitSweepTx.TxHash() + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, &txid) } // We'll now mine enough blocks for the HTLC to expire. After this, Bob @@ -133,103 +102,62 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // commit sweep. numBlocks -= defaultCSV } - _, err = net.Miner.Client.Generate(numBlocks) - require.NoError(t.t, err) + ht.MineBlocksAssertNodesSync(numBlocks) // Bob's pending channel report should show that he has a single HTLC // that's now in stage one. - err = waitForNumChannelPendingForceClose( - bob, 1, func(c *lnrpcForceCloseChannel) error { - if len(c.PendingHtlcs) != 1 { - return fmt.Errorf("bob should have pending " + - "htlc but doesn't") - } - - if c.PendingHtlcs[0].Stage != 1 { - return fmt.Errorf("bob's htlc should have "+ - "advanced to the first stage: %v", err) - } - - return nil - }, - ) - require.NoError(t.t, err) + ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 1) // We should also now find a transaction in the mempool, as Bob should // have broadcast his second layer timeout transaction. - timeoutTx := assertSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint, - ) + timeoutTx := ht.Miner.AssertOutpointInMempool(htlcOutpoint).TxHash() // Next, we'll mine an additional block. This should serve to confirm // the second layer timeout transaction. - block := mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, &timeoutTx) + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, &timeoutTx) // With the second layer timeout transaction confirmed, Bob should have // canceled backwards the HTLC that carol sent. - nodes = []*lntest.HarnessNode{alice} - err = wait.NoError(func() error { - return assertNumActiveHtlcs(nodes, 0) - }, defaultTimeout) - require.NoError(t.t, err) + ht.AssertNumActiveHtlcs(alice, 0) // Additionally, Bob should now show that HTLC as being advanced to the // second stage. - err = waitForNumChannelPendingForceClose( - bob, 1, func(c *lnrpcForceCloseChannel) error { - if len(c.PendingHtlcs) != 1 { - return fmt.Errorf("bob should have pending " + - "htlc but doesn't") - } - - if c.PendingHtlcs[0].Stage != 2 { - return fmt.Errorf("bob's htlc should have "+ - "advanced to the second stage: %v", err) - } - - return nil - }, - ) - require.NoError(t.t, err) + ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2) // Bob should now broadcast a transaction that sweeps certain inputs // depending on the commitment type. We'll need to mine some blocks // before the broadcast is possible. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := bob.PendingChannels(ctxt, &lnrpc.PendingChannelsRequest{}) - require.NoError(t.t, err) + resp := bob.RPC.PendingChannels() - require.Len(t.t, resp.PendingForceClosingChannels, 1) + require.Len(ht, resp.PendingForceClosingChannels, 1) forceCloseChan := resp.PendingForceClosingChannels[0] - require.Len(t.t, forceCloseChan.PendingHtlcs, 1) + require.Len(ht, forceCloseChan.PendingHtlcs, 1) pendingHtlc := forceCloseChan.PendingHtlcs[0] - require.Positive(t.t, pendingHtlc.BlocksTilMaturity) + require.Positive(ht, pendingHtlc.BlocksTilMaturity) numBlocks = uint32(pendingHtlc.BlocksTilMaturity) - _, err = net.Miner.Client.Generate(numBlocks) - require.NoError(t.t, err) + ht.MineBlocksAssertNodesSync(numBlocks) // Now that the CSV/CLTV timelock has expired, the transaction should // either only sweep the HTLC timeout transaction, or sweep both the // HTLC timeout transaction and Bob's commit output depending on the // commitment type. htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0} - expectedInputsSwept := []wire.OutPoint{htlcTimeoutOutpoint} + sweepTx := ht.Miner.AssertOutpointInMempool( + htlcTimeoutOutpoint, + ).TxHash() if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - expectedInputsSwept = append(expectedInputsSwept, bobCommitOutpoint) + ht.Miner.AssertOutpointInMempool(bobCommitOutpoint) } - sweepTx := assertSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, expectedInputsSwept..., - ) - block = mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, &sweepTx) + + block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, &sweepTx) // At this point, Bob should no longer show any channels as pending // close. - err = waitForNumChannelPendingForceClose(bob, 0, nil) - require.NoError(t.t, err) + ht.AssertNumPendingForceClose(bob, 0) // Coop close, no anchors. - closeChannelAndAssertType(t, net, alice, aliceChanPoint, false, false) + ht.CloseChannel(alice, aliceChanPoint) } diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index 856691b92..554de89d9 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -39,12 +39,12 @@ func testMultiHopHtlcClaims(ht *lntemp.HarnessTest) { 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 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