From e0ab462a7452235189cdacf5f412240363198dee Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Fri, 22 Nov 2024 18:48:43 +0100 Subject: [PATCH] itest: extend liquidity edge cases for rfq htlc tracking --- itest/litd_custom_channels_test.go | 97 ++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index 86790ad82..535affa6a 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -1,6 +1,7 @@ package itest import ( + "bytes" "context" "fmt" "math" @@ -18,12 +19,14 @@ import ( "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightninglabs/taproot-assets/tapscript" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/port" @@ -1805,7 +1808,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, SatPerVByte: 5, }, ) - defer closeChannelAndAssert(t, net, dave, channelOp, false) + defer closeChannelAndAssert(t, net, dave, channelOp, true) // This is the only public channel, we need everyone to be aware of it. assertChannelKnown(t.t, charlie, channelOp) @@ -2020,10 +2023,11 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, logBalance(t.t, nodes, assetID, "after small payment (asset "+ "invoice, <354sats)") - // Edge case: Now Charlie creates an asset invoice to be paid for by + // Edge case: Now Dave creates an asset invoice to be paid for by // Yara with satoshi. For the last hop we try to settle the invoice in - // satoshi, where we will check whether Charlie's strict forwarding - // works as expected. + // satoshi, where we will check whether Dave's strict forwarding works + // as expected. Charlie is only used as a dummy RFQ peer in this case, + // Yara totally ignored the RFQ hint and pays agnostically with sats. invoiceResp = createAssetInvoice( t.t, charlie, dave, 1, assetID, ) @@ -2046,6 +2050,91 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, logBalance(t.t, nodes, assetID, "after failed payment (asset "+ "invoice, strict forwarding)") + + // Edge case: Charlie negotiates a quote with Dave which has a low max + // amount (~170k sats). Then Charlie creates an invoice with a total + // amount slightly larger than the max allowed in the quote (200k sats). + // Erin will try to pay that invoice with sats, in shards of max size + // 80k sats. Dave will eventually stop forwarding HTLCs as the RFQ HTLC + // tracking mechanism should stop them from being forwarded, as they + // violate the maximum allowed amount of the quote. + + // Charlie starts by negotiating the quote. + res, err := charlieTap.RfqClient.AddAssetBuyOrder( + ctxb, &rfqrpc.AddAssetBuyOrderRequest{ + AssetSpecifier: &rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_AssetId{ + AssetId: assetID, + }, + }, + AssetMaxAmt: 10_000, + Expiry: uint64(time.Now().Add(time.Hour).Unix()), + PeerPubKey: dave.PubKey[:], + TimeoutSeconds: 10, + }, + ) + require.NoError(t.t, err) + + quote, ok := res.Response.(*rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote) + require.True(t.t, ok) + + // We now manually add the invoice in order to inject the above, + // manually generated, quote. + iResp, err := charlie.AddInvoice(ctxb, &lnrpc.Invoice{ + Memo: "", + Value: 200_000, + RPreimage: bytes.Repeat([]byte{11}, 32), + CltvExpiry: 60, + RouteHints: []*lnrpc.RouteHint{ + &lnrpc.RouteHint{ + HopHints: []*lnrpc.HopHint{ + &lnrpc.HopHint{ + NodeId: dave.PubKeyStr, + ChanId: quote.AcceptedQuote.Scid, + }, + }, + }, + }, + }) + require.NoError(t.t, err) + + // Now Erin tries to pay the invoice. Since the multipart payment will + // have some of its shards failing the pathfinding logic will keep going + // and we won't see a payment failure but a timeout. If a final outcome + // is not produced within a reasonable amount of time, we assume the + // payment is still trying to find a route, therefore the HTLC rejection + // works. + timeoutChan = time.After(PaymentTimeout / 2) + done = make(chan bool, 1) + + ctxc, cancel := context.WithCancel(context.Background()) + + //nolint:lll + go func() { + // payInvoiceWithSatoshi(t.t, erin, iResp, lnrpc.Payment_FAILED) + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: iResp.PaymentRequest, + TimeoutSeconds: int32(PaymentTimeout.Seconds()), + MaxShardSizeMsat: 80_000_000, + FeeLimitMsat: 1_000_000, + } + stream, err := erin.RouterClient.SendPaymentV2(ctxc, sendReq) + if err == nil { + _, _ = getPaymentResult(stream) + } + + done <- true + }() + + select { + case <-done: + t.Fatalf("Payment should not produce a final outcome") + + case <-timeoutChan: + cancel() + } + + logBalance(t.t, nodes, assetID, "after htlc track") } // testCustomChannelsBalanceConsistency is a test that test the balance of nodes