itest: refactor testMultiHopLocalForceCloseOnChainHtlcTimeout
This commit is contained in:
4 files changed
+66
-132
No files matched your search
+1
-1
@@ -809,7 +809,7 @@ func (h *HarnessTest) CloseChannel(hn *node.HarnessNode,
|
|||||||
|
|
||||||
stream, _ := h.CloseChannelAssertPending(hn, cp, false)
|
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
|
// ForceCloseChannel attempts to force close a non-anchored channel identified
|
||||||
|
|||||||
@@ -454,9 +454,15 @@ func (h *HarnessTest) AssertNumPendingForceClose(hn *node.HarnessNode,
|
|||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
// assertChannelCoopClosed asserts that the channel is properly cleaned up
|
// AssertStreamChannelCoopClosed reads an update from the close channel client
|
||||||
// after initiating a cooperative close.
|
// stream and asserts that the mempool state and node's topology match a coop
|
||||||
func (h *HarnessTest) assertChannelCoopClosed(hn *node.HarnessNode,
|
// 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,
|
cp *lnrpc.ChannelPoint, anchors bool,
|
||||||
stream rpc.CloseChanClient) *chainhash.Hash {
|
stream rpc.CloseChanClient) *chainhash.Hash {
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package itest
|
package itest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntemp"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntemp/node"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,22 +15,16 @@ import (
|
|||||||
// commitment on-chain early, then it eventually recognizes this HTLC as one
|
// 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
|
// that's timed out. At this point, the node should timeout the HTLC using the
|
||||||
// HTLC timeout transaction, then cancel it backwards as normal.
|
// HTLC timeout transaction, then cancel it backwards as normal.
|
||||||
func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
|
||||||
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
||||||
zeroConf bool) {
|
|
||||||
|
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
// First, we'll create a three hop network: Alice -> Bob -> Carol, with
|
// First, we'll create a three hop network: Alice -> Bob -> Carol, with
|
||||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||||
// self.
|
// self.
|
||||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetworkOld(
|
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||||
t, net, alice, bob, true, c, zeroConf,
|
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
|
// 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
|
// to Carol. As Carol is in hodl mode, she won't settle this HTLC which
|
||||||
// opens up the base for out tests.
|
// opens up the base for out tests.
|
||||||
@@ -41,59 +32,39 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
|||||||
finalCltvDelta = 40
|
finalCltvDelta = 40
|
||||||
htlcAmt = btcutil.Amount(300_000)
|
htlcAmt = btcutil.Amount(300_000)
|
||||||
)
|
)
|
||||||
ctx, cancel := context.WithCancel(ctxb)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// We'll now send a single HTLC across our multi-hop network.
|
// We'll now send a single HTLC across our multi-hop network.
|
||||||
carolPubKey := carol.PubKey[:]
|
carolPubKey := carol.PubKey[:]
|
||||||
payHash := makeFakePayHash(t)
|
payHash := ht.Random32Bytes()
|
||||||
_, err := alice.RouterClient.SendPaymentV2(
|
req := &routerrpc.SendPaymentRequest{
|
||||||
ctx, &routerrpc.SendPaymentRequest{
|
Dest: carolPubKey,
|
||||||
Dest: carolPubKey,
|
Amt: int64(htlcAmt),
|
||||||
Amt: int64(htlcAmt),
|
PaymentHash: payHash,
|
||||||
PaymentHash: payHash,
|
FinalCltvDelta: finalCltvDelta,
|
||||||
FinalCltvDelta: finalCltvDelta,
|
TimeoutSeconds: 60,
|
||||||
TimeoutSeconds: 60,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
}
|
||||||
},
|
alice.RPC.SendPayment(req)
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Once the HTLC has cleared, all channels in our mini network should
|
// Once the HTLC has cleared, all channels in our mini network should
|
||||||
// have the it locked in.
|
// have the it locked in.
|
||||||
nodes := []*lntest.HarnessNode{alice, bob, carol}
|
ht.AssertActiveHtlcs(alice, payHash)
|
||||||
err = wait.NoError(func() error {
|
ht.AssertActiveHtlcs(bob, payHash)
|
||||||
return assertActiveHtlcs(nodes, payHash)
|
ht.AssertActiveHtlcs(carol, payHash)
|
||||||
}, defaultTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Increase the fee estimate so that the following force close tx will
|
// Increase the fee estimate so that the following force close tx will
|
||||||
// be cpfp'ed.
|
// be cpfp'ed.
|
||||||
net.SetFeeEstimate(30000)
|
ht.SetFeeEstimate(30000)
|
||||||
|
|
||||||
// Now that all parties have the HTLC locked in, we'll immediately
|
// Now that all parties have the HTLC locked in, we'll immediately
|
||||||
// force close the Bob -> Carol channel. This should trigger contract
|
// force close the Bob -> Carol channel. This should trigger contract
|
||||||
// resolution mode for both of them.
|
// resolution mode for both of them.
|
||||||
hasAnchors := commitTypeHasAnchors(c)
|
hasAnchors := commitTypeHasAnchors(c)
|
||||||
closeTx := closeChannelAndAssertType(
|
stream, _ := ht.CloseChannelAssertPending(bob, bobChanPoint, true)
|
||||||
t, net, bob, bobChanPoint, hasAnchors, 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
|
// If the channel closed has anchors, we should expect to see a sweep
|
||||||
// transaction for Carol's anchor.
|
// transaction for Carol's anchor.
|
||||||
htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0}
|
htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0}
|
||||||
@@ -101,8 +72,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
|||||||
if hasAnchors {
|
if hasAnchors {
|
||||||
htlcOutpoint.Index = 2
|
htlcOutpoint.Index = 2
|
||||||
bobCommitOutpoint.Index = 3
|
bobCommitOutpoint.Index = 3
|
||||||
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
ht.Miner.AssertNumTxsInMempool(1)
|
||||||
require.NoError(t.t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before the HTLC times out, we'll need to assert that Bob broadcasts a
|
// 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
|
// CSV expires and the commitment was already mined inside
|
||||||
// closeChannelAndAssertType(), so mine one block less than
|
// closeChannelAndAssertType(), so mine one block less than
|
||||||
// defaultCSV in order to perform mempool assertions.
|
// defaultCSV in order to perform mempool assertions.
|
||||||
_, err = net.Miner.Client.Generate(defaultCSV - 1)
|
ht.MineBlocksAssertNodesSync(defaultCSV - 1)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
commitSweepTx := assertSpendingTxInMempool(
|
commitSweepTx := ht.Miner.AssertOutpointInMempool(
|
||||||
t, net.Miner.Client, minerMempoolTimeout,
|
|
||||||
bobCommitOutpoint,
|
bobCommitOutpoint,
|
||||||
)
|
)
|
||||||
blocks := mineBlocks(t, net, 1, 1)
|
txid := commitSweepTx.TxHash()
|
||||||
assertTxInBlock(t, blocks[0], &commitSweepTx)
|
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
|
// 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.
|
// commit sweep.
|
||||||
numBlocks -= defaultCSV
|
numBlocks -= defaultCSV
|
||||||
}
|
}
|
||||||
_, err = net.Miner.Client.Generate(numBlocks)
|
ht.MineBlocksAssertNodesSync(numBlocks)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Bob's pending channel report should show that he has a single HTLC
|
// Bob's pending channel report should show that he has a single HTLC
|
||||||
// that's now in stage one.
|
// that's now in stage one.
|
||||||
err = waitForNumChannelPendingForceClose(
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 1)
|
||||||
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)
|
|
||||||
|
|
||||||
// We should also now find a transaction in the mempool, as Bob should
|
// We should also now find a transaction in the mempool, as Bob should
|
||||||
// have broadcast his second layer timeout transaction.
|
// have broadcast his second layer timeout transaction.
|
||||||
timeoutTx := assertSpendingTxInMempool(
|
timeoutTx := ht.Miner.AssertOutpointInMempool(htlcOutpoint).TxHash()
|
||||||
t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Next, we'll mine an additional block. This should serve to confirm
|
// Next, we'll mine an additional block. This should serve to confirm
|
||||||
// the second layer timeout transaction.
|
// the second layer timeout transaction.
|
||||||
block := mineBlocks(t, net, 1, 1)[0]
|
block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
assertTxInBlock(t, block, &timeoutTx)
|
ht.Miner.AssertTxInBlock(block, &timeoutTx)
|
||||||
|
|
||||||
// With the second layer timeout transaction confirmed, Bob should have
|
// With the second layer timeout transaction confirmed, Bob should have
|
||||||
// canceled backwards the HTLC that carol sent.
|
// canceled backwards the HTLC that carol sent.
|
||||||
nodes = []*lntest.HarnessNode{alice}
|
ht.AssertNumActiveHtlcs(alice, 0)
|
||||||
err = wait.NoError(func() error {
|
|
||||||
return assertNumActiveHtlcs(nodes, 0)
|
|
||||||
}, defaultTimeout)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Additionally, Bob should now show that HTLC as being advanced to the
|
// Additionally, Bob should now show that HTLC as being advanced to the
|
||||||
// second stage.
|
// second stage.
|
||||||
err = waitForNumChannelPendingForceClose(
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2)
|
||||||
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)
|
|
||||||
|
|
||||||
// Bob should now broadcast a transaction that sweeps certain inputs
|
// Bob should now broadcast a transaction that sweeps certain inputs
|
||||||
// depending on the commitment type. We'll need to mine some blocks
|
// depending on the commitment type. We'll need to mine some blocks
|
||||||
// before the broadcast is possible.
|
// before the broadcast is possible.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
resp := bob.RPC.PendingChannels()
|
||||||
resp, err := bob.PendingChannels(ctxt, &lnrpc.PendingChannelsRequest{})
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
require.Len(t.t, resp.PendingForceClosingChannels, 1)
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
||||||
forceCloseChan := resp.PendingForceClosingChannels[0]
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
||||||
require.Len(t.t, forceCloseChan.PendingHtlcs, 1)
|
require.Len(ht, forceCloseChan.PendingHtlcs, 1)
|
||||||
pendingHtlc := forceCloseChan.PendingHtlcs[0]
|
pendingHtlc := forceCloseChan.PendingHtlcs[0]
|
||||||
require.Positive(t.t, pendingHtlc.BlocksTilMaturity)
|
require.Positive(ht, pendingHtlc.BlocksTilMaturity)
|
||||||
numBlocks = uint32(pendingHtlc.BlocksTilMaturity)
|
numBlocks = uint32(pendingHtlc.BlocksTilMaturity)
|
||||||
|
|
||||||
_, err = net.Miner.Client.Generate(numBlocks)
|
ht.MineBlocksAssertNodesSync(numBlocks)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Now that the CSV/CLTV timelock has expired, the transaction should
|
// Now that the CSV/CLTV timelock has expired, the transaction should
|
||||||
// either only sweep the HTLC timeout transaction, or sweep both the
|
// either only sweep the HTLC timeout transaction, or sweep both the
|
||||||
// HTLC timeout transaction and Bob's commit output depending on the
|
// HTLC timeout transaction and Bob's commit output depending on the
|
||||||
// commitment type.
|
// commitment type.
|
||||||
htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0}
|
htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0}
|
||||||
expectedInputsSwept := []wire.OutPoint{htlcTimeoutOutpoint}
|
sweepTx := ht.Miner.AssertOutpointInMempool(
|
||||||
|
htlcTimeoutOutpoint,
|
||||||
|
).TxHash()
|
||||||
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
||||||
expectedInputsSwept = append(expectedInputsSwept, bobCommitOutpoint)
|
ht.Miner.AssertOutpointInMempool(bobCommitOutpoint)
|
||||||
}
|
}
|
||||||
sweepTx := assertSpendingTxInMempool(
|
|
||||||
t, net.Miner.Client, minerMempoolTimeout, expectedInputsSwept...,
|
block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
)
|
ht.Miner.AssertTxInBlock(block, &sweepTx)
|
||||||
block = mineBlocks(t, net, 1, 1)[0]
|
|
||||||
assertTxInBlock(t, block, &sweepTx)
|
|
||||||
|
|
||||||
// At this point, Bob should no longer show any channels as pending
|
// At this point, Bob should no longer show any channels as pending
|
||||||
// close.
|
// close.
|
||||||
err = waitForNumChannelPendingForceClose(bob, 0, nil)
|
ht.AssertNumPendingForceClose(bob, 0)
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Coop close, no anchors.
|
// Coop close, no anchors.
|
||||||
closeChannelAndAssertType(t, net, alice, aliceChanPoint, false, false)
|
ht.CloseChannel(alice, aliceChanPoint)
|
||||||
}
|
}
|
||||||
@@ -39,12 +39,12 @@ func testMultiHopHtlcClaims(ht *lntemp.HarnessTest) {
|
|||||||
name: "receiver chain claim",
|
name: "receiver chain claim",
|
||||||
test: testMultiHopReceiverChainClaim,
|
test: testMultiHopReceiverChainClaim,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// // bob: outgoing our commit watch and see timeout
|
// bob: outgoing our commit watch and see timeout
|
||||||
// // carol: incoming their commit watch and see timeout
|
// carol: incoming their commit watch and see timeout
|
||||||
// name: "local force close on-chain htlc timeout",
|
name: "local force close on-chain htlc timeout",
|
||||||
// test: testMultiHopLocalForceCloseOnChainHtlcTimeout,
|
test: testMultiHopLocalForceCloseOnChainHtlcTimeout,
|
||||||
// },
|
},
|
||||||
// {
|
// {
|
||||||
// // bob: outgoing their commit watch and see timeout
|
// // bob: outgoing their commit watch and see timeout
|
||||||
// // carol: incoming our commit watch and see timeout
|
// // carol: incoming our commit watch and see timeout
|
||||||
|
|||||||
Reference in new issue
Block a user