mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-04 20:19:24 +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",
|
Name: "payment succeeded htlc remote swept",
|
||||||
TestFunc: testPaymentSucceededHTLCRemoteSwept,
|
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/rpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1211,3 +1212,219 @@ func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
|||||||
// Cancel the context, which will disconnect the above interceptor.
|
// Cancel the context, which will disconnect the above interceptor.
|
||||||
cancelInterceptor()
|
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