diff --git a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go deleted file mode 100644 index 091ff4936..000000000 --- a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go +++ /dev/null @@ -1,392 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "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/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntemp/rpc" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/require" -) - -// testMultiHopHtlcAggregation tests that in a multi-hop HTLC scenario, if we -// force close a channel with both incoming and outgoing HTLCs, we can properly -// 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, - alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { - - // First, we'll create a three hop network: Alice -> Bob -> Carol. - aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork( - ht, alice, bob, false, c, zeroConf, - ) - - // For neutrino backend, we need one additional UTXO to create - // the sweeping tx for the second-level success txes. - if ht.IsNeutrinoBackend() { - ht.FundCoins(btcutil.SatoshiPerBitcoin, bob) - } - - // To ensure we have capacity in both directions of the route, we'll - // make a fairly large payment Alice->Carol and settle it. - 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) - - // 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 - // enough, otherwise Bob won't send out the outgoing htlc. - const numInvoices = 5 - const invoiceAmt = 50_000 - - var ( - carolInvoices []*invoicesrpc.AddHoldInvoiceResp - aliceInvoices []*invoicesrpc.AddHoldInvoiceResp - alicePreimages []lntypes.Preimage - payHashes [][]byte - invoiceStreamsCarol []rpc.SingleInvoiceClient - invoiceStreamsAlice []rpc.SingleInvoiceClient - ) - - // Add Carol invoices. - for i := 0; i < numInvoices; i++ { - var preimage lntypes.Preimage - copy(preimage[:], ht.Random32Bytes()) - payHash := preimage.Hash() - invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ - Value: invoiceAmt, - CltvExpiry: finalCltvDelta, - Hash: payHash[:], - } - carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq) - - carolInvoices = append(carolInvoices, carolInvoice) - payHashes = append(payHashes, payHash[:]) - - // Subscribe the invoice. - stream := carol.RPC.SubscribeSingleInvoice(payHash[:]) - invoiceStreamsCarol = append(invoiceStreamsCarol, stream) - } - - // We'll give Alice's invoices a longer CLTV expiry, to ensure the - // channel Bob<->Carol will be closed first. - for i := 0; i < numInvoices; i++ { - var preimage lntypes.Preimage - copy(preimage[:], ht.Random32Bytes()) - payHash := preimage.Hash() - invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ - Value: invoiceAmt, - CltvExpiry: 2 * finalCltvDelta, - Hash: payHash[:], - } - aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq) - - aliceInvoices = append(aliceInvoices, aliceInvoice) - alicePreimages = append(alicePreimages, preimage) - payHashes = append(payHashes, payHash[:]) - - // Subscribe the invoice. - stream := alice.RPC.SubscribeSingleInvoice(payHash[:]) - invoiceStreamsAlice = append(invoiceStreamsAlice, stream) - } - - // Now that we've created the invoices, we'll pay them all from - // Alice<->Carol, going through Bob. We won't wait for the response - // however, as neither will immediately settle the payment. - - // Alice will pay all of Carol's invoices. - for _, carolInvoice := range carolInvoices { - req := &routerrpc.SendPaymentRequest{ - PaymentRequest: carolInvoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - alice.RPC.SendPayment(req) - } - - // And Carol will pay Alice's. - for _, aliceInvoice := range aliceInvoices { - req := &routerrpc.SendPaymentRequest{ - PaymentRequest: aliceInvoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - carol.RPC.SendPayment(req) - } - - // At this point, all 3 nodes should now the HTLCs active on their - // channels. - ht.AssertActiveHtlcs(alice, payHashes...) - ht.AssertActiveHtlcs(bob, payHashes...) - ht.AssertActiveHtlcs(carol, payHashes...) - - // Wait for Alice and Carol to mark the invoices as accepted. There is - // a small gap to bridge between adding the htlc to the channel and - // executing the exit hop logic. - for _, stream := range invoiceStreamsCarol { - ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED) - } - - for _, stream := range invoiceStreamsAlice { - ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED) - } - - // Increase the fee estimate so that the following force close tx will - // be cpfp'ed. - ht.SetFeeEstimate(30000) - - // We want Carol's htlcs to expire off-chain to demonstrate bob's force - // close. However, Carol will cancel her invoices to prevent force - // closes, so we shut her down for now. - restartCarol := ht.SuspendNode(carol) - - // We'll now mine enough blocks to trigger Bob's broadcast of his - // commitment transaction due to the fact that the Carol's HTLCs are - // about to timeout. With the default outgoing broadcast delta of zero, - // this will be the same height as the htlc expiry height. - numBlocks := padCLTV( - uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), - ) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Bob's force close transaction should now be found in the mempool. If - // there are anchors, we also expect Bob's anchor sweep. - hasAnchors := commitTypeHasAnchors(c) - expectedTxes := 1 - if hasAnchors { - expectedTxes = 2 - } - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - closeTx := ht.Miner.AssertOutpointInMempool( - ht.OutPointFromChannelPoint(bobChanPoint), - ) - closeTxid := closeTx.TxHash() - - // Go through the closing transaction outputs, and make an index for - // the HTLC outputs. - successOuts := make(map[wire.OutPoint]struct{}) - timeoutOuts := make(map[wire.OutPoint]struct{}) - for i, txOut := range closeTx.TxOut { - op := wire.OutPoint{ - Hash: closeTxid, - Index: uint32(i), - } - - switch txOut.Value { - // If this HTLC goes towards Carol, Bob will claim it with a - // timeout Tx. In this case the value will be the invoice - // amount. - case invoiceAmt: - timeoutOuts[op] = struct{}{} - - // If the HTLC has direction towards Alice, Bob will claim it - // with the success TX when he learns the preimage. In this - // case one extra sat will be on the output, because of the - // routing fee. - case invoiceAmt + 1: - successOuts[op] = struct{}{} - } - } - - // Once bob has force closed, we can restart carol. - require.NoError(ht, restartCarol()) - - // Mine a block to confirm the closing transaction. - ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) - - // Let Alice settle her invoices. When Bob now gets the preimages, he - // has no other option than to broadcast his second-level transactions - // to claim the money. - for _, preimage := range alicePreimages { - alice.RPC.SettleInvoice(preimage[:]) - } - - switch c { - // With the closing transaction confirmed, we should expect Bob's HTLC - // timeout transactions to be broadcast due to the expiry being reached. - // We will also expect the success transactions, since he learnt the - // preimages from Alice. We also expect Carol to sweep her commitment - // output. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 2*numInvoices + 1 - - // In case of anchors, all success transactions will be aggregated into - // one, the same is the case for the timeout transactions. In this case - // Carol will also sweep her commitment and anchor output as separate - // txs (since it will be low fee). - case lnrpc.CommitmentType_ANCHORS, - lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: - expectedTxes = 4 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) - - // Since Bob can aggregate the transactions, we expect a single - // transaction, that have multiple spends from the commitment. - var ( - timeoutTxs []*chainhash.Hash - successTxs []*chainhash.Hash - ) - for _, tx := range txes { - txid := tx.TxHash() - - for i := range tx.TxIn { - prevOp := tx.TxIn[i].PreviousOutPoint - if _, ok := successOuts[prevOp]; ok { - successTxs = append(successTxs, &txid) - break - } - - if _, ok := timeoutOuts[prevOp]; ok { - timeoutTxs = append(timeoutTxs, &txid) - break - } - } - } - - // In case of anchor we expect all the timeout and success second - // levels to be aggregated into one tx. For earlier channel types, they - // will be separate transactions. - if hasAnchors { - require.Len(ht, timeoutTxs, 1) - require.Len(ht, successTxs, 1) - } else { - require.Len(ht, timeoutTxs, numInvoices) - require.Len(ht, successTxs, numInvoices) - } - - // All mempool transactions should be spending from the commitment - // transaction. - ht.AssertAllTxesSpendFrom(txes, closeTxid) - - // 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) - - // At this point, Bob should have broadcast his second layer success - // transaction, and should have sent it to the nursery for incubation, - // or to the sweeper for sweeping. - ht.AssertNumPendingForceClose(bob, 1) - - // For this channel, we also check the number of HTLCs and the stage - // are correct. - ht.AssertNumHTLCsAndStage(bob, bobChanPoint, numInvoices*2, 2) - - if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - // If we then mine additional blocks, Bob can sweep his - // commitment output. - ht.MineBlocksAssertNodesSync(defaultCSV - 2) - - // Find the commitment sweep. - bobCommitSweep := ht.Miner.GetNumTxsFromMempool(1)[0] - ht.AssertTxSpendFrom(bobCommitSweep, closeTxid) - - // Also ensure it is not spending from any of the HTLC output. - for _, txin := range bobCommitSweep.TxIn { - for _, timeoutTx := range timeoutTxs { - require.NotEqual(ht, *timeoutTx, - txin.PreviousOutPoint.Hash, - "found unexpected spend of timeout tx") - } - - for _, successTx := range successTxs { - require.NotEqual(ht, *successTx, - txin.PreviousOutPoint.Hash, - "found unexpected spend of success tx") - } - } - } - - 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 - // are mined, we should expect to see Bob's commit sweep in the mempool. - case lnrpc.CommitmentType_LEGACY: - ht.Miner.MineBlocksAndAssertNumTxes(2, 1) - - // Mining one additional block, Bob's second level tx is mature, and he - // can sweep the output. Before the blocks are mined, we should expect - // to see Bob's commit sweep in the mempool. - case lnrpc.CommitmentType_ANCHORS: - ht.Miner.MineBlocksAndAssertNumTxes(1, 1) - - // Since Bob is the initiator of the Bob-Carol script-enforced leased - // channel, he incurs an additional CLTV when sweeping outputs back to - // his wallet. We'll need to mine enough blocks for the timelock to - // expire to prompt his broadcast. - case 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 := uint32(forceCloseChan.BlocksTilMaturity) - - // Add debug log. - _, height := ht.Miner.GetBestBlock() - bob.AddToLogf("itest: now mine %d blocks at height %d", - numBlocks, height) - ht.MineBlocksAssertNodesSync(numBlocks) - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - - // Make sure it spends from the second level tx. - secondLevelSweep := ht.Miner.GetNumTxsFromMempool(1)[0] - bobSweep := secondLevelSweep.TxHash() - - // It should be sweeping all the second-level outputs. - var secondLvlSpends int - for _, txin := range secondLevelSweep.TxIn { - for _, timeoutTx := range timeoutTxs { - if *timeoutTx == txin.PreviousOutPoint.Hash { - secondLvlSpends++ - } - } - - for _, successTx := range successTxs { - if *successTx == txin.PreviousOutPoint.Hash { - secondLvlSpends++ - } - } - } - - require.Equal(ht, 2*numInvoices, secondLvlSpends) - - // 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] - ht.Miner.AssertTxInBlock(block, &bobSweep) - ht.AssertNumPendingForceClose(bob, 0) - - // THe channel with Alice is still open. - ht.AssertNodeNumChannels(bob, 1) - - // Carol should have no channels left (open nor pending). - ht.AssertNumPendingForceClose(carol, 0) - ht.AssertNodeNumChannels(carol, 0) - - // Coop close, no anchors. - ht.CloseChannel(alice, aliceChanPoint) -} diff --git a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go deleted file mode 100644 index 14054e876..000000000 --- a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go +++ /dev/null @@ -1,297 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/wire" - "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/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/require" -) - -// testMultiHopHtlcLocalChainClaim tests that in a multi-hop HTLC scenario, if -// 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, - 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, - ) - - // 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[:], - } - 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) - - // Increase the fee estimate so that the following force close tx will - // be cpfp'ed. - ht.SetFeeEstimate(30000) - - // At this point, Bob decides that he wants to exit the channel - // immediately, so he force closes his commitment transaction. - hasAnchors := commitTypeHasAnchors(c) - closeStream, _ := ht.CloseChannelAssertPending( - bob, aliceChanPoint, true, - ) - bobForceClose := ht.AssertStreamChannelForceClosed( - bob, aliceChanPoint, hasAnchors, closeStream, - ) - - var expectedTxes int - switch c { - // Alice will sweep her commitment output immediately. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 1 - - // Alice will sweep her commitment and anchor output immediately. - case lnrpc.CommitmentType_ANCHORS: - expectedTxes = 2 - - // Alice will sweep her anchor output immediately. Her commitment - // output cannot be swept yet as it has incurred an additional CLTV due - // to being the initiator of a script-enforced leased channel. - case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: - expectedTxes = 1 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - // Suspend Bob to force Carol to go to chain. - 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'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)) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Carol's commitment transaction should now be in the mempool. If - // there is an anchor, Carol will sweep that too. - if commitTypeHasAnchors(c) { - expectedTxes = 2 - } - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - // Look up the closing transaction. It should be spending from the - // funding transaction, - closingTx := ht.Miner.AssertOutpointInMempool( - ht.OutPointFromChannelPoint(bobChanPoint), - ) - closingTxid := closingTx.TxHash() - - // Mine a block that should confirm the commit tx, the anchor if - // present and the coinbase. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] - ht.Miner.AssertTxInBlock(block, &closingTxid) - - // Restart bob again. - require.NoError(ht, restartBob()) - - // After the force close transaction is mined, transactions will be - // broadcast by both Bob and Carol. - switch c { - // Carol will broadcast her second level HTLC transaction and Bob will - // sweep his commitment output. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 2 - - // Carol will broadcast her second level HTLC transaction and Bob will - // sweep his commitment and anchor output. - case lnrpc.CommitmentType_ANCHORS: - expectedTxes = 3 - - // Carol will broadcast her second level HTLC transaction and anchor - // sweep, and Bob will sweep his anchor output. Bob can't sweep his - // commitment output yet as it has incurred an additional CLTV due to - // being the initiator of a script-enforced leased channel. - case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: - expectedTxes = 2 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - - txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) - - // Both Carol's second level transaction and Bob's sweep should be - // spending from the commitment transaction. - ht.AssertAllTxesSpendFrom(txes, closingTxid) - - // At this point we suspend Alice to make sure she'll handle the - // on-chain settle after a restart. - restartAlice := ht.SuspendNode(alice) - - // Mine a block to confirm the expected transactions (+ the coinbase). - block = ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] - require.Len(ht, block.Transactions, expectedTxes+1) - - // For non-anchor channel types, the nursery will handle sweeping the - // second level output, and it will wait one extra block before - // sweeping it. - secondLevelMaturity := uint32(defaultCSV) - - // If this is a channel of the anchor type, we will subtract one block - // from the default CSV, as the Sweeper will handle the input, and the - // Sweeper sweeps the input as soon as the lock expires. - if hasAnchors { - secondLevelMaturity = defaultCSV - 1 - } - - // Keep track of the second level tx maturity. - carolSecondLevelCSV := secondLevelMaturity - - // When Bob notices Carol's second level transaction in the block, he - // will extract the preimage and broadcast a second level tx to claim - // the HTLC in his (already closed) channel with Alice. - bobSecondLvlTx := ht.Miner.GetNumTxsFromMempool(1)[0] - - // It should spend from the commitment in the channel with Alice. - ht.AssertTxSpendFrom(bobSecondLvlTx, *bobForceClose) - - // At this point, Bob should have broadcast his second layer success - // transaction, and should have sent it to the nursery for incubation. - numPendingChans := 1 - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - numPendingChans++ - } - ht.AssertNumPendingForceClose(bob, numPendingChans) - ht.AssertNumHTLCsAndStage(bob, aliceChanPoint, 1, 1) - - // We'll now mine a block which should confirm Bob's second layer - // transaction. - block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] - bobSecondLvlTxid := bobSecondLvlTx.TxHash() - ht.Miner.AssertTxInBlock(block, &bobSecondLvlTxid) - - // Keep track of Bob's second level maturity, and decrement our track - // of Carol's. - bobSecondLevelCSV := secondLevelMaturity - carolSecondLevelCSV-- - - // Now that the preimage from Bob has hit the chain, restart Alice to - // ensure she'll pick it up. - require.NoError(ht, restartAlice()) - - // If we then mine 3 additional blocks, Carol's second level tx should - // mature, and she can pull the funds from it with a sweep tx. - ht.MineBlocksAssertNodesSync(carolSecondLevelCSV) - carolSweep := ht.Miner.AssertNumTxsInMempool(1)[0] - - // Mining one additional block, Bob's second level tx is mature, and he - // can sweep the output. - bobSecondLevelCSV -= carolSecondLevelCSV - block = ht.Miner.MineBlocksAndAssertNumTxes(bobSecondLevelCSV, 1)[0] - ht.Miner.AssertTxInBlock(block, carolSweep) - - bobSweep := ht.Miner.GetNumTxsFromMempool(1)[0] - bobSweepTxid := bobSweep.TxHash() - - // Make sure it spends from the second level tx. - ht.AssertTxSpendFrom(bobSweep, bobSecondLvlTxid) - - // When we mine one additional block, that will confirm Bob's 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] - ht.Miner.AssertTxInBlock(block, &bobSweepTxid) - - // With the script-enforced lease commitment type, Alice and Bob still - // haven't been able to sweep their respective commit outputs due to the - // additional CLTV. We'll need to mine enough blocks for the timelock to - // expire and prompt their sweep. - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - for _, node := range []*node.HarnessNode{alice, bob} { - ht.AssertNumPendingForceClose(node, 1) - } - - // Due to the way the test is set up, Alice and Bob share the - // same CLTV for their commit outputs even though it's enforced - // on different channels (Alice-Bob and Bob-Carol). - resp := alice.RPC.PendingChannels() - require.Len(ht, resp.PendingForceClosingChannels, 1) - forceCloseChan := resp.PendingForceClosingChannels[0] - require.Positive(ht, forceCloseChan.BlocksTilMaturity) - - // Mine enough blocks for the timelock to expire. - numBlocks := uint32(forceCloseChan.BlocksTilMaturity) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Both Alice and Bob show broadcast their commit sweeps. - aliceCommitOutpoint := wire.OutPoint{ - Hash: *bobForceClose, Index: 3, - } - aliceCommitSweep := ht.Miner.AssertOutpointInMempool( - aliceCommitOutpoint, - ).TxHash() - bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} - bobCommitSweep := ht.Miner.AssertOutpointInMempool( - bobCommitOutpoint, - ).TxHash() - - // Confirm their sweeps. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, 2)[0] - ht.Miner.AssertTxInBlock(block, &aliceCommitSweep) - ht.Miner.AssertTxInBlock(block, &bobCommitSweep) - } - - // All nodes should show zero pending and open channels. - for _, node := range []*node.HarnessNode{alice, bob, carol} { - ht.AssertNumPendingForceClose(node, 0) - ht.AssertNodeNumChannels(node, 0) - } - - // Finally, check that the Alice's payment is correctly marked - // succeeded. - ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) -} diff --git a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go deleted file mode 100644 index c1bf83d12..000000000 --- a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/lncfg" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/stretchr/testify/require" -) - -// testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the -// outgoing HTLC is about to time out, then we'll go to chain in order to claim -// 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, - 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, - ) - - // Now that our channels are set up, we'll send two HTLC's from Alice - // to Carol. The first HTLC will be universally considered "dust", - // while the second will be a proper fully valued HTLC. - const ( - dustHtlcAmt = btcutil.Amount(100) - htlcAmt = btcutil.Amount(300_000) - ) - - // We'll create two random payment hashes unknown to carol, then send - // each of them by manually specifying the HTLC details. - carolPubKey := carol.PubKey[:] - dustPayHash := ht.Random32Bytes() - payHash := ht.Random32Bytes() - - alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{ - Dest: carolPubKey, - Amt: int64(dustHtlcAmt), - PaymentHash: dustPayHash, - FinalCltvDelta: finalCltvDelta, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }) - - alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{ - Dest: carolPubKey, - Amt: int64(htlcAmt), - PaymentHash: payHash, - FinalCltvDelta: finalCltvDelta, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }) - - // Verify that all nodes in the path now have two HTLC's with the - // proper parameters. - ht.AssertActiveHtlcs(alice, dustPayHash, payHash) - ht.AssertActiveHtlcs(bob, dustPayHash, payHash) - ht.AssertActiveHtlcs(carol, dustPayHash, payHash) - - // Increase the fee estimate so that the following force close tx will - // be cpfp'ed. - ht.SetFeeEstimate(30000) - - // We'll now mine enough blocks to trigger Bob's broadcast of his - // commitment transaction due to the fact that the HTLC is about to - // timeout. With the default outgoing broadcast delta of zero, this will - // be the same height as the htlc expiry height. - numBlocks := padCLTV( - uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), - ) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Bob's force close transaction should now be found in the mempool. If - // there are anchors, we also expect Bob's anchor sweep. - expectedTxes := 1 - hasAnchors := commitTypeHasAnchors(c) - if hasAnchors { - expectedTxes = 2 - } - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - op := ht.OutPointFromChannelPoint(bobChanPoint) - closeTx := ht.Miner.AssertOutpointInMempool(op) - - // Mine a block to confirm the closing transaction. - ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) - - // At this point, Bob should have canceled backwards the dust HTLC - // that we sent earlier. This means Alice should now only have a single - // HTLC on her channel. - ht.AssertActiveHtlcs(alice, payHash) - - // With the closing transaction confirmed, we should expect Bob's HTLC - // timeout transaction to be broadcast due to the expiry being reached. - // If there are anchors, we also expect Carol's anchor sweep now. - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - // We'll also obtain the expected HTLC timeout transaction hash. - htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 0} - commitOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 1} - if hasAnchors { - htlcOutpoint.Index = 2 - commitOutpoint.Index = 3 - } - htlcTimeoutTxid := ht.Miner.AssertOutpointInMempool( - htlcOutpoint, - ).TxHash() - - // Mine a block to confirm the expected transactions. - ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) - - // With Bob's HTLC timeout transaction confirmed, there should be no - // active HTLC's on the commitment transaction from Alice -> Bob. - ht.AssertNumActiveHtlcs(alice, 0) - - // At this point, Bob should show that the pending HTLC has advanced to - // the second stage and is ready to be swept once the timelock is up. - pendingChanResp := bob.RPC.PendingChannels() - require.Equal(ht, 1, len(pendingChanResp.PendingForceClosingChannels)) - forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] - require.NotZero(ht, forceCloseChan.LimboBalance) - require.Positive(ht, forceCloseChan.BlocksTilMaturity) - require.Equal(ht, 1, len(forceCloseChan.PendingHtlcs)) - require.Equal(ht, uint32(2), forceCloseChan.PendingHtlcs[0].Stage) - - htlcTimeoutOutpoint := wire.OutPoint{Hash: htlcTimeoutTxid, Index: 0} - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - // Since Bob is the initiator of the script-enforced leased - // channel between him and Carol, he will incur an additional - // CLTV on top of the usual CSV delay on any outputs that he can - // sweep back to his wallet. - blocksTilMaturity := uint32(forceCloseChan.BlocksTilMaturity) - ht.MineBlocksAssertNodesSync(blocksTilMaturity) - - // Check that the sweep spends the expected inputs. - ht.Miner.AssertOutpointInMempool(commitOutpoint) - ht.Miner.AssertOutpointInMempool(htlcTimeoutOutpoint) - } else { - // Since Bob force closed the channel between him and Carol, he - // will incur the usual CSV delay on any outputs that he can - // sweep back to his wallet. We'll subtract one block from our - // current maturity period to assert on the mempool. - numBlocks := uint32(forceCloseChan.BlocksTilMaturity - 1) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Check that the sweep spends from the mined commitment. - ht.Miner.AssertOutpointInMempool(commitOutpoint) - - // Mine a block to confirm Bob's commit sweep tx and assert it - // was in fact mined. - ht.Miner.MineBlocksAndAssertNumTxes(1, 1) - - // Mine an additional block to prompt Bob to broadcast their - // second layer sweep due to the CSV on the HTLC timeout output. - ht.Miner.MineBlocksAndAssertNumTxes(1, 0) - ht.Miner.AssertOutpointInMempool(htlcTimeoutOutpoint) - } - - // Next, we'll mine a final block that should confirm the sweeping - // transactions left. - ht.MineBlocksAssertNodesSync(1) - - // Once this transaction has been confirmed, Bob should detect that he - // no longer has any pending channels. - ht.AssertNumPendingForceClose(bob, 0) - - // Coop close channel, expect no anchors. - ht.CloseChannel(alice, aliceChanPoint) -} diff --git a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go deleted file mode 100644 index 9a5567b3b..000000000 --- a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/wire" - "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/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/require" -) - -// 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 *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 := createThreeHopNetwork( - ht, alice, bob, false, c, 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[:], - } - 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) - - // 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, - )) - ht.MineBlocksAssertNodesSync(numBlocks) - - // At this point, Carol should broadcast her active commitment - // transaction in order to go to the chain and sweep her HTLC. If there - // are anchors, Carol also sweeps hers. - expectedTxes := 1 - hasAnchors := commitTypeHasAnchors(c) - if hasAnchors { - expectedTxes = 2 - } - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - closingTx := ht.Miner.AssertOutpointInMempool( - ht.OutPointFromChannelPoint(bobChanPoint), - ) - closingTxid := closingTx.TxHash() - - // Confirm the commitment. - ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) - - // Restart bob again. - require.NoError(ht, restartBob()) - - // 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 { - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a sweep tx to sweep his output in the channel with - // Carol. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 2 - - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a sweep tx to sweep his output in the channel with - // Carol, and another sweep tx to sweep his anchor output. - case lnrpc.CommitmentType_ANCHORS: - expectedTxes = 3 - - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a sweep tx to sweep his anchor output. 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 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - - // All transactions should be spending from the commitment transaction. - txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) - ht.AssertAllTxesSpendFrom(txes, closingTxid) - - // We'll now mine an additional block which should confirm both the - // 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. - 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. - ht.MineBlocksAssertNodesSync(defaultCSV) - - // We should have a new transaction in the mempool. - ht.Miner.AssertNumTxsInMempool(1) - - // Finally, if we mine an additional block to confirm these two sweep - // transactions, Carol should not show a pending channel in her report - // afterwards. - ht.MineBlocksAssertNodesSync(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 := uint32(forceCloseChan.BlocksTilMaturity) - ht.MineBlocksAssertNodesSync(numBlocks) - commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} - ht.Miner.AssertOutpointInMempool(commitOutpoint) - ht.MineBlocksAssertNodesSync(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) -} diff --git a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go deleted file mode 100644 index 75437c423..000000000 --- a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/wire" - "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/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/require" -) - -// testMultiHopHtlcRemoteChainClaim tests that in the multi-hop HTLC scenario, -// if the remote party goes to chain while we have an incoming HTLC, then when -// 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, - 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, - ) - - // 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[:], - } - 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) - - // Increase the fee estimate so that the following force close tx will - // be cpfp'ed. - ht.SetFeeEstimate(30000) - - // Next, Alice decides that she wants to exit the channel, so she'll - // immediately force close the channel by broadcast her commitment - // transaction. - hasAnchors := commitTypeHasAnchors(c) - closeStream, _ := ht.CloseChannelAssertPending( - alice, aliceChanPoint, true, - ) - aliceForceClose := ht.AssertStreamChannelForceClosed( - alice, aliceChanPoint, hasAnchors, closeStream, - ) - - // 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). - 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 - // order to perform mempool assertions. - ht.MineBlocksAssertNodesSync(defaultCSV - 1) - - // Alice should now sweep her funds. - ht.Miner.AssertNumTxsInMempool(1) - } - - // Suspend bob, so Carol is forced to go on chain. - 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'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, - )) - if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - numBlocks -= defaultCSV - } - ht.MineBlocksAssertNodesSync(numBlocks) - - expectedTxes := 1 - if hasAnchors { - expectedTxes = 2 - } - - // Carol's commitment transaction should now be in the mempool. If - // there are anchors, Carol also sweeps her anchor. - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - // The closing transaction should be spending from the funding - // transaction. - closingTx := ht.Miner.AssertOutpointInMempool( - ht.OutPointFromChannelPoint(bobChanPoint), - ) - closingTxid := closingTx.TxHash() - - // Mine a block, which should contain: the commitment, possibly an - // anchor sweep and the coinbase tx. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] - ht.Miner.AssertTxInBlock(block, &closingTxid) - - // Restart bob again. - require.NoError(ht, restartBob()) - - // After the force close transaction is mined, we should expect Bob and - // Carol to broadcast some transactions depending on the channel - // commitment type. - switch c { - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a transaction to sweep his commitment output. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 2 - - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a transaction to sweep his commitment output and - // another to sweep his anchor output. - case lnrpc.CommitmentType_ANCHORS: - expectedTxes = 3 - - // Carol should broadcast her second level HTLC transaction and Bob - // should broadcast a transaction to sweep his anchor output. Bob can't - // sweep his commitment output yet as he has incurred an additional CLTV - // due to being the channel initiator of a force closed script-enforced - // leased channel. - case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: - expectedTxes = 2 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) - - // All transactions should be pending from the commitment transaction. - ht.AssertAllTxesSpendFrom(txes, closingTxid) - - // Mine a block to confirm the two transactions (+ coinbase). - ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) - - // Keep track of the second level tx maturity. - carolSecondLevelCSV := uint32(defaultCSV) - - // When Bob notices Carol's second level transaction in the block, he - // will extract the preimage and broadcast a sweep tx to directly claim - // the HTLC in his (already closed) channel with Alice. - bobHtlcSweep := ht.Miner.GetNumTxsFromMempool(1)[0] - bobHtlcSweepTxid := bobHtlcSweep.TxHash() - - // It should spend from the commitment in the channel with Alice. - ht.AssertTxSpendFrom(bobHtlcSweep, *aliceForceClose) - - // We'll now mine a block which should confirm Bob's HTLC sweep - // transaction. - block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.Miner.AssertTxInBlock(block, &bobHtlcSweepTxid) - carolSecondLevelCSV-- - - // Now that the sweeping transaction has been confirmed, Bob should now - // recognize that all contracts for the Bob-Carol channel have been - // fully resolved - aliceBobPendingChansLeft := 0 - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - aliceBobPendingChansLeft = 1 - } - for _, node := range []*node.HarnessNode{alice, bob} { - ht.AssertNumPendingForceClose( - node, aliceBobPendingChansLeft, - ) - } - - // If we then mine 3 additional blocks, Carol's second level tx will - // mature, and she should pull the funds. - ht.MineBlocksAssertNodesSync(carolSecondLevelCSV) - carolSweep := ht.Miner.AssertNumTxsInMempool(1)[0] - - // When Carol's sweep gets confirmed, she should have no more pending - // channels. - block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.Miner.AssertTxInBlock(block, carolSweep) - ht.AssertNumPendingForceClose(carol, 0) - - // With the script-enforced lease commitment type, Alice and Bob still - // haven't been able to sweep their respective commit outputs due to the - // additional CLTV. We'll need to mine enough blocks for the timelock to - // expire and prompt their sweep. - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - // Due to the way the test is set up, Alice and Bob share the - // same CLTV for their commit outputs even though it's enforced - // on different channels (Alice-Bob and Bob-Carol). - resp := alice.RPC.PendingChannels() - require.Len(ht, resp.PendingForceClosingChannels, 1) - forceCloseChan := resp.PendingForceClosingChannels[0] - require.Positive(ht, forceCloseChan.BlocksTilMaturity) - - // Mine enough blocks for the timelock to expire. - numBlocks := uint32(forceCloseChan.BlocksTilMaturity) - ht.MineBlocksAssertNodesSync(numBlocks) - - // Both Alice and Bob show broadcast their commit sweeps. - aliceCommitOutpoint := wire.OutPoint{ - Hash: *aliceForceClose, Index: 3, - } - aliceCommitSweep := ht.Miner.AssertOutpointInMempool( - aliceCommitOutpoint, - ) - aliceCommitSweepTxid := aliceCommitSweep.TxHash() - bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} - bobCommitSweep := ht.Miner.AssertOutpointInMempool( - bobCommitOutpoint, - ) - bobCommitSweepTxid := bobCommitSweep.TxHash() - - // Confirm their sweeps. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, 2)[0] - ht.Miner.AssertTxInBlock(block, &aliceCommitSweepTxid) - ht.Miner.AssertTxInBlock(block, &bobCommitSweepTxid) - - // Alice and Bob should not show any pending channels anymore as - // they have been fully resolved. - for _, node := range []*node.HarnessNode{alice, bob} { - ht.AssertNumPendingForceClose(node, 0) - } - } - - // The invoice should show as settled for Carol, indicating that it was - // swept on-chain. - invoice := ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED) - require.Equal(ht, int64(invoiceAmt), invoice.AmtPaidSat) - - // Finally, check that the Alice's payment is correctly marked - // succeeded. - ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) -} 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 deleted file mode 100644 index 82b00d4db..000000000 --- a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package itest - -import ( - "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/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/stretchr/testify/require" -) - -// 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 -// 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, - 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(300_000) - - // We'll now send a single HTLC across our multi-hop network. - carolPubKey := carol.PubKey[:] - 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. - 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. - 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) - stream, _ := ht.CloseChannelAssertPending(bob, bobChanPoint, true) - closeTx := ht.AssertStreamChannelForceClosed( - bob, bobChanPoint, hasAnchors, stream, - ) - - // 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} - bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 1} - if hasAnchors { - htlcOutpoint.Index = 2 - bobCommitOutpoint.Index = 3 - ht.Miner.AssertNumTxsInMempool(1) - } - - // Before the HTLC times out, we'll need to assert that Bob broadcasts a - // sweep transaction for his commit output. Note that if the channel has - // a script-enforced lease, then Bob will have to wait for an additional - // CLTV before sweeping it. - 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) - - commitSweepTx := ht.Miner.AssertOutpointInMempool( - bobCommitOutpoint, - ) - 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 - // should hand off the now expired HTLC output to the utxo nursery. - numBlocks := padCLTV(finalCltvDelta) - 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) - - // Bob's pending channel report should show that he has a single HTLC - // that's now in stage one. - 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 := ht.Miner.AssertOutpointInMempool(htlcOutpoint).TxHash() - - // Next, we'll mine an additional block. This should serve to confirm - // the second layer timeout transaction. - 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. - ht.AssertNumActiveHtlcs(alice, 0) - - // Additionally, Bob should now show that HTLC as being advanced to the - // second stage. - 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. - resp := bob.RPC.PendingChannels() - - require.Len(ht, resp.PendingForceClosingChannels, 1) - forceCloseChan := resp.PendingForceClosingChannels[0] - require.Len(ht, forceCloseChan.PendingHtlcs, 1) - pendingHtlc := forceCloseChan.PendingHtlcs[0] - require.Positive(ht, pendingHtlc.BlocksTilMaturity) - numBlocks = uint32(pendingHtlc.BlocksTilMaturity) - - 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} - sweepTx := ht.Miner.AssertOutpointInMempool( - htlcTimeoutOutpoint, - ).TxHash() - if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { - ht.Miner.AssertOutpointInMempool(bobCommitOutpoint) - } - - 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. - ht.AssertNumPendingForceClose(bob, 0) - - // Coop close, no anchors. - ht.CloseChannel(alice, aliceChanPoint) -} diff --git a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go deleted file mode 100644 index 1767c3421..000000000 --- a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntemp" - "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/require" -) - -// 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 *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 := 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) - - // 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[:], - } - 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[:]) - - // Increase the fee estimate so that the following force close tx will - // be cpfp'ed. - ht.SetFeeEstimate(30000) - - // 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. If - // Carol has an anchor, it will be swept too. - hasAnchors := commitTypeHasAnchors(c) - closeStream, _ := ht.CloseChannelAssertPending( - carol, bobChanPoint, true, - ) - closeTx := ht.AssertStreamChannelForceClosed( - carol, bobChanPoint, hasAnchors, closeStream, - ) - - // 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 output immediately. - case lnrpc.CommitmentType_LEGACY: - expectedTxes = 1 - - // Bob can sweep his commit and anchor outputs immediately. - case lnrpc.CommitmentType_ANCHORS: - expectedTxes = 2 - - // 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 sweep his anchor output however. - case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: - expectedTxes = 1 - - default: - ht.Fatalf("unhandled commitment type %v", c) - } - - ht.Miner.AssertNumTxsInMempool(expectedTxes) - - // 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(finalCltvDelta - 1) - ht.MineBlocksAssertNodesSync(numBlocks) - - // 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 skip the initial - // first stage since this is a direct HTLC. - ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2) - - // We need to generate an additional block to trigger the sweep. - ht.MineBlocksAssertNodesSync(1) - - // 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. - block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.Miner.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 := uint32(forceCloseChan.BlocksTilMaturity) - ht.MineBlocksAssertNodesSync(numBlocks) - - bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3} - bobCommitSweep := ht.Miner.AssertOutpointInMempool( - bobCommitOutpoint, - ) - bobCommitSweepTxid := bobCommitSweep.TxHash() - block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.Miner.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) -} diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index 09b995237..7652112eb 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -8,14 +8,17 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "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/lntemp" "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing" + "github.com/stretchr/testify/require" ) const ( @@ -366,3 +369,1601 @@ func createThreeHopNetwork(ht *lntemp.HarnessTest, return aliceChanPoint, bobChanPoint, carol } + +// testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the +// outgoing HTLC is about to time out, then we'll go to chain in order to claim +// 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, + 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, + ) + + // Now that our channels are set up, we'll send two HTLC's from Alice + // to Carol. The first HTLC will be universally considered "dust", + // while the second will be a proper fully valued HTLC. + const ( + dustHtlcAmt = btcutil.Amount(100) + htlcAmt = btcutil.Amount(300_000) + ) + + // We'll create two random payment hashes unknown to carol, then send + // each of them by manually specifying the HTLC details. + carolPubKey := carol.PubKey[:] + dustPayHash := ht.Random32Bytes() + payHash := ht.Random32Bytes() + + alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{ + Dest: carolPubKey, + Amt: int64(dustHtlcAmt), + PaymentHash: dustPayHash, + FinalCltvDelta: finalCltvDelta, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }) + + alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{ + Dest: carolPubKey, + Amt: int64(htlcAmt), + PaymentHash: payHash, + FinalCltvDelta: finalCltvDelta, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }) + + // Verify that all nodes in the path now have two HTLC's with the + // proper parameters. + ht.AssertActiveHtlcs(alice, dustPayHash, payHash) + ht.AssertActiveHtlcs(bob, dustPayHash, payHash) + ht.AssertActiveHtlcs(carol, dustPayHash, payHash) + + // Increase the fee estimate so that the following force close tx will + // be cpfp'ed. + ht.SetFeeEstimate(30000) + + // We'll now mine enough blocks to trigger Bob's broadcast of his + // commitment transaction due to the fact that the HTLC is about to + // timeout. With the default outgoing broadcast delta of zero, this will + // be the same height as the htlc expiry height. + numBlocks := padCLTV( + uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), + ) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Bob's force close transaction should now be found in the mempool. If + // there are anchors, we also expect Bob's anchor sweep. + expectedTxes := 1 + hasAnchors := commitTypeHasAnchors(c) + if hasAnchors { + expectedTxes = 2 + } + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + op := ht.OutPointFromChannelPoint(bobChanPoint) + closeTx := ht.Miner.AssertOutpointInMempool(op) + + // Mine a block to confirm the closing transaction. + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) + + // At this point, Bob should have canceled backwards the dust HTLC + // that we sent earlier. This means Alice should now only have a single + // HTLC on her channel. + ht.AssertActiveHtlcs(alice, payHash) + + // With the closing transaction confirmed, we should expect Bob's HTLC + // timeout transaction to be broadcast due to the expiry being reached. + // If there are anchors, we also expect Carol's anchor sweep now. + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + // We'll also obtain the expected HTLC timeout transaction hash. + htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 0} + commitOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 1} + if hasAnchors { + htlcOutpoint.Index = 2 + commitOutpoint.Index = 3 + } + htlcTimeoutTxid := ht.Miner.AssertOutpointInMempool( + htlcOutpoint, + ).TxHash() + + // Mine a block to confirm the expected transactions. + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) + + // With Bob's HTLC timeout transaction confirmed, there should be no + // active HTLC's on the commitment transaction from Alice -> Bob. + ht.AssertNumActiveHtlcs(alice, 0) + + // At this point, Bob should show that the pending HTLC has advanced to + // the second stage and is ready to be swept once the timelock is up. + pendingChanResp := bob.RPC.PendingChannels() + require.Equal(ht, 1, len(pendingChanResp.PendingForceClosingChannels)) + forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] + require.NotZero(ht, forceCloseChan.LimboBalance) + require.Positive(ht, forceCloseChan.BlocksTilMaturity) + require.Equal(ht, 1, len(forceCloseChan.PendingHtlcs)) + require.Equal(ht, uint32(2), forceCloseChan.PendingHtlcs[0].Stage) + + htlcTimeoutOutpoint := wire.OutPoint{Hash: htlcTimeoutTxid, Index: 0} + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Since Bob is the initiator of the script-enforced leased + // channel between him and Carol, he will incur an additional + // CLTV on top of the usual CSV delay on any outputs that he can + // sweep back to his wallet. + blocksTilMaturity := uint32(forceCloseChan.BlocksTilMaturity) + ht.MineBlocksAssertNodesSync(blocksTilMaturity) + + // Check that the sweep spends the expected inputs. + ht.Miner.AssertOutpointInMempool(commitOutpoint) + ht.Miner.AssertOutpointInMempool(htlcTimeoutOutpoint) + } else { + // Since Bob force closed the channel between him and Carol, he + // will incur the usual CSV delay on any outputs that he can + // sweep back to his wallet. We'll subtract one block from our + // current maturity period to assert on the mempool. + numBlocks := uint32(forceCloseChan.BlocksTilMaturity - 1) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Check that the sweep spends from the mined commitment. + ht.Miner.AssertOutpointInMempool(commitOutpoint) + + // Mine a block to confirm Bob's commit sweep tx and assert it + // was in fact mined. + ht.Miner.MineBlocksAndAssertNumTxes(1, 1) + + // Mine an additional block to prompt Bob to broadcast their + // second layer sweep due to the CSV on the HTLC timeout output. + ht.Miner.MineBlocksAndAssertNumTxes(1, 0) + ht.Miner.AssertOutpointInMempool(htlcTimeoutOutpoint) + } + + // Next, we'll mine a final block that should confirm the sweeping + // transactions left. + ht.MineBlocksAssertNodesSync(1) + + // Once this transaction has been confirmed, Bob should detect that he + // no longer has any pending channels. + ht.AssertNumPendingForceClose(bob, 0) + + // Coop close channel, expect no anchors. + ht.CloseChannel(alice, aliceChanPoint) +} + +// 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 *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 := createThreeHopNetwork( + ht, alice, bob, false, c, 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[:], + } + 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) + + // 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, + )) + ht.MineBlocksAssertNodesSync(numBlocks) + + // At this point, Carol should broadcast her active commitment + // transaction in order to go to the chain and sweep her HTLC. If there + // are anchors, Carol also sweeps hers. + expectedTxes := 1 + hasAnchors := commitTypeHasAnchors(c) + if hasAnchors { + expectedTxes = 2 + } + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + closingTx := ht.Miner.AssertOutpointInMempool( + ht.OutPointFromChannelPoint(bobChanPoint), + ) + closingTxid := closingTx.TxHash() + + // Confirm the commitment. + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) + + // Restart bob again. + require.NoError(ht, restartBob()) + + // 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 { + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his output in the channel with + // Carol. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his output in the channel with + // Carol, and another sweep tx to sweep his anchor output. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 3 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his anchor output. 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 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + + // All transactions should be spending from the commitment transaction. + txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) + ht.AssertAllTxesSpendFrom(txes, closingTxid) + + // We'll now mine an additional block which should confirm both the + // 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. + 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. + ht.MineBlocksAssertNodesSync(defaultCSV) + + // We should have a new transaction in the mempool. + ht.Miner.AssertNumTxsInMempool(1) + + // Finally, if we mine an additional block to confirm these two sweep + // transactions, Carol should not show a pending channel in her report + // afterwards. + ht.MineBlocksAssertNodesSync(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 := uint32(forceCloseChan.BlocksTilMaturity) + ht.MineBlocksAssertNodesSync(numBlocks) + commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + ht.Miner.AssertOutpointInMempool(commitOutpoint) + ht.MineBlocksAssertNodesSync(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 +// 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, + 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(300_000) + + // We'll now send a single HTLC across our multi-hop network. + carolPubKey := carol.PubKey[:] + 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. + 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. + 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) + stream, _ := ht.CloseChannelAssertPending(bob, bobChanPoint, true) + closeTx := ht.AssertStreamChannelForceClosed( + bob, bobChanPoint, hasAnchors, stream, + ) + + // 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} + bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 1} + if hasAnchors { + htlcOutpoint.Index = 2 + bobCommitOutpoint.Index = 3 + ht.Miner.AssertNumTxsInMempool(1) + } + + // Before the HTLC times out, we'll need to assert that Bob broadcasts a + // sweep transaction for his commit output. Note that if the channel has + // a script-enforced lease, then Bob will have to wait for an additional + // CLTV before sweeping it. + 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) + + commitSweepTx := ht.Miner.AssertOutpointInMempool( + bobCommitOutpoint, + ) + 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 + // should hand off the now expired HTLC output to the utxo nursery. + numBlocks := padCLTV(finalCltvDelta) + 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) + + // Bob's pending channel report should show that he has a single HTLC + // that's now in stage one. + 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 := ht.Miner.AssertOutpointInMempool(htlcOutpoint).TxHash() + + // Next, we'll mine an additional block. This should serve to confirm + // the second layer timeout transaction. + 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. + ht.AssertNumActiveHtlcs(alice, 0) + + // Additionally, Bob should now show that HTLC as being advanced to the + // second stage. + 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. + resp := bob.RPC.PendingChannels() + + require.Len(ht, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Len(ht, forceCloseChan.PendingHtlcs, 1) + pendingHtlc := forceCloseChan.PendingHtlcs[0] + require.Positive(ht, pendingHtlc.BlocksTilMaturity) + numBlocks = uint32(pendingHtlc.BlocksTilMaturity) + + 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} + sweepTx := ht.Miner.AssertOutpointInMempool( + htlcTimeoutOutpoint, + ).TxHash() + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + ht.Miner.AssertOutpointInMempool(bobCommitOutpoint) + } + + 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. + ht.AssertNumPendingForceClose(bob, 0) + + // Coop close, no anchors. + ht.CloseChannel(alice, aliceChanPoint) +} + +// 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 *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 := 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) + + // 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[:], + } + 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[:]) + + // Increase the fee estimate so that the following force close tx will + // be cpfp'ed. + ht.SetFeeEstimate(30000) + + // 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. If + // Carol has an anchor, it will be swept too. + hasAnchors := commitTypeHasAnchors(c) + closeStream, _ := ht.CloseChannelAssertPending( + carol, bobChanPoint, true, + ) + closeTx := ht.AssertStreamChannelForceClosed( + carol, bobChanPoint, hasAnchors, closeStream, + ) + + // 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 output immediately. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 1 + + // Bob can sweep his commit and anchor outputs immediately. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 2 + + // 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 sweep his anchor output however. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 1 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + // 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(finalCltvDelta - 1) + ht.MineBlocksAssertNodesSync(numBlocks) + + // 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 skip the initial + // first stage since this is a direct HTLC. + ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2) + + // We need to generate an additional block to trigger the sweep. + ht.MineBlocksAssertNodesSync(1) + + // 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. + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.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 := uint32(forceCloseChan.BlocksTilMaturity) + ht.MineBlocksAssertNodesSync(numBlocks) + + bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3} + bobCommitSweep := ht.Miner.AssertOutpointInMempool( + bobCommitOutpoint, + ) + bobCommitSweepTxid := bobCommitSweep.TxHash() + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.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 +// 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, + 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, + ) + + // 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[:], + } + 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) + + // Increase the fee estimate so that the following force close tx will + // be cpfp'ed. + ht.SetFeeEstimate(30000) + + // At this point, Bob decides that he wants to exit the channel + // immediately, so he force closes his commitment transaction. + hasAnchors := commitTypeHasAnchors(c) + closeStream, _ := ht.CloseChannelAssertPending( + bob, aliceChanPoint, true, + ) + bobForceClose := ht.AssertStreamChannelForceClosed( + bob, aliceChanPoint, hasAnchors, closeStream, + ) + + var expectedTxes int + switch c { + // Alice will sweep her commitment output immediately. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 1 + + // Alice will sweep her commitment and anchor output immediately. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 2 + + // Alice will sweep her anchor output immediately. Her commitment + // output cannot be swept yet as it has incurred an additional CLTV due + // to being the initiator of a script-enforced leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 1 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + // Suspend Bob to force Carol to go to chain. + 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'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)) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Carol's commitment transaction should now be in the mempool. If + // there is an anchor, Carol will sweep that too. + if commitTypeHasAnchors(c) { + expectedTxes = 2 + } + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + // Look up the closing transaction. It should be spending from the + // funding transaction, + closingTx := ht.Miner.AssertOutpointInMempool( + ht.OutPointFromChannelPoint(bobChanPoint), + ) + closingTxid := closingTx.TxHash() + + // Mine a block that should confirm the commit tx, the anchor if + // present and the coinbase. + block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] + ht.Miner.AssertTxInBlock(block, &closingTxid) + + // Restart bob again. + require.NoError(ht, restartBob()) + + // After the force close transaction is mined, transactions will be + // broadcast by both Bob and Carol. + switch c { + // Carol will broadcast her second level HTLC transaction and Bob will + // sweep his commitment output. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol will broadcast her second level HTLC transaction and Bob will + // sweep his commitment and anchor output. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 3 + + // Carol will broadcast her second level HTLC transaction and anchor + // sweep, and Bob will sweep his anchor output. Bob can't sweep his + // commitment output yet as it has incurred an additional CLTV due to + // being the initiator of a script-enforced leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 2 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + + txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) + + // Both Carol's second level transaction and Bob's sweep should be + // spending from the commitment transaction. + ht.AssertAllTxesSpendFrom(txes, closingTxid) + + // At this point we suspend Alice to make sure she'll handle the + // on-chain settle after a restart. + restartAlice := ht.SuspendNode(alice) + + // Mine a block to confirm the expected transactions (+ the coinbase). + block = ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] + require.Len(ht, block.Transactions, expectedTxes+1) + + // For non-anchor channel types, the nursery will handle sweeping the + // second level output, and it will wait one extra block before + // sweeping it. + secondLevelMaturity := uint32(defaultCSV) + + // If this is a channel of the anchor type, we will subtract one block + // from the default CSV, as the Sweeper will handle the input, and the + // Sweeper sweeps the input as soon as the lock expires. + if hasAnchors { + secondLevelMaturity = defaultCSV - 1 + } + + // Keep track of the second level tx maturity. + carolSecondLevelCSV := secondLevelMaturity + + // When Bob notices Carol's second level transaction in the block, he + // will extract the preimage and broadcast a second level tx to claim + // the HTLC in his (already closed) channel with Alice. + bobSecondLvlTx := ht.Miner.GetNumTxsFromMempool(1)[0] + + // It should spend from the commitment in the channel with Alice. + ht.AssertTxSpendFrom(bobSecondLvlTx, *bobForceClose) + + // At this point, Bob should have broadcast his second layer success + // transaction, and should have sent it to the nursery for incubation. + numPendingChans := 1 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + numPendingChans++ + } + ht.AssertNumPendingForceClose(bob, numPendingChans) + ht.AssertNumHTLCsAndStage(bob, aliceChanPoint, 1, 1) + + // We'll now mine a block which should confirm Bob's second layer + // transaction. + block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + bobSecondLvlTxid := bobSecondLvlTx.TxHash() + ht.Miner.AssertTxInBlock(block, &bobSecondLvlTxid) + + // Keep track of Bob's second level maturity, and decrement our track + // of Carol's. + bobSecondLevelCSV := secondLevelMaturity + carolSecondLevelCSV-- + + // Now that the preimage from Bob has hit the chain, restart Alice to + // ensure she'll pick it up. + require.NoError(ht, restartAlice()) + + // If we then mine 3 additional blocks, Carol's second level tx should + // mature, and she can pull the funds from it with a sweep tx. + ht.MineBlocksAssertNodesSync(carolSecondLevelCSV) + carolSweep := ht.Miner.AssertNumTxsInMempool(1)[0] + + // Mining one additional block, Bob's second level tx is mature, and he + // can sweep the output. + bobSecondLevelCSV -= carolSecondLevelCSV + block = ht.Miner.MineBlocksAndAssertNumTxes(bobSecondLevelCSV, 1)[0] + ht.Miner.AssertTxInBlock(block, carolSweep) + + bobSweep := ht.Miner.GetNumTxsFromMempool(1)[0] + bobSweepTxid := bobSweep.TxHash() + + // Make sure it spends from the second level tx. + ht.AssertTxSpendFrom(bobSweep, bobSecondLvlTxid) + + // When we mine one additional block, that will confirm Bob's 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] + ht.Miner.AssertTxInBlock(block, &bobSweepTxid) + + // With the script-enforced lease commitment type, Alice and Bob still + // haven't been able to sweep their respective commit outputs due to the + // additional CLTV. We'll need to mine enough blocks for the timelock to + // expire and prompt their sweep. + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + for _, node := range []*node.HarnessNode{alice, bob} { + ht.AssertNumPendingForceClose(node, 1) + } + + // Due to the way the test is set up, Alice and Bob share the + // same CLTV for their commit outputs even though it's enforced + // on different channels (Alice-Bob and Bob-Carol). + resp := alice.RPC.PendingChannels() + require.Len(ht, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(ht, forceCloseChan.BlocksTilMaturity) + + // Mine enough blocks for the timelock to expire. + numBlocks := uint32(forceCloseChan.BlocksTilMaturity) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Both Alice and Bob show broadcast their commit sweeps. + aliceCommitOutpoint := wire.OutPoint{ + Hash: *bobForceClose, Index: 3, + } + aliceCommitSweep := ht.Miner.AssertOutpointInMempool( + aliceCommitOutpoint, + ).TxHash() + bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + bobCommitSweep := ht.Miner.AssertOutpointInMempool( + bobCommitOutpoint, + ).TxHash() + + // Confirm their sweeps. + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 2)[0] + ht.Miner.AssertTxInBlock(block, &aliceCommitSweep) + ht.Miner.AssertTxInBlock(block, &bobCommitSweep) + } + + // All nodes should show zero pending and open channels. + for _, node := range []*node.HarnessNode{alice, bob, carol} { + ht.AssertNumPendingForceClose(node, 0) + ht.AssertNodeNumChannels(node, 0) + } + + // Finally, check that the Alice's payment is correctly marked + // succeeded. + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) +} + +// testMultiHopHtlcRemoteChainClaim tests that in the multi-hop HTLC scenario, +// if the remote party goes to chain while we have an incoming HTLC, then when +// 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, + 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, + ) + + // 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[:], + } + 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) + + // Increase the fee estimate so that the following force close tx will + // be cpfp'ed. + ht.SetFeeEstimate(30000) + + // Next, Alice decides that she wants to exit the channel, so she'll + // immediately force close the channel by broadcast her commitment + // transaction. + hasAnchors := commitTypeHasAnchors(c) + closeStream, _ := ht.CloseChannelAssertPending( + alice, aliceChanPoint, true, + ) + aliceForceClose := ht.AssertStreamChannelForceClosed( + alice, aliceChanPoint, hasAnchors, closeStream, + ) + + // 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). + 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 + // order to perform mempool assertions. + ht.MineBlocksAssertNodesSync(defaultCSV - 1) + + // Alice should now sweep her funds. + ht.Miner.AssertNumTxsInMempool(1) + } + + // Suspend bob, so Carol is forced to go on chain. + 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'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, + )) + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + numBlocks -= defaultCSV + } + ht.MineBlocksAssertNodesSync(numBlocks) + + expectedTxes := 1 + if hasAnchors { + expectedTxes = 2 + } + + // Carol's commitment transaction should now be in the mempool. If + // there are anchors, Carol also sweeps her anchor. + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + // The closing transaction should be spending from the funding + // transaction. + closingTx := ht.Miner.AssertOutpointInMempool( + ht.OutPointFromChannelPoint(bobChanPoint), + ) + closingTxid := closingTx.TxHash() + + // Mine a block, which should contain: the commitment, possibly an + // anchor sweep and the coinbase tx. + block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] + ht.Miner.AssertTxInBlock(block, &closingTxid) + + // Restart bob again. + require.NoError(ht, restartBob()) + + // After the force close transaction is mined, we should expect Bob and + // Carol to broadcast some transactions depending on the channel + // commitment type. + switch c { + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his commitment output. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his commitment output and + // another to sweep his anchor output. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 3 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his anchor output. Bob can't + // sweep his commitment output yet as he has incurred an additional CLTV + // due to being the channel initiator of a force closed script-enforced + // leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 2 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) + + // All transactions should be pending from the commitment transaction. + ht.AssertAllTxesSpendFrom(txes, closingTxid) + + // Mine a block to confirm the two transactions (+ coinbase). + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) + + // Keep track of the second level tx maturity. + carolSecondLevelCSV := uint32(defaultCSV) + + // When Bob notices Carol's second level transaction in the block, he + // will extract the preimage and broadcast a sweep tx to directly claim + // the HTLC in his (already closed) channel with Alice. + bobHtlcSweep := ht.Miner.GetNumTxsFromMempool(1)[0] + bobHtlcSweepTxid := bobHtlcSweep.TxHash() + + // It should spend from the commitment in the channel with Alice. + ht.AssertTxSpendFrom(bobHtlcSweep, *aliceForceClose) + + // We'll now mine a block which should confirm Bob's HTLC sweep + // transaction. + block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, &bobHtlcSweepTxid) + carolSecondLevelCSV-- + + // Now that the sweeping transaction has been confirmed, Bob should now + // recognize that all contracts for the Bob-Carol channel have been + // fully resolved + aliceBobPendingChansLeft := 0 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + aliceBobPendingChansLeft = 1 + } + for _, node := range []*node.HarnessNode{alice, bob} { + ht.AssertNumPendingForceClose( + node, aliceBobPendingChansLeft, + ) + } + + // If we then mine 3 additional blocks, Carol's second level tx will + // mature, and she should pull the funds. + ht.MineBlocksAssertNodesSync(carolSecondLevelCSV) + carolSweep := ht.Miner.AssertNumTxsInMempool(1)[0] + + // When Carol's sweep gets confirmed, she should have no more pending + // channels. + block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, carolSweep) + ht.AssertNumPendingForceClose(carol, 0) + + // With the script-enforced lease commitment type, Alice and Bob still + // haven't been able to sweep their respective commit outputs due to the + // additional CLTV. We'll need to mine enough blocks for the timelock to + // expire and prompt their sweep. + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Due to the way the test is set up, Alice and Bob share the + // same CLTV for their commit outputs even though it's enforced + // on different channels (Alice-Bob and Bob-Carol). + resp := alice.RPC.PendingChannels() + require.Len(ht, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(ht, forceCloseChan.BlocksTilMaturity) + + // Mine enough blocks for the timelock to expire. + numBlocks := uint32(forceCloseChan.BlocksTilMaturity) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Both Alice and Bob show broadcast their commit sweeps. + aliceCommitOutpoint := wire.OutPoint{ + Hash: *aliceForceClose, Index: 3, + } + aliceCommitSweep := ht.Miner.AssertOutpointInMempool( + aliceCommitOutpoint, + ) + aliceCommitSweepTxid := aliceCommitSweep.TxHash() + bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + bobCommitSweep := ht.Miner.AssertOutpointInMempool( + bobCommitOutpoint, + ) + bobCommitSweepTxid := bobCommitSweep.TxHash() + + // Confirm their sweeps. + block := ht.Miner.MineBlocksAndAssertNumTxes(1, 2)[0] + ht.Miner.AssertTxInBlock(block, &aliceCommitSweepTxid) + ht.Miner.AssertTxInBlock(block, &bobCommitSweepTxid) + + // Alice and Bob should not show any pending channels anymore as + // they have been fully resolved. + for _, node := range []*node.HarnessNode{alice, bob} { + ht.AssertNumPendingForceClose(node, 0) + } + } + + // The invoice should show as settled for Carol, indicating that it was + // swept on-chain. + invoice := ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED) + require.Equal(ht, int64(invoiceAmt), invoice.AmtPaidSat) + + // Finally, check that the Alice's payment is correctly marked + // succeeded. + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) +} + +// testMultiHopHtlcAggregation tests that in a multi-hop HTLC scenario, if we +// force close a channel with both incoming and outgoing HTLCs, we can properly +// 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, + alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { + + // First, we'll create a three hop network: Alice -> Bob -> Carol. + aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork( + ht, alice, bob, false, c, zeroConf, + ) + + // For neutrino backend, we need one additional UTXO to create + // the sweeping tx for the second-level success txes. + if ht.IsNeutrinoBackend() { + ht.FundCoins(btcutil.SatoshiPerBitcoin, bob) + } + + // To ensure we have capacity in both directions of the route, we'll + // make a fairly large payment Alice->Carol and settle it. + 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) + + // 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 + // enough, otherwise Bob won't send out the outgoing htlc. + const numInvoices = 5 + const invoiceAmt = 50_000 + + var ( + carolInvoices []*invoicesrpc.AddHoldInvoiceResp + aliceInvoices []*invoicesrpc.AddHoldInvoiceResp + alicePreimages []lntypes.Preimage + payHashes [][]byte + invoiceStreamsCarol []rpc.SingleInvoiceClient + invoiceStreamsAlice []rpc.SingleInvoiceClient + ) + + // Add Carol invoices. + for i := 0; i < numInvoices; i++ { + var preimage lntypes.Preimage + copy(preimage[:], ht.Random32Bytes()) + payHash := preimage.Hash() + invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ + Value: invoiceAmt, + CltvExpiry: finalCltvDelta, + Hash: payHash[:], + } + carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq) + + carolInvoices = append(carolInvoices, carolInvoice) + payHashes = append(payHashes, payHash[:]) + + // Subscribe the invoice. + stream := carol.RPC.SubscribeSingleInvoice(payHash[:]) + invoiceStreamsCarol = append(invoiceStreamsCarol, stream) + } + + // We'll give Alice's invoices a longer CLTV expiry, to ensure the + // channel Bob<->Carol will be closed first. + for i := 0; i < numInvoices; i++ { + var preimage lntypes.Preimage + copy(preimage[:], ht.Random32Bytes()) + payHash := preimage.Hash() + invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ + Value: invoiceAmt, + CltvExpiry: 2 * finalCltvDelta, + Hash: payHash[:], + } + aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq) + + aliceInvoices = append(aliceInvoices, aliceInvoice) + alicePreimages = append(alicePreimages, preimage) + payHashes = append(payHashes, payHash[:]) + + // Subscribe the invoice. + stream := alice.RPC.SubscribeSingleInvoice(payHash[:]) + invoiceStreamsAlice = append(invoiceStreamsAlice, stream) + } + + // Now that we've created the invoices, we'll pay them all from + // Alice<->Carol, going through Bob. We won't wait for the response + // however, as neither will immediately settle the payment. + + // Alice will pay all of Carol's invoices. + for _, carolInvoice := range carolInvoices { + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: carolInvoice.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + alice.RPC.SendPayment(req) + } + + // And Carol will pay Alice's. + for _, aliceInvoice := range aliceInvoices { + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: aliceInvoice.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + carol.RPC.SendPayment(req) + } + + // At this point, all 3 nodes should now the HTLCs active on their + // channels. + ht.AssertActiveHtlcs(alice, payHashes...) + ht.AssertActiveHtlcs(bob, payHashes...) + ht.AssertActiveHtlcs(carol, payHashes...) + + // Wait for Alice and Carol to mark the invoices as accepted. There is + // a small gap to bridge between adding the htlc to the channel and + // executing the exit hop logic. + for _, stream := range invoiceStreamsCarol { + ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED) + } + + for _, stream := range invoiceStreamsAlice { + ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED) + } + + // Increase the fee estimate so that the following force close tx will + // be cpfp'ed. + ht.SetFeeEstimate(30000) + + // We want Carol's htlcs to expire off-chain to demonstrate bob's force + // close. However, Carol will cancel her invoices to prevent force + // closes, so we shut her down for now. + restartCarol := ht.SuspendNode(carol) + + // We'll now mine enough blocks to trigger Bob's broadcast of his + // commitment transaction due to the fact that the Carol's HTLCs are + // about to timeout. With the default outgoing broadcast delta of zero, + // this will be the same height as the htlc expiry height. + numBlocks := padCLTV( + uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), + ) + ht.MineBlocksAssertNodesSync(numBlocks) + + // Bob's force close transaction should now be found in the mempool. If + // there are anchors, we also expect Bob's anchor sweep. + hasAnchors := commitTypeHasAnchors(c) + expectedTxes := 1 + if hasAnchors { + expectedTxes = 2 + } + ht.Miner.AssertNumTxsInMempool(expectedTxes) + + closeTx := ht.Miner.AssertOutpointInMempool( + ht.OutPointFromChannelPoint(bobChanPoint), + ) + closeTxid := closeTx.TxHash() + + // Go through the closing transaction outputs, and make an index for + // the HTLC outputs. + successOuts := make(map[wire.OutPoint]struct{}) + timeoutOuts := make(map[wire.OutPoint]struct{}) + for i, txOut := range closeTx.TxOut { + op := wire.OutPoint{ + Hash: closeTxid, + Index: uint32(i), + } + + switch txOut.Value { + // If this HTLC goes towards Carol, Bob will claim it with a + // timeout Tx. In this case the value will be the invoice + // amount. + case invoiceAmt: + timeoutOuts[op] = struct{}{} + + // If the HTLC has direction towards Alice, Bob will claim it + // with the success TX when he learns the preimage. In this + // case one extra sat will be on the output, because of the + // routing fee. + case invoiceAmt + 1: + successOuts[op] = struct{}{} + } + } + + // Once bob has force closed, we can restart carol. + require.NoError(ht, restartCarol()) + + // Mine a block to confirm the closing transaction. + ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes) + + // Let Alice settle her invoices. When Bob now gets the preimages, he + // has no other option than to broadcast his second-level transactions + // to claim the money. + for _, preimage := range alicePreimages { + alice.RPC.SettleInvoice(preimage[:]) + } + + switch c { + // With the closing transaction confirmed, we should expect Bob's HTLC + // timeout transactions to be broadcast due to the expiry being reached. + // We will also expect the success transactions, since he learnt the + // preimages from Alice. We also expect Carol to sweep her commitment + // output. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2*numInvoices + 1 + + // In case of anchors, all success transactions will be aggregated into + // one, the same is the case for the timeout transactions. In this case + // Carol will also sweep her commitment and anchor output as separate + // txs (since it will be low fee). + case lnrpc.CommitmentType_ANCHORS, + lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 4 + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + txes := ht.Miner.GetNumTxsFromMempool(expectedTxes) + + // Since Bob can aggregate the transactions, we expect a single + // transaction, that have multiple spends from the commitment. + var ( + timeoutTxs []*chainhash.Hash + successTxs []*chainhash.Hash + ) + for _, tx := range txes { + txid := tx.TxHash() + + for i := range tx.TxIn { + prevOp := tx.TxIn[i].PreviousOutPoint + if _, ok := successOuts[prevOp]; ok { + successTxs = append(successTxs, &txid) + break + } + + if _, ok := timeoutOuts[prevOp]; ok { + timeoutTxs = append(timeoutTxs, &txid) + break + } + } + } + + // In case of anchor we expect all the timeout and success second + // levels to be aggregated into one tx. For earlier channel types, they + // will be separate transactions. + if hasAnchors { + require.Len(ht, timeoutTxs, 1) + require.Len(ht, successTxs, 1) + } else { + require.Len(ht, timeoutTxs, numInvoices) + require.Len(ht, successTxs, numInvoices) + } + + // All mempool transactions should be spending from the commitment + // transaction. + ht.AssertAllTxesSpendFrom(txes, closeTxid) + + // 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) + + // At this point, Bob should have broadcast his second layer success + // transaction, and should have sent it to the nursery for incubation, + // or to the sweeper for sweeping. + ht.AssertNumPendingForceClose(bob, 1) + + // For this channel, we also check the number of HTLCs and the stage + // are correct. + ht.AssertNumHTLCsAndStage(bob, bobChanPoint, numInvoices*2, 2) + + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // If we then mine additional blocks, Bob can sweep his + // commitment output. + ht.MineBlocksAssertNodesSync(defaultCSV - 2) + + // Find the commitment sweep. + bobCommitSweep := ht.Miner.GetNumTxsFromMempool(1)[0] + ht.AssertTxSpendFrom(bobCommitSweep, closeTxid) + + // Also ensure it is not spending from any of the HTLC output. + for _, txin := range bobCommitSweep.TxIn { + for _, timeoutTx := range timeoutTxs { + require.NotEqual(ht, *timeoutTx, + txin.PreviousOutPoint.Hash, + "found unexpected spend of timeout tx") + } + + for _, successTx := range successTxs { + require.NotEqual(ht, *successTx, + txin.PreviousOutPoint.Hash, + "found unexpected spend of success tx") + } + } + } + + 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 + // are mined, we should expect to see Bob's commit sweep in the mempool. + case lnrpc.CommitmentType_LEGACY: + ht.Miner.MineBlocksAndAssertNumTxes(2, 1) + + // Mining one additional block, Bob's second level tx is mature, and he + // can sweep the output. Before the blocks are mined, we should expect + // to see Bob's commit sweep in the mempool. + case lnrpc.CommitmentType_ANCHORS: + ht.Miner.MineBlocksAndAssertNumTxes(1, 1) + + // Since Bob is the initiator of the Bob-Carol script-enforced leased + // channel, he incurs an additional CLTV when sweeping outputs back to + // his wallet. We'll need to mine enough blocks for the timelock to + // expire to prompt his broadcast. + case 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 := uint32(forceCloseChan.BlocksTilMaturity) + + // Add debug log. + _, height := ht.Miner.GetBestBlock() + bob.AddToLogf("itest: now mine %d blocks at height %d", + numBlocks, height) + ht.MineBlocksAssertNodesSync(numBlocks) + + default: + ht.Fatalf("unhandled commitment type %v", c) + } + + // Make sure it spends from the second level tx. + secondLevelSweep := ht.Miner.GetNumTxsFromMempool(1)[0] + bobSweep := secondLevelSweep.TxHash() + + // It should be sweeping all the second-level outputs. + var secondLvlSpends int + for _, txin := range secondLevelSweep.TxIn { + for _, timeoutTx := range timeoutTxs { + if *timeoutTx == txin.PreviousOutPoint.Hash { + secondLvlSpends++ + } + } + + for _, successTx := range successTxs { + if *successTx == txin.PreviousOutPoint.Hash { + secondLvlSpends++ + } + } + } + + require.Equal(ht, 2*numInvoices, secondLvlSpends) + + // 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] + ht.Miner.AssertTxInBlock(block, &bobSweep) + ht.AssertNumPendingForceClose(bob, 0) + + // THe channel with Alice is still open. + ht.AssertNodeNumChannels(bob, 1) + + // Carol should have no channels left (open nor pending). + ht.AssertNumPendingForceClose(carol, 0) + ht.AssertNodeNumChannels(carol, 0) + + // Coop close, no anchors. + ht.CloseChannel(alice, aliceChanPoint) +}