mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-26 01:33:02 +01:00
itest: simplify and flatten testMultiHopReceiverChainClaim
This commit is contained in:
parent
9ab9cd5f99
commit
bc31979f7b
@ -13,10 +13,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "basic funding flow",
|
||||
TestFunc: testBasicChannelFunding,
|
||||
},
|
||||
{
|
||||
Name: "multi hop receiver chain claim",
|
||||
TestFunc: testMultiHopReceiverChainClaim,
|
||||
},
|
||||
{
|
||||
Name: "external channel funding",
|
||||
TestFunc: testExternalFundingChanPoint,
|
||||
|
@ -6,9 +6,11 @@ import (
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -31,6 +33,18 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{
|
||||
Name: "multihop local claim outgoing htlc leased",
|
||||
TestFunc: testLocalClaimOutgoingHTLCLeased,
|
||||
},
|
||||
{
|
||||
Name: "multihop receiver preimage claim anchor",
|
||||
TestFunc: testMultiHopReceiverPreimageClaimAnchor,
|
||||
},
|
||||
{
|
||||
Name: "multihop receiver preimage claim simple taproot",
|
||||
TestFunc: testMultiHopReceiverPreimageClaimSimpleTaproot,
|
||||
},
|
||||
{
|
||||
Name: "multihop receiver preimage claim leased",
|
||||
TestFunc: testMultiHopReceiverPreimageClaimLeased,
|
||||
},
|
||||
}
|
||||
|
||||
// testLocalClaimOutgoingHTLCAnchor tests `runLocalClaimOutgoingHTLC` with
|
||||
@ -359,3 +373,382 @@ func runLocalClaimOutgoingHTLC(ht *lntest.HarnessTest,
|
||||
// no longer has any pending channels.
|
||||
ht.AssertNumPendingForceClose(bob, 0)
|
||||
}
|
||||
|
||||
// testMultiHopReceiverPreimageClaimAnchor tests
|
||||
// `runMultiHopReceiverPreimageClaim` with anchor channels.
|
||||
func testMultiHopReceiverPreimageClaimAnchor(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.
|
||||
openChannelParams := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
|
||||
cfg := node.CfgAnchor
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
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.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
ZeroConf: true,
|
||||
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
}
|
||||
|
||||
// Prepare Carol's node config to enable zero-conf and anchor.
|
||||
cfg := node.CfgZeroConf
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// testMultiHopReceiverPreimageClaimSimpleTaproot tests
|
||||
// `runMultiHopReceiverPreimageClaim` with simple taproot channels.
|
||||
func testMultiHopReceiverPreimageClaimSimpleTaproot(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.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: c,
|
||||
Private: true,
|
||||
}
|
||||
|
||||
cfg := node.CfgSimpleTaproot
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
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.
|
||||
openChannelParams := 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...)
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// testMultiHopReceiverPreimageClaimLeased tests
|
||||
// `runMultiHopReceiverPreimageClaim` with script enforce lease channels.
|
||||
func testMultiHopReceiverPreimageClaimLeased(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.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: leasedType,
|
||||
}
|
||||
|
||||
cfg := node.CfgLeased
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
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.
|
||||
openChannelParams := 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...)
|
||||
cfgs := [][]string{cfg, cfg, cfg}
|
||||
|
||||
runMultiHopReceiverPreimageClaim(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// runMultiHopReceiverClaim tests that in the multi-hop setting, if the
|
||||
// receiver of an HTLC knows the preimage, but wasn't able to settle the HTLC
|
||||
// off-chain, then it goes on chain to claim the HTLC uing the HTLC success
|
||||
// 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 runMultiHopReceiverPreimageClaim(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]
|
||||
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
// For neutrino backend, we need to one more UTXO for Carol so she can
|
||||
// sweep her outputs.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
}
|
||||
|
||||
// Fund Carol one UTXO so she can sweep outputs.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
// 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 the network active, we'll now add a new hodl invoice at Carol's
|
||||
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
||||
// won't send out the outgoing htlc.
|
||||
const invoiceAmt = 100000
|
||||
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], ht.Random32Bytes())
|
||||
payHash := preimage.Hash()
|
||||
|
||||
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||
Value: invoiceAmt,
|
||||
CltvExpiry: finalCltvDelta,
|
||||
Hash: payHash[:],
|
||||
RouteHints: routeHints,
|
||||
}
|
||||
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
||||
|
||||
// Subscribe the invoice.
|
||||
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||
|
||||
// Now that we've created the invoice, we'll send a single payment from
|
||||
// Alice to Carol. We won't wait for the response however, as Carol
|
||||
// will not immediately settle the payment.
|
||||
req := &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: carolInvoice.PaymentRequest,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
}
|
||||
alice.RPC.SendPayment(req)
|
||||
|
||||
// At this point, all 3 nodes should now have an active channel with
|
||||
// the created HTLC pending on all of them.
|
||||
ht.AssertActiveHtlcs(alice, payHash[:])
|
||||
ht.AssertActiveHtlcs(bob, payHash[:])
|
||||
ht.AssertActiveHtlcs(carol, payHash[:])
|
||||
|
||||
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
||||
// bridge between adding the htlc to the channel and executing the exit
|
||||
// hop logic.
|
||||
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
||||
|
||||
// Stop Bob so he won't be able to settle the incoming htlc.
|
||||
restartBob := ht.SuspendNode(bob)
|
||||
|
||||
// Settle invoice. This will just mark the invoice as settled, as there
|
||||
// is no link anymore to remove the htlc from the commitment tx. For
|
||||
// this test, it is important to actually settle and not leave the
|
||||
// invoice in the accepted state, because without a known preimage, the
|
||||
// channel arbitrator won't go to chain.
|
||||
carol.RPC.SettleInvoice(preimage[:])
|
||||
|
||||
// We now advance the block height to the point where Carol will force
|
||||
// close her channel with Bob, broadcast the closing tx but keep it
|
||||
// unconfirmed.
|
||||
numBlocks := padCLTV(uint32(
|
||||
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
||||
))
|
||||
|
||||
// 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.
|
||||
ht.MineBlocks(int(numBlocks))
|
||||
|
||||
// Carol's force close tx should have the following outputs,
|
||||
// 1. anchor output.
|
||||
// 2. to_local output, which is CSV locked.
|
||||
// 3. incoming HTLC output, which she has the preimage to settle.
|
||||
//
|
||||
// Carol's anchor output should be offered to her sweeper since she has
|
||||
// time-sensitive HTLCs - we expect both anchors to be offered, while
|
||||
// the sweeping of the remote anchor will be marked as failed due to
|
||||
// `testmempoolaccept` check.
|
||||
//
|
||||
// For neutrino backend, there's no way to know the sweeping of the
|
||||
// remote anchor is failed, so Carol still sees two pending sweeps.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.AssertNumPendingSweeps(carol, 2)
|
||||
} else {
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
}
|
||||
|
||||
// We expect to see tow txns in the mempool,
|
||||
// 1. Carol's force close tx.
|
||||
// 2. Carol's anchor sweep tx.
|
||||
ht.AssertNumTxsInMempool(2)
|
||||
|
||||
// Mine a block to confirm the closing tx and the anchor sweeping tx.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||
|
||||
ht.Log("Current height", ht.CurrentHeight())
|
||||
|
||||
// After the force close tx is mined, Carol should offer her second
|
||||
// level HTLC tx to the sweeper.
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
|
||||
// Restart bob again.
|
||||
require.NoError(ht, restartBob())
|
||||
|
||||
// Once Bob is online, he should notice Carol's second level tx in the
|
||||
// mempool, he will extract the preimage and settle the HTLC back
|
||||
// off-chain. He will also try to sweep his anchor and to_local
|
||||
// outputs, with the anchor output being skipped due to it being
|
||||
// uneconomical.
|
||||
if params.CommitmentType == leasedType {
|
||||
// For leased channels, Bob cannot sweep his to_local output
|
||||
// yet since it's timelocked, so we only see his anchor input.
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
} else {
|
||||
// For non-leased channels, Bob should have two pending sweeps,
|
||||
// 1. to_local output.
|
||||
// 2. anchor output, tho it won't be swept due to it being
|
||||
// uneconomical.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
}
|
||||
|
||||
// Mine an empty block the for neutrino backend. We need this step to
|
||||
// trigger Bob's chain watcher to detect the force close tx. Deep down,
|
||||
// this happens because the notification system for neutrino is very
|
||||
// different from others. Specifically, when a block contains the force
|
||||
// close tx is notified, these two calls,
|
||||
// - RegisterBlockEpochNtfn, will notify the block first.
|
||||
// - RegisterSpendNtfn, will wait for the neutrino notifier to sync to
|
||||
// the block, then perform a GetUtxo, which, by the time the spend
|
||||
// details are sent, the blockbeat is considered processed in Bob's
|
||||
// chain watcher.
|
||||
//
|
||||
// TODO(yy): refactor txNotifier to fix the above issue.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.MineEmptyBlocks(1)
|
||||
}
|
||||
|
||||
if params.CommitmentType == leasedType {
|
||||
// We expect to see 1 txns in the mempool,
|
||||
// - Carol's second level HTLC sweep tx.
|
||||
// We now mine a block to confirm it.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
} else {
|
||||
// We expect to see 2 txns in the mempool,
|
||||
// - Bob's to_local sweep tx.
|
||||
// - Carol's second level HTLC sweep tx.
|
||||
// We now mine a block to confirm the sweeping txns.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||
}
|
||||
|
||||
// Once the second-level transaction confirmed, Bob should have
|
||||
// extracted the preimage from the chain, and sent it back to Alice,
|
||||
// clearing the HTLC off-chain.
|
||||
ht.AssertNumActiveHtlcs(alice, 0)
|
||||
|
||||
// Check that the Alice's payment is correctly marked succeeded.
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
// 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.
|
||||
ht.AssertNumHTLCsAndStage(carol, bobChanPoint, 1, 2)
|
||||
|
||||
// If we mine 4 additional blocks, then Carol can sweep the second
|
||||
// level HTLC output once the CSV expires.
|
||||
ht.MineBlocks(defaultCSV - 1)
|
||||
|
||||
// Assert Carol has the pending HTLC sweep.
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
|
||||
// We should have a new transaction in the mempool.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
// Finally, if we mine an additional block to confirm Carol's second
|
||||
// level success transaction. Carol should not show a pending channel
|
||||
// in her report afterwards.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
ht.AssertNumPendingForceClose(carol, 0)
|
||||
|
||||
// The invoice should show as settled for Carol, indicating that it was
|
||||
// swept on-chain.
|
||||
ht.AssertInvoiceSettled(carol, carolInvoice.PaymentAddr)
|
||||
|
||||
// For leased channels, Bob still has his commit output to sweep to
|
||||
// since he incurred an additional CLTV from being the channel
|
||||
// initiator.
|
||||
if params.CommitmentType == leasedType {
|
||||
resp := ht.AssertNumPendingForceClose(bob, 1)[0]
|
||||
require.Positive(ht, resp.LimboBalance)
|
||||
require.Positive(ht, resp.BlocksTilMaturity)
|
||||
|
||||
// Mine enough blocks for Bob's commit output's CLTV to expire
|
||||
// and sweep it.
|
||||
ht.MineBlocks(int(resp.BlocksTilMaturity))
|
||||
|
||||
// Bob should have two pending inputs to be swept, the commit
|
||||
// output and the anchor output.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
|
||||
// Mine a block to confirm the commit output sweep.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
}
|
||||
|
||||
// Assert Bob also sees the channel as closed.
|
||||
ht.AssertNumPendingForceClose(bob, 0)
|
||||
}
|
||||
|
@ -160,258 +160,6 @@ func runMultiHopHtlcClaimTest(ht *lntest.HarnessTest, tester caseRunner) {
|
||||
}
|
||||
}
|
||||
|
||||
// testMultiHopReceiverChainClaim tests that in the multi-hop setting, if the
|
||||
// receiver of an HTLC knows the preimage, but wasn't able to settle the HTLC
|
||||
// off-chain, then it goes on chain to claim the HTLC uing the HTLC success
|
||||
// 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 *lntest.HarnessTest) {
|
||||
runMultiHopHtlcClaimTest(ht, runMultiHopReceiverChainClaim)
|
||||
}
|
||||
|
||||
func runMultiHopReceiverChainClaim(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, false, c, zeroConf,
|
||||
)
|
||||
|
||||
// For neutrino backend, we need to fund one more UTXO for Carol so she
|
||||
// can sweep her outputs.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// With the network active, we'll now add a new hodl invoice at Carol's
|
||||
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
||||
// won't send out the outgoing htlc.
|
||||
const invoiceAmt = 100000
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], ht.Random32Bytes())
|
||||
payHash := preimage.Hash()
|
||||
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||
Value: invoiceAmt,
|
||||
CltvExpiry: finalCltvDelta,
|
||||
Hash: payHash[:],
|
||||
RouteHints: routeHints,
|
||||
}
|
||||
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
||||
|
||||
// Subscribe the invoice.
|
||||
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||
|
||||
// Now that we've created the invoice, we'll send a single payment from
|
||||
// Alice to Carol. We won't wait for the response however, as Carol
|
||||
// will not immediately settle the payment.
|
||||
req := &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: carolInvoice.PaymentRequest,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
}
|
||||
alice.RPC.SendPayment(req)
|
||||
|
||||
// At this point, all 3 nodes should now have an active channel with
|
||||
// the created HTLC pending on all of them.
|
||||
ht.AssertActiveHtlcs(alice, payHash[:])
|
||||
ht.AssertActiveHtlcs(bob, payHash[:])
|
||||
ht.AssertActiveHtlcs(carol, payHash[:])
|
||||
|
||||
// Wait for carol to mark invoice as accepted. There is a small gap to
|
||||
// bridge between adding the htlc to the channel and executing the exit
|
||||
// hop logic.
|
||||
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
||||
|
||||
restartBob := ht.SuspendNode(bob)
|
||||
|
||||
// Settle invoice. This will just mark the invoice as settled, as there
|
||||
// is no link anymore to remove the htlc from the commitment tx. For
|
||||
// this test, it is important to actually settle and not leave the
|
||||
// invoice in the accepted state, because without a known preimage, the
|
||||
// channel arbitrator won't go to chain.
|
||||
carol.RPC.SettleInvoice(preimage[:])
|
||||
|
||||
// Increase the fee estimate so that the following force close tx will
|
||||
// be cpfp'ed.
|
||||
ht.SetFeeEstimate(30000)
|
||||
|
||||
// We now advance the block height to the point where Carol will force
|
||||
// close her channel with Bob, broadcast the closing tx but keep it
|
||||
// unconfirmed.
|
||||
numBlocks := padCLTV(uint32(
|
||||
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
||||
))
|
||||
|
||||
// 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.
|
||||
ht.MineEmptyBlocks(int(numBlocks))
|
||||
|
||||
// At this point, Carol should broadcast her active commitment
|
||||
// transaction in order to go to the chain and sweep her HTLC.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
closingTx := ht.AssertOutpointInMempool(
|
||||
ht.OutPointFromChannelPoint(bobChanPoint),
|
||||
)
|
||||
closingTxid := closingTx.TxHash()
|
||||
|
||||
// Carol's anchor should have been offered to her sweeper as she has
|
||||
// time-sensitive HTLCs. Assert that we have two anchors - one for the
|
||||
// anchor on the local commitment and the other for the anchor on the
|
||||
// remote commitment (invalid).
|
||||
ht.AssertNumPendingSweeps(carol, 2)
|
||||
|
||||
// Confirm the commitment.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// The above mined block will trigger Carol's sweeper to publish the
|
||||
// anchor sweeping tx.
|
||||
//
|
||||
// TODO(yy): should instead cancel the broadcast of the anchor sweeping
|
||||
// tx to save fees since we know the force close tx has been confirmed?
|
||||
// This is very difficult as it introduces more complicated RBF
|
||||
// scenarios, as we are using a wallet utxo, which means any txns using
|
||||
// that wallet utxo must pay more fees. On the other hand, there's no
|
||||
// way to remove that anchor-CPFP tx from the mempool.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
// After the force close transaction is mined, Carol should offer her
|
||||
// second level HTLC tx to the sweeper, which means we should see two
|
||||
// pending inputs now - the anchor and the htlc.
|
||||
ht.AssertNumPendingSweeps(carol, 2)
|
||||
|
||||
// Restart bob again.
|
||||
require.NoError(ht, restartBob())
|
||||
|
||||
var expectedTxes int
|
||||
|
||||
// After the force close transaction is mined, a series of transactions
|
||||
// should be broadcast by Bob and Carol. When Bob notices Carol's
|
||||
// second level transaction in the mempool, he will extract the
|
||||
// preimage and settle the HTLC back off-chain.
|
||||
switch c {
|
||||
// We expect to see three txns in the mempool:
|
||||
// 1. Carol should broadcast her second level HTLC tx.
|
||||
// 2. Carol should broadcast her anchor sweeping tx.
|
||||
// 3. Bob should broadcast a sweep tx to sweep his output in the
|
||||
// channel with Carol, and in the same sweep tx to sweep his anchor
|
||||
// output.
|
||||
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
expectedTxes = 3
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
|
||||
// We expect to see two txns in the mempool:
|
||||
// 1. Carol should broadcast her second level HTLC tx.
|
||||
// 2. Carol should broadcast her anchor sweeping tx.
|
||||
// Bob would offer his anchor output to his sweeper, but it cannot be
|
||||
// swept due to it being uneconomical. Bob's commit output can't be
|
||||
// swept yet as he's incurring an additional CLTV from being the
|
||||
// channel initiator of a script-enforced leased channel.
|
||||
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
||||
expectedTxes = 2
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
|
||||
default:
|
||||
ht.Fatalf("unhandled commitment type %v", c)
|
||||
}
|
||||
|
||||
// Mine one block to trigger the sweeper to sweep.
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
// All transactions should be spending from the commitment transaction.
|
||||
txes := ht.GetNumTxsFromMempool(expectedTxes)
|
||||
ht.AssertAllTxesSpendFrom(txes, closingTxid)
|
||||
|
||||
// We'll now mine an additional block which should confirm both the
|
||||
// second layer transactions.
|
||||
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
||||
|
||||
// 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.
|
||||
ht.AssertNumHTLCsAndStage(carol, bobChanPoint, 1, 2)
|
||||
|
||||
// Once the second-level transaction confirmed, Bob should have
|
||||
// extracted the preimage from the chain, and sent it back to Alice,
|
||||
// clearing the HTLC off-chain.
|
||||
ht.AssertNumActiveHtlcs(alice, 0)
|
||||
|
||||
// If we mine 4 additional blocks, then Carol can sweep the second
|
||||
// level HTLC output once the CSV expires.
|
||||
ht.MineEmptyBlocks(defaultCSV - 1)
|
||||
|
||||
// Assert Carol has the pending HTLC sweep.
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
|
||||
// Mine one block to trigger the sweeper to sweep.
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
// We should have a new transaction in the mempool.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
// Finally, if we mine an additional block to confirm Carol's second
|
||||
// level success transaction. Carol should not show a pending channel
|
||||
// in her report afterwards.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
ht.AssertNumPendingForceClose(carol, 0)
|
||||
|
||||
// The invoice should show as settled for Carol, indicating that it was
|
||||
// swept on-chain.
|
||||
ht.AssertInvoiceSettled(carol, carolInvoice.PaymentAddr)
|
||||
|
||||
// Finally, check that the Alice's payment is correctly marked
|
||||
// succeeded.
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
||||
// Bob still has his commit output to sweep to since he
|
||||
// incurred an additional CLTV from being the channel initiator
|
||||
// of a script-enforced leased channel, regardless of whether
|
||||
// he forced closed the channel or not.
|
||||
pendingChanResp := bob.RPC.PendingChannels()
|
||||
|
||||
require.Len(ht, pendingChanResp.PendingForceClosingChannels, 1)
|
||||
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
|
||||
require.Positive(ht, forceCloseChan.LimboBalance)
|
||||
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
||||
|
||||
// TODO: Bob still shows a pending HTLC at this point when he
|
||||
// shouldn't, as he already extracted the preimage from Carol's
|
||||
// claim.
|
||||
// require.Len(t.t, forceCloseChan.PendingHtlcs, 0)
|
||||
|
||||
// Mine enough blocks for Bob's commit output's CLTV to expire
|
||||
// and sweep it.
|
||||
numBlocks := int(forceCloseChan.BlocksTilMaturity)
|
||||
ht.MineEmptyBlocks(numBlocks)
|
||||
|
||||
// Bob should have two pending inputs to be swept, the commit
|
||||
// output and the anchor output.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
|
||||
ht.AssertOutpointInMempool(commitOutpoint)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
}
|
||||
|
||||
ht.AssertNumPendingForceClose(bob, 0)
|
||||
|
||||
// We'll close out the channel between Alice and Bob, then shutdown
|
||||
// carol to conclude the test.
|
||||
ht.CloseChannel(alice, aliceChanPoint)
|
||||
}
|
||||
|
||||
// testMultiHopLocalForceCloseOnChainHtlcTimeout tests that in a multi-hop HTLC
|
||||
// scenario, if the node that extended the HTLC to the final node closes their
|
||||
// commitment on-chain early, then it eventually recognizes this HTLC as one
|
||||
|
Loading…
x
Reference in New Issue
Block a user