package itest import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/routing" "github.com/stretchr/testify/require" ) const ( finalCltvDelta = routing.MinCLTVDelta // 18. thawHeightDelta = finalCltvDelta * 2 // 36. ) // makeRouteHints creates a route hints that will allow Carol to be reached // using an unadvertised channel created by Bob (Bob -> Carol). If the zeroConf // bool is set, then the scid alias of Bob will be used in place. func makeRouteHints(bob, carol *node.HarnessNode, zeroConf bool) []*lnrpc.RouteHint { carolChans := carol.RPC.ListChannels( &lnrpc.ListChannelsRequest{}, ) carolChan := carolChans.Channels[0] hopHint := &lnrpc.HopHint{ NodeId: carolChan.RemotePubkey, ChanId: carolChan.ChanId, FeeBaseMsat: uint32( chainreg.DefaultBitcoinBaseFeeMSat, ), FeeProportionalMillionths: uint32( chainreg.DefaultBitcoinFeeRate, ), CltvExpiryDelta: chainreg.DefaultBitcoinTimeLockDelta, } if zeroConf { bobChans := bob.RPC.ListChannels( &lnrpc.ListChannelsRequest{}, ) // Now that we have Bob's channels, scan for the channel he has // open to Carol so we can use the proper scid. var found bool for _, bobChan := range bobChans.Channels { if bobChan.RemotePubkey == carol.PubKeyStr { hopHint.ChanId = bobChan.AliasScids[0] found = true break } } if !found { bob.Fatalf("unable to create route hint") } } return []*lnrpc.RouteHint{ { HopHints: []*lnrpc.HopHint{hopHint}, }, } } // testHtlcTimeoutResolverExtractPreimageRemote tests that in the multi-hop // setting, Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using // the 2nd level success tx2nd level success tx, Bob's timeout resolver will // extract the preimage from the sweep tx found in mempool. The 2nd level // success tx is broadcast by Carol and spends the outpoint on her commit tx. func testHtlcTimeoutResolverExtractPreimageRemote(ht *lntest.HarnessTest) { // For neutrino backend there's no mempool source so we skip it. The // test of extracting preimage from blocks has already been covered in // other tests. if ht.IsNeutrinoBackend() { ht.Skip("skipping neutrino") } // Set the min relay feerate to be 10 sat/vbyte so the non-CPFP anchor // is never swept. // // TODO(yy): delete this line once the normal anchor sweeping is // removed. ht.SetMinRelayFeerate(10_000) // Create a three hop network: Alice -> Bob -> Carol, using // anchor channels. // // Prepare params. params := lntest.OpenChannelParams{Amt: chanAmt} cfg := node.CfgAnchor cfgs := [][]string{cfg, cfg, cfg} // First, we'll create a three hop network: Alice -> Bob -> Carol, with // Carol refusing to actually settle or directly cancel any HTLC's // self. chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params) alice, bob, carol := nodes[0], nodes[1], nodes[2] aliceChanPoint, bobChanPoint := chanPoints[0], chanPoints[1] ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // 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. preimage := ht.RandomPreimage() payHash := preimage.Hash() invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ Value: 100_000, CltvExpiry: finalCltvDelta, Hash: payHash[:], } eveInvoice := 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: eveInvoice.PaymentRequest, FeeLimitMsat: noFeeLimitMsat, } ht.SendPaymentAssertInflight(alice, req) // Once the payment sent, Alice should have one outgoing HTLC active. ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:]) // Bob should have two HTLCs active. One incoming HTLC from Alice, and // one outgoing to Carol. ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:]) htlc := ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:]) // Carol should have one incoming HTLC from Bob. ht.AssertIncomingHTLCActive(carol, bobChanPoint, 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) // Bob now goes offline so the link between Bob and Carol is broken. restartBob := ht.SuspendNode(bob) // Carol now settles the invoice, since her link with Bob is broken, // Bob won't know the preimage. carol.RPC.SettleInvoice(preimage[:]) // We'll now mine enough blocks to trigger Carol's broadcast of her // commitment transaction due to the fact that the HTLC is about to // timeout. With the default incoming broadcast delta of 10, this // will be the htlc expiry height minus 10. numBlocks := padCLTV(uint32( invoiceReq.CltvExpiry - incomingBroadcastDelta, )) ht.MineBlocks(int(numBlocks)) // Mine the two txns made from Carol, // - the force close tx. // - the anchor sweeping tx. ht.MineBlocksAndAssertNumTxes(1, 2) // With the closing transaction confirmed, we should expect Carol's // HTLC success transaction to be offered to the sweeper. along with her // anchor output. Note that the anchor output is uneconomical to sweep. ht.AssertNumPendingSweeps(carol, 1) // We should now have Carol's htlc success tx in the mempool. ht.AssertNumTxsInMempool(1) // Restart Bob. Once he finishes syncing the channel state, he should // notice the force close from Carol. require.NoError(ht, restartBob()) // Get the current height to compute number of blocks to mine to // trigger the htlc timeout resolver from Bob. height := ht.CurrentHeight() // We'll now mine enough blocks to trigger Bob's timeout resolver. numBlocks = htlc.ExpirationHeight - height - lncfg.DefaultOutgoingBroadcastDelta // Mine empty blocks so Carol's htlc success tx stays in mempool. Once // the height is reached, Bob's timeout resolver will resolve the htlc // by extracing the preimage from the mempool. ht.MineEmptyBlocks(int(numBlocks)) // Finally, check that the Alice's payment is marked as succeeded as // Bob has settled the htlc using the preimage extracted from Carol's // 2nd level success tx. ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) // Mine a block to clean the mempool. ht.MineBlocksAndAssertNumTxes(1, 2) // NOTE: for non-standby nodes there's no need to clean up the force // close as long as the mempool is cleaned. ht.CleanShutDown() } // testHtlcTimeoutResolverExtractPreimage tests that in the multi-hop setting, // Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using the // direct preimage spend, Bob's timeout resolver will extract the preimage from // the sweep tx found in mempool. The direct spend tx is broadcast by Carol and // spends the outpoint on Bob's commit tx. func testHtlcTimeoutResolverExtractPreimageLocal(ht *lntest.HarnessTest) { // For neutrino backend there's no mempool source so we skip it. The // test of extracting preimage from blocks has already been covered in // other tests. if ht.IsNeutrinoBackend() { ht.Skip("skipping neutrino") } // Set the min relay feerate to be 10 sat/vbyte so the non-CPFP anchor // is never swept. // // TODO(yy): delete this line once the normal anchor sweeping is // removed. ht.SetMinRelayFeerate(10_000) // Create a three hop network: Alice -> Bob -> Carol, using // anchor channels. // // Prepare params. params := lntest.OpenChannelParams{Amt: chanAmt} cfg := node.CfgAnchor cfgs := [][]string{cfg, cfg, cfg} // First, we'll create a three hop network: Alice -> Bob -> Carol, with // Carol refusing to actually settle or directly cancel any HTLC's // self. // Create a three hop network: Alice -> Bob -> Carol. chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params) alice, bob, carol := nodes[0], nodes[1], nodes[2] aliceChanPoint, bobChanPoint := chanPoints[0], chanPoints[1] // 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. preimage := ht.RandomPreimage() payHash := preimage.Hash() invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ Value: 100_000, CltvExpiry: finalCltvDelta, Hash: payHash[:], } carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq) // Record the height which the invoice will expire. invoiceExpiry := ht.CurrentHeight() + uint32(invoiceReq.CltvExpiry) // 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, FeeLimitMsat: noFeeLimitMsat, } ht.SendPaymentAssertInflight(alice, req) // Once the payment sent, Alice should have one outgoing HTLC active. ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:]) // Bob should have two HTLCs active. One incoming HTLC from Alice, and // one outgoing to Carol. ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:]) ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:]) // Carol should have one incoming HTLC from Bob. ht.AssertIncomingHTLCActive(carol, bobChanPoint, 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) // Bob now goes offline so the link between Bob and Carol is broken. restartBob := ht.SuspendNode(bob) // Carol now settles the invoice, since her link with Bob is broken, // Bob won't know the preimage. carol.RPC.SettleInvoice(preimage[:]) // Stop Carol so it's easier to check the mempool's state since she // will broadcast the anchor sweeping once Bob force closes. restartCarol := ht.SuspendNode(carol) // Restart Bob to force close the channel. require.NoError(ht, restartBob()) // Bob force closes the channel, which gets his commitment tx into the // mempool. ht.CloseChannelAssertPending(bob, bobChanPoint, true) // Mine Bob's force close tx. ht.MineClosingTx(bobChanPoint) // Once Bob's force closing tx is confirmed, he will re-offer the // anchor output to his sweeper, which won't be swept due to it being // uneconomical. ht.AssertNumPendingSweeps(bob, 1) // Mine 3 blocks so the output will be offered to the sweeper. ht.MineBlocks(defaultCSV - 1) // Bob should have two pending sweeps now, // - the commit output. // - the anchor output, uneconomical. ht.AssertNumPendingSweeps(bob, 2) // Mine a block to confirm Bob's sweeping tx. ht.MineBlocksAndAssertNumTxes(1, 1) ht.Logf("Invoice expire height: %d, current: %d", invoiceExpiry, ht.CurrentHeight()) // We'll now mine enough blocks to trigger Carol's sweeping of the htlc // via the direct spend. numBlocks := padCLTV( invoiceExpiry - ht.CurrentHeight() - incomingBroadcastDelta, ) ht.MineBlocks(int(numBlocks)) // Restart Carol to sweep the htlc output. require.NoError(ht, restartCarol()) // With the above blocks mined, we should expect Carol's to offer the // htlc output on Bob's commitment to the sweeper. // // Carol should two pending sweeps, // - htlc output. // - anchor output, uneconomical. ht.AssertNumPendingSweeps(carol, 2) // Check the current mempool state and we should see, // - Carol's direct spend tx, which contains the preimage. // - Carol's anchor sweep tx cannot be broadcast as it's uneconomical. ht.AssertNumTxsInMempool(1) // We'll now mine enough blocks to trigger Bob's htlc timeout resolver // to act. Once his timeout resolver starts, it will extract the // preimage from Carol's direct spend tx found in the mempool. resp := ht.AssertNumPendingForceClose(bob, 1)[0] require.Equal(ht, 1, len(resp.PendingHtlcs)) ht.Logf("Bob's timelock to_local output=%v, timelock on second stage "+ "htlc=%v", resp.BlocksTilMaturity, resp.PendingHtlcs[0].BlocksTilMaturity) // Mine empty blocks so Carol's direct spend tx stays in mempool. Once // the height is reached, Bob's timeout resolver will resolve the htlc // by extracing the preimage from the mempool. // // TODO(yy): there's no need to wait till the HTLC's CLTV is reached, // Bob's outgoing contest resolver can also monitor the mempool and // resolve the payment even earlier. ht.MineEmptyBlocks(int(resp.PendingHtlcs[0].BlocksTilMaturity)) // Finally, check that the Alice's payment is marked as succeeded as // Bob has settled the htlc using the preimage extracted from Carol's // direct spend tx. ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) // NOTE: for non-standby nodes there's no need to clean up the force // close as long as the mempool is cleaned. ht.CleanShutDown() }