mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-29 15:11:09 +02:00
itest: rename file to reflect the tests
This commit is contained in:
381
itest/lnd_htlc_timeout_resolver_test.go
Normal file
381
itest/lnd_htlc_timeout_resolver_test.go
Normal file
@@ -0,0 +1,381 @@
|
||||
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,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
}
|
||||
alice.RPC.SendPayment(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,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
}
|
||||
alice.RPC.SendPayment(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()
|
||||
}
|
Reference in New Issue
Block a user