lntest+itest: flatten testMultiHopRemoteForceCloseOnChainHtlcTimeout
This commit is contained in:
4 files changed
+323
-217
No files matched your search
@@ -149,10 +149,6 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "addpeer config",
|
Name: "addpeer config",
|
||||||
TestFunc: testAddPeerConfig,
|
TestFunc: testAddPeerConfig,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "multi hop remote force close on-chain htlc timeout",
|
|
||||||
TestFunc: testMultiHopRemoteForceCloseOnChainHtlcTimeout,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "private channel update policy",
|
Name: "private channel update policy",
|
||||||
TestFunc: testUpdateChannelPolicyForPrivateChannel,
|
TestFunc: testUpdateChannelPolicyForPrivateChannel,
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{
|
|||||||
Name: "multihop local force close before timeout leased",
|
Name: "multihop local force close before timeout leased",
|
||||||
TestFunc: testLocalForceCloseBeforeTimeoutLeased,
|
TestFunc: testLocalForceCloseBeforeTimeoutLeased,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "multihop remote force close before timeout anchor",
|
||||||
|
TestFunc: testRemoteForceCloseBeforeTimeoutAnchor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "multihop remote force close before timeout simple taproot",
|
||||||
|
TestFunc: testRemoteForceCloseBeforeTimeoutSimpleTaproot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "multihop remote force close before timeout leased",
|
||||||
|
TestFunc: testRemoteForceCloseBeforeTimeoutLeased,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// testLocalClaimOutgoingHTLCAnchor tests `runLocalClaimOutgoingHTLC` with
|
// testLocalClaimOutgoingHTLCAnchor tests `runLocalClaimOutgoingHTLC` with
|
||||||
@@ -1089,3 +1101,310 @@ func runLocalForceCloseBeforeHtlcTimeout(ht *lntest.HarnessTest,
|
|||||||
// close.
|
// close.
|
||||||
ht.AssertNumPendingForceClose(bob, 0)
|
ht.AssertNumPendingForceClose(bob, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testRemoteForceCloseBeforeTimeoutAnchor tests
|
||||||
|
// `runRemoteForceCloseBeforeHtlcTimeout` with anchor channel.
|
||||||
|
func testRemoteForceCloseBeforeTimeoutAnchor(ht *lntest.HarnessTest) {
|
||||||
|
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// anchor channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{Amt: chanAmt}
|
||||||
|
|
||||||
|
cfg := node.CfgAnchor
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.Run("zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// zero-conf anchor channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
ZeroConf: true,
|
||||||
|
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Carol's node config to enable zero-conf and anchor.
|
||||||
|
cfg := node.CfgZeroConf
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testRemoteForceCloseBeforeTimeoutSimpleTaproot tests
|
||||||
|
// `runLocalForceCloseBeforeHtlcTimeout` with simple taproot channel.
|
||||||
|
func testRemoteForceCloseBeforeTimeoutSimpleTaproot(ht *lntest.HarnessTest) {
|
||||||
|
c := lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||||
|
|
||||||
|
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// simple taproot channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
CommitmentType: c,
|
||||||
|
Private: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := node.CfgSimpleTaproot
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.Run("zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// zero-conf simple taproot channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
ZeroConf: true,
|
||||||
|
CommitmentType: c,
|
||||||
|
Private: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Carol's node config to enable zero-conf and leased
|
||||||
|
// channel.
|
||||||
|
cfg := node.CfgSimpleTaproot
|
||||||
|
cfg = append(cfg, node.CfgZeroConf...)
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testRemoteForceCloseBeforeTimeoutLeased tests
|
||||||
|
// `runRemoteForceCloseBeforeHtlcTimeout` with script enforced lease channel.
|
||||||
|
func testRemoteForceCloseBeforeTimeoutLeased(ht *lntest.HarnessTest) {
|
||||||
|
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// leased channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
CommitmentType: leasedType,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := node.CfgLeased
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.Run("zero conf", func(t *testing.T) {
|
||||||
|
st := ht.Subtest(t)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||||
|
// zero-conf anchor channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
params := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
ZeroConf: true,
|
||||||
|
CommitmentType: leasedType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Carol's node config to enable zero-conf and leased
|
||||||
|
// channel.
|
||||||
|
cfg := node.CfgLeased
|
||||||
|
cfg = append(cfg, node.CfgZeroConf...)
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||||
|
|
||||||
|
runRemoteForceCloseBeforeHtlcTimeout(st, cfgs, params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRemoteForceCloseBeforeHtlcTimeout tests that if we extend a multi-hop
|
||||||
|
// HTLC, and the final destination of the HTLC force closes the 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 runRemoteForceCloseBeforeHtlcTimeout(ht *lntest.HarnessTest,
|
||||||
|
cfgs [][]string, params lntest.OpenChannelParams) {
|
||||||
|
|
||||||
|
// Set the min relay feerate to be 10 sat/vbyte so the non-CPFP anchor
|
||||||
|
// is never swept.
|
||||||
|
//
|
||||||
|
// TODO(yy): delete this line once the normal anchor sweeping is
|
||||||
|
// removed.
|
||||||
|
ht.SetMinRelayFeerate(10_000)
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol.
|
||||||
|
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params)
|
||||||
|
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||||
|
bobChanPoint := chanPoints[1]
|
||||||
|
|
||||||
|
// If this is a taproot channel, then we'll need to make some manual
|
||||||
|
// route hints so Alice can actually find a route.
|
||||||
|
var routeHints []*lnrpc.RouteHint
|
||||||
|
if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||||
|
routeHints = makeRouteHints(bob, carol, params.ZeroConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
var preimage lntypes.Preimage
|
||||||
|
copy(preimage[:], ht.Random32Bytes())
|
||||||
|
payHash := preimage.Hash()
|
||||||
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||||
|
Value: int64(htlcAmt),
|
||||||
|
CltvExpiry: finalCltvDelta,
|
||||||
|
Hash: payHash[:],
|
||||||
|
RouteHints: routeHints,
|
||||||
|
}
|
||||||
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
||||||
|
|
||||||
|
// Subscribe the invoice.
|
||||||
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||||
|
|
||||||
|
req := &routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: carolInvoice.PaymentRequest,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
}
|
||||||
|
alice.RPC.SendPayment(req)
|
||||||
|
|
||||||
|
// Once the HTLC has cleared, all the nodes in our mini network should
|
||||||
|
// show that the HTLC has been locked in.
|
||||||
|
ht.AssertActiveHtlcs(alice, payHash[:])
|
||||||
|
ht.AssertActiveHtlcs(bob, payHash[:])
|
||||||
|
ht.AssertActiveHtlcs(carol, payHash[:])
|
||||||
|
|
||||||
|
// At this point, we'll now instruct Carol to force close the tx. This
|
||||||
|
// will let us exercise that Bob is able to sweep the expired HTLC on
|
||||||
|
// Carol's version of the commitment tx.
|
||||||
|
closeStream, _ := ht.CloseChannelAssertPending(
|
||||||
|
carol, bobChanPoint, true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// For anchor channels, the anchor won't be used for CPFP because
|
||||||
|
// channel arbitrator thinks Carol doesn't have preimage for her
|
||||||
|
// incoming HTLC on the commitment transaction Bob->Carol. Although
|
||||||
|
// Carol created this invoice, because it's a hold invoice, the
|
||||||
|
// preimage won't be generated automatically.
|
||||||
|
ht.AssertStreamChannelForceClosed(
|
||||||
|
carol, bobChanPoint, true, closeStream,
|
||||||
|
)
|
||||||
|
|
||||||
|
// At this point, Bob should have a pending force close channel as
|
||||||
|
// Carol has gone directly to chain.
|
||||||
|
ht.AssertNumPendingForceClose(bob, 1)
|
||||||
|
|
||||||
|
// Carol will offer her anchor to her sweeper.
|
||||||
|
ht.AssertNumPendingSweeps(carol, 1)
|
||||||
|
|
||||||
|
// Bob should offered the anchor output to his sweeper.
|
||||||
|
if params.CommitmentType == leasedType {
|
||||||
|
// For script enforced lease channels, Bob can sweep his anchor
|
||||||
|
// output immediately although it will be skipped due to it
|
||||||
|
// being uneconomical. His to_local output is CLTV locked so it
|
||||||
|
// cannot be swept yet.
|
||||||
|
ht.AssertNumPendingSweeps(bob, 1)
|
||||||
|
} else {
|
||||||
|
// For non-leased channels, Bob can sweep his commit and anchor
|
||||||
|
// outputs immediately.
|
||||||
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// We expect to see only one sweeping tx to be published from
|
||||||
|
// Bob, which sweeps his to_local output. His anchor output
|
||||||
|
// won't be swept due it being uneconomical. For Carol, since
|
||||||
|
// her anchor is not used for CPFP, it'd be also uneconomical
|
||||||
|
// to sweep so it will fail.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we'll mine enough blocks for the HTLC to expire. At this
|
||||||
|
// point, Bob should hand off the output to his sweeper, which will
|
||||||
|
// broadcast a sweep transaction.
|
||||||
|
resp := ht.AssertNumPendingForceClose(bob, 1)[0]
|
||||||
|
require.Equal(ht, 1, len(resp.PendingHtlcs))
|
||||||
|
|
||||||
|
ht.Logf("Bob's timelock to_local output=%v, timelock on second stage "+
|
||||||
|
"htlc=%v", resp.BlocksTilMaturity,
|
||||||
|
resp.PendingHtlcs[0].BlocksTilMaturity)
|
||||||
|
|
||||||
|
ht.MineBlocks(int(resp.PendingHtlcs[0].BlocksTilMaturity))
|
||||||
|
|
||||||
|
// If we check Bob's pending channel report, it should show that he has
|
||||||
|
// a single HTLC that's now in the second stage, as it skipped the
|
||||||
|
// initial first stage since this is a direct HTLC.
|
||||||
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2)
|
||||||
|
|
||||||
|
// Bob should have two pending sweep requests,
|
||||||
|
// 1. the uneconomical anchor sweep.
|
||||||
|
// 2. the direct timeout sweep.
|
||||||
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// Bob's sweeping tx should now be found in the mempool.
|
||||||
|
sweepTx := ht.AssertNumTxsInMempool(1)[0]
|
||||||
|
|
||||||
|
// If we mine an additional block, then this should confirm Bob's tx
|
||||||
|
// which sweeps the direct HTLC output.
|
||||||
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
|
ht.AssertTxInBlock(block, sweepTx)
|
||||||
|
|
||||||
|
// Now that the sweeping tx has been confirmed, Bob should cancel back
|
||||||
|
// that HTLC. As a result, Alice should not know of any active HTLC's.
|
||||||
|
ht.AssertNumActiveHtlcs(alice, 0)
|
||||||
|
|
||||||
|
// For script enforced lease channels, Bob still need to wait for the
|
||||||
|
// CLTV lock to expire before he can sweep his to_local output.
|
||||||
|
if params.CommitmentType == leasedType {
|
||||||
|
// Get the remaining blocks to mine.
|
||||||
|
resp = ht.AssertNumPendingForceClose(bob, 1)[0]
|
||||||
|
ht.MineBlocks(int(resp.BlocksTilMaturity))
|
||||||
|
|
||||||
|
// Assert the commit output has been offered to the sweeper.
|
||||||
|
// Bob should have two pending sweep requests - one for the
|
||||||
|
// commit output and one for the anchor output.
|
||||||
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// Mine the to_local sweep tx.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we'll check Bob's pending channel report. Since this was Carol's
|
||||||
|
// commitment, he doesn't have to wait for any CSV delays, but he may
|
||||||
|
// still need to wait for a CLTV on his commit output to expire
|
||||||
|
// depending on the commitment type.
|
||||||
|
ht.AssertNumPendingForceClose(bob, 0)
|
||||||
|
|
||||||
|
// While we're here, we assert that our expired invoice's state is
|
||||||
|
// correctly updated, and can no longer be settled.
|
||||||
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_CANCELED)
|
||||||
|
}
|
||||||
@@ -160,217 +160,6 @@ func runMultiHopHtlcClaimTest(ht *lntest.HarnessTest, tester caseRunner) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testMultiHopRemoteForceCloseOnChainHtlcTimeout tests that if we extend a
|
|
||||||
// multi-hop HTLC, and the final destination of the HTLC force closes the
|
|
||||||
// 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 *lntest.HarnessTest) {
|
|
||||||
runMultiHopHtlcClaimTest(
|
|
||||||
ht, runMultiHopRemoteForceCloseOnChainHtlcTimeout,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntest.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 := createThreeHopNetwork(
|
|
||||||
ht, alice, bob, true, c, zeroConf,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
const htlcAmt = btcutil.Amount(30000)
|
|
||||||
|
|
||||||
// If this is a taproot channel, then we'll need to make some manual
|
|
||||||
// route hints so Alice can actually find a route.
|
|
||||||
var routeHints []*lnrpc.RouteHint
|
|
||||||
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
||||||
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll now send a single HTLC across our multi-hop network.
|
|
||||||
var preimage lntypes.Preimage
|
|
||||||
copy(preimage[:], ht.Random32Bytes())
|
|
||||||
payHash := preimage.Hash()
|
|
||||||
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
||||||
Value: int64(htlcAmt),
|
|
||||||
CltvExpiry: finalCltvDelta,
|
|
||||||
Hash: payHash[:],
|
|
||||||
RouteHints: routeHints,
|
|
||||||
}
|
|
||||||
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
||||||
|
|
||||||
// Subscribe the invoice.
|
|
||||||
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
||||||
|
|
||||||
req := &routerrpc.SendPaymentRequest{
|
|
||||||
PaymentRequest: carolInvoice.PaymentRequest,
|
|
||||||
TimeoutSeconds: 60,
|
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
|
||||||
}
|
|
||||||
alice.RPC.SendPayment(req)
|
|
||||||
|
|
||||||
// blocksMined records how many blocks have mined after the creation of
|
|
||||||
// the invoice so it can be used to calculate how many more blocks need
|
|
||||||
// to be mined to trigger a force close later on.
|
|
||||||
var blocksMined uint32
|
|
||||||
|
|
||||||
// Once the HTLC has cleared, all the nodes in our mini network should
|
|
||||||
// show that the HTLC has been locked in.
|
|
||||||
ht.AssertActiveHtlcs(alice, payHash[:])
|
|
||||||
ht.AssertActiveHtlcs(bob, payHash[:])
|
|
||||||
ht.AssertActiveHtlcs(carol, payHash[:])
|
|
||||||
|
|
||||||
// At this point, we'll now instruct Carol to force close the
|
|
||||||
// transaction. This will let us exercise that Bob is able to sweep the
|
|
||||||
// expired HTLC on Carol's version of the commitment transaction.
|
|
||||||
closeStream, _ := ht.CloseChannelAssertPending(
|
|
||||||
carol, bobChanPoint, true,
|
|
||||||
)
|
|
||||||
|
|
||||||
// For anchor channels, the anchor won't be used for CPFP because
|
|
||||||
// channel arbitrator thinks Carol doesn't have preimage for her
|
|
||||||
// incoming HTLC on the commitment transaction Bob->Carol. Although
|
|
||||||
// Carol created this invoice, because it's a hold invoice, the
|
|
||||||
// preimage won't be generated automatically.
|
|
||||||
closeTx := ht.AssertStreamChannelForceClosed(
|
|
||||||
carol, bobChanPoint, true, closeStream,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Increase the blocks mined. At this step
|
|
||||||
// AssertStreamChannelForceClosed mines one block.
|
|
||||||
blocksMined++
|
|
||||||
|
|
||||||
// At this point, Bob should have a pending force close channel as
|
|
||||||
// Carol has gone directly to chain.
|
|
||||||
ht.AssertNumPendingForceClose(bob, 1)
|
|
||||||
|
|
||||||
var expectedTxes int
|
|
||||||
switch c {
|
|
||||||
// Bob can sweep his commit and anchor outputs immediately. Carol will
|
|
||||||
// also offer her anchor to her sweeper.
|
|
||||||
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
||||||
ht.AssertNumPendingSweeps(bob, 2)
|
|
||||||
ht.AssertNumPendingSweeps(carol, 1)
|
|
||||||
|
|
||||||
// We expect to see only one sweeping tx to be published from
|
|
||||||
// Bob, which sweeps his commit and anchor outputs in the same
|
|
||||||
// tx. For Carol, since her anchor is not used for CPFP, it'd
|
|
||||||
// be uneconomical to sweep so it will fail.
|
|
||||||
expectedTxes = 1
|
|
||||||
|
|
||||||
// Bob can't sweep his commit output yet as he was the initiator of a
|
|
||||||
// script-enforced leased channel, so he'll always incur the additional
|
|
||||||
// CLTV. He can still offer his anchor output to his sweeper however.
|
|
||||||
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
||||||
ht.AssertNumPendingSweeps(bob, 1)
|
|
||||||
ht.AssertNumPendingSweeps(carol, 1)
|
|
||||||
|
|
||||||
// We expect to see only no sweeping txns to be published,
|
|
||||||
// neither Bob's or Carol's anchor sweep can succeed due to
|
|
||||||
// it's uneconomical.
|
|
||||||
expectedTxes = 0
|
|
||||||
|
|
||||||
default:
|
|
||||||
ht.Fatalf("unhandled commitment type %v", c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mine one block to trigger the sweeps.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
blocksMined++
|
|
||||||
|
|
||||||
// We now mine a block to clear up the mempool.
|
|
||||||
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
||||||
blocksMined++
|
|
||||||
|
|
||||||
// Next, we'll mine enough blocks for the HTLC to expire. At this
|
|
||||||
// point, Bob should hand off the output to his internal utxo nursery,
|
|
||||||
// which will broadcast a sweep transaction.
|
|
||||||
numBlocks := padCLTV(uint32(finalCltvDelta) -
|
|
||||||
lncfg.DefaultOutgoingBroadcastDelta)
|
|
||||||
ht.MineEmptyBlocks(int(numBlocks - blocksMined))
|
|
||||||
|
|
||||||
// If we check Bob's pending channel report, it should show that he has
|
|
||||||
// a single HTLC that's now in the second stage, as it skipped the
|
|
||||||
// initial first stage since this is a direct HTLC.
|
|
||||||
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2)
|
|
||||||
|
|
||||||
// We need to generate an additional block to expire the CSV 1.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// For script-enforced leased channels, Bob has failed to sweep his
|
|
||||||
// anchor output before, so it's still pending.
|
|
||||||
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
||||||
ht.AssertNumPendingSweeps(bob, 2)
|
|
||||||
} else {
|
|
||||||
// Bob should have a pending sweep request.
|
|
||||||
ht.AssertNumPendingSweeps(bob, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mine a block to trigger the sweeper to sweep it.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// Bob's sweeping transaction should now be found in the mempool at
|
|
||||||
// this point.
|
|
||||||
sweepTx := ht.AssertNumTxsInMempool(1)[0]
|
|
||||||
|
|
||||||
// If we mine an additional block, then this should confirm Bob's
|
|
||||||
// transaction which sweeps the direct HTLC output.
|
|
||||||
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
||||||
ht.AssertTxInBlock(block, sweepTx)
|
|
||||||
|
|
||||||
// Now that the sweeping transaction has been confirmed, Bob should
|
|
||||||
// cancel back that HTLC. As a result, Alice should not know of any
|
|
||||||
// active HTLC's.
|
|
||||||
ht.AssertNumActiveHtlcs(alice, 0)
|
|
||||||
|
|
||||||
// Now we'll check Bob's pending channel report. Since this was Carol's
|
|
||||||
// commitment, he doesn't have to wait for any CSV delays, but he may
|
|
||||||
// still need to wait for a CLTV on his commit output to expire
|
|
||||||
// depending on the commitment type.
|
|
||||||
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
||||||
resp := bob.RPC.PendingChannels()
|
|
||||||
|
|
||||||
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
||||||
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
||||||
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
||||||
|
|
||||||
numBlocks := int(forceCloseChan.BlocksTilMaturity)
|
|
||||||
ht.MineEmptyBlocks(numBlocks)
|
|
||||||
|
|
||||||
// Assert the commit output has been offered to the sweeper.
|
|
||||||
// Bob should have two pending sweep requests - one for the
|
|
||||||
// commit output and one for the anchor output.
|
|
||||||
ht.AssertNumPendingSweeps(bob, 2)
|
|
||||||
|
|
||||||
// Mine a block to trigger the sweep.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
bobCommitOutpoint := wire.OutPoint{Hash: closeTx, Index: 3}
|
|
||||||
bobCommitSweep := ht.AssertOutpointInMempool(
|
|
||||||
bobCommitOutpoint,
|
|
||||||
)
|
|
||||||
bobCommitSweepTxid := bobCommitSweep.TxHash()
|
|
||||||
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
||||||
ht.AssertTxInBlock(block, bobCommitSweepTxid)
|
|
||||||
}
|
|
||||||
ht.AssertNumPendingForceClose(bob, 0)
|
|
||||||
|
|
||||||
// While we're here, we assert that our expired invoice's state is
|
|
||||||
// correctly updated, and can no longer be settled.
|
|
||||||
ht.AssertInvoiceState(stream, lnrpc.Invoice_CANCELED)
|
|
||||||
|
|
||||||
// We'll close out the test by closing the channel from Alice to Bob,
|
|
||||||
// and then shutting down the new node we created as its no longer
|
|
||||||
// needed. Coop close, no anchors.
|
|
||||||
ht.CloseChannel(alice, aliceChanPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testMultiHopHtlcLocalChainClaim tests that in a multi-hop HTLC scenario, if
|
// testMultiHopHtlcLocalChainClaim tests that in a multi-hop HTLC scenario, if
|
||||||
// we force close a channel with an incoming HTLC, and later find out the
|
// 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
|
// preimage via the witness beacon, we properly settle the HTLC on-chain using
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -1613,8 +1614,9 @@ func (h *HarnessTest) AssertNumHTLCsAndStage(hn *node.HarnessNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(target.PendingHtlcs) != num {
|
if len(target.PendingHtlcs) != num {
|
||||||
return fmt.Errorf("got %d pending htlcs, want %d",
|
return fmt.Errorf("got %d pending htlcs, want %d, %s",
|
||||||
len(target.PendingHtlcs), num)
|
len(target.PendingHtlcs), num,
|
||||||
|
lnutils.SpewLogClosure(target.PendingHtlcs)())
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, htlc := range target.PendingHtlcs {
|
for i, htlc := range target.PendingHtlcs {
|
||||||
|
|||||||
Reference in new issue
Block a user