mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-31 02:01:46 +02:00
itest: add testSendToRouteFailHTLCTimeout
to check SendToRouteV2
This commit is contained in:
parent
21112cfdf8
commit
d28d5d7a47
@ -662,4 +662,8 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "payment succeeded htlc remote swept",
|
||||
TestFunc: testPaymentSucceededHTLCRemoteSwept,
|
||||
},
|
||||
{
|
||||
Name: "send to route failed htlc timeout",
|
||||
TestFunc: testSendToRouteFailHTLCTimeout,
|
||||
},
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -1211,3 +1212,219 @@ func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
||||
// Cancel the context, which will disconnect the above interceptor.
|
||||
cancelInterceptor()
|
||||
}
|
||||
|
||||
// testSendToRouteFailHTLCTimeout is similar to
|
||||
// testPaymentFailedHTLCLocalSwept. The only difference is the `SendPayment` is
|
||||
// replaced with `SendToRouteV2`. It checks that when an outgoing HTLC is timed
|
||||
// out and claimed onchain via the timeout path, the payment will be marked as
|
||||
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
||||
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
||||
// time out. Alice will also be restarted to make sure resumed payments are
|
||||
// also marked as failed.
|
||||
func testSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest) {
|
||||
success := ht.Run("fail payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runSendToRouteFailHTLCTimeout(st, false)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
|
||||
ht.Run("fail resumed payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runTestPaymentHTLCTimeout(st, true)
|
||||
})
|
||||
}
|
||||
|
||||
// runSendToRouteFailHTLCTimeout is the helper function that actually runs the
|
||||
// testSendToRouteFailHTLCTimeout.
|
||||
func runSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest, restartAlice bool) {
|
||||
// Set the feerate to be 10 sat/vb.
|
||||
ht.SetFeeEstimate(2500)
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
chanAmt := btcutil.Amount(100_000)
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
}
|
||||
|
||||
// Create a two hop network: Alice -> Bob.
|
||||
chanPoints, nodes := createSimpleNetwork(ht, nil, 2, openChannelParams)
|
||||
chanPoint := chanPoints[0]
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// We now create two payments, one above dust and the other below dust,
|
||||
// and we should see different behavior in terms of when the payment
|
||||
// will be marked as failed due to the HTLC timeout.
|
||||
//
|
||||
// First, create random preimages.
|
||||
preimage := ht.RandomPreimage()
|
||||
dustPreimage := ht.RandomPreimage()
|
||||
|
||||
// Get the preimage hashes.
|
||||
payHash := preimage.Hash()
|
||||
dustPayHash := dustPreimage.Hash()
|
||||
|
||||
// Create an hold invoice for Bob which expects a payment of 10k
|
||||
// satoshis from Alice.
|
||||
const paymentAmt = 20_000
|
||||
req := &invoicesrpc.AddHoldInvoiceRequest{
|
||||
Value: paymentAmt,
|
||||
Hash: payHash[:],
|
||||
// Use a small CLTV value so we can mine fewer blocks.
|
||||
CltvExpiry: finalCltvDelta,
|
||||
}
|
||||
invoice := bob.RPC.AddHoldInvoice(req)
|
||||
|
||||
// Create another hold invoice for Bob which expects a payment of 1k
|
||||
// satoshis from Alice.
|
||||
const dustAmt = 1000
|
||||
req = &invoicesrpc.AddHoldInvoiceRequest{
|
||||
Value: dustAmt,
|
||||
Hash: dustPayHash[:],
|
||||
// Use a small CLTV value so we can mine fewer blocks.
|
||||
CltvExpiry: finalCltvDelta,
|
||||
}
|
||||
dustInvoice := bob.RPC.AddHoldInvoice(req)
|
||||
|
||||
// Construct a route to send the non-dust payment.
|
||||
go func() {
|
||||
// Query the route to send the payment.
|
||||
routesReq := &lnrpc.QueryRoutesRequest{
|
||||
PubKey: bob.PubKeyStr,
|
||||
Amt: paymentAmt,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
}
|
||||
routes := alice.RPC.QueryRoutes(routesReq)
|
||||
require.Len(ht, routes.Routes, 1)
|
||||
|
||||
route := routes.Routes[0]
|
||||
require.Len(ht, route.Hops, 1)
|
||||
|
||||
// Modify the hop to include MPP info.
|
||||
route.Hops[0].MppRecord = &lnrpc.MPPRecord{
|
||||
PaymentAddr: invoice.PaymentAddr,
|
||||
TotalAmtMsat: int64(
|
||||
lnwire.NewMSatFromSatoshis(paymentAmt),
|
||||
),
|
||||
}
|
||||
|
||||
// Send the payment with the modified value.
|
||||
req := &routerrpc.SendToRouteRequest{
|
||||
PaymentHash: payHash[:],
|
||||
Route: route,
|
||||
}
|
||||
|
||||
// Send the payment and expect no error.
|
||||
attempt := alice.RPC.SendToRouteV2(req)
|
||||
require.Equal(ht, lnrpc.HTLCAttempt_FAILED, attempt.Status)
|
||||
}()
|
||||
|
||||
// Check that the payment is in-flight.
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
||||
|
||||
// Construct a route to send the dust payment.
|
||||
go func() {
|
||||
// Query the route to send the payment.
|
||||
routesReq := &lnrpc.QueryRoutesRequest{
|
||||
PubKey: bob.PubKeyStr,
|
||||
Amt: dustAmt,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
}
|
||||
routes := alice.RPC.QueryRoutes(routesReq)
|
||||
require.Len(ht, routes.Routes, 1)
|
||||
|
||||
route := routes.Routes[0]
|
||||
require.Len(ht, route.Hops, 1)
|
||||
|
||||
// Modify the hop to include MPP info.
|
||||
route.Hops[0].MppRecord = &lnrpc.MPPRecord{
|
||||
PaymentAddr: dustInvoice.PaymentAddr,
|
||||
TotalAmtMsat: int64(
|
||||
lnwire.NewMSatFromSatoshis(dustAmt),
|
||||
),
|
||||
}
|
||||
|
||||
// Send the payment with the modified value.
|
||||
req := &routerrpc.SendToRouteRequest{
|
||||
PaymentHash: dustPayHash[:],
|
||||
Route: route,
|
||||
}
|
||||
|
||||
// Send the payment and expect no error.
|
||||
attempt := alice.RPC.SendToRouteV2(req)
|
||||
require.Equal(ht, lnrpc.HTLCAttempt_FAILED, attempt.Status)
|
||||
}()
|
||||
|
||||
// Check that the dust payment is in-flight.
|
||||
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_IN_FLIGHT)
|
||||
|
||||
// Bob should have two incoming HTLC.
|
||||
ht.AssertIncomingHTLCActive(bob, chanPoint, payHash[:])
|
||||
ht.AssertIncomingHTLCActive(bob, chanPoint, dustPayHash[:])
|
||||
|
||||
// Alice should have two outgoing HTLCs.
|
||||
ht.AssertOutgoingHTLCActive(alice, chanPoint, payHash[:])
|
||||
ht.AssertOutgoingHTLCActive(alice, chanPoint, dustPayHash[:])
|
||||
|
||||
// Let Bob go offline.
|
||||
ht.Shutdown(bob)
|
||||
|
||||
// We'll now mine enough blocks to trigger Alice to broadcast her
|
||||
// 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(req.CltvExpiry - lncfg.DefaultOutgoingBroadcastDelta),
|
||||
)
|
||||
ht.MineEmptyBlocks(int(numBlocks - 1))
|
||||
|
||||
// Restart Alice if requested.
|
||||
if restartAlice {
|
||||
// Restart Alice to test the resumed payment is canceled.
|
||||
ht.RestartNode(alice)
|
||||
}
|
||||
|
||||
// We now subscribe to the payment status.
|
||||
payStream := alice.RPC.TrackPaymentV2(payHash[:])
|
||||
dustPayStream := alice.RPC.TrackPaymentV2(dustPayHash[:])
|
||||
|
||||
// Mine a block to confirm Alice's closing transaction.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Now the channel is closed, we expect different behaviors based on
|
||||
// whether the HTLC is a dust. For dust payment, it should be failed
|
||||
// now as the HTLC won't go onchain. For non-dust payment, it should
|
||||
// still be inflight. It won't be marked as failed unless the outgoing
|
||||
// HTLC is resolved onchain.
|
||||
//
|
||||
// Check that the dust payment is failed in both the stream and DB.
|
||||
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_FAILED)
|
||||
ht.AssertPaymentStatusFromStream(dustPayStream, lnrpc.Payment_FAILED)
|
||||
|
||||
// Check that the non-dust payment is still in-flight.
|
||||
//
|
||||
// NOTE: we don't check the payment status from the stream here as
|
||||
// there's no new status being sent.
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
||||
|
||||
// We now have two possible cases for the non-dust payment:
|
||||
// - Bob stays offline, and Alice will sweep her outgoing HTLC, which
|
||||
// makes the payment failed.
|
||||
// - Bob comes back online, and claims the HTLC on Alice's commitment
|
||||
// via direct preimage spend, hence racing against Alice onchain. If
|
||||
// he succeeds, Alice should mark the payment as succeeded.
|
||||
//
|
||||
// TODO(yy): test the second case once we have the RPC to clean
|
||||
// mempool.
|
||||
|
||||
// Since Alice's force close transaction has been confirmed, she should
|
||||
// sweep her outgoing HTLC in next block.
|
||||
ht.MineBlocksAndAssertNumTxes(2, 1)
|
||||
|
||||
// We expect the non-dust payment to marked as failed in Alice's
|
||||
// database and also from her stream.
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_FAILED)
|
||||
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user