mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
382 lines
13 KiB
Go
382 lines
13 KiB
Go
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,
|
|
}
|
|
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,
|
|
TimeoutSeconds: 60,
|
|
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()
|
|
}
|