Skip to content

Commit e52a8c5

Browse files
committed
itest: extend liquidity edge cases for rfq htlc tracking
1 parent e45a856 commit e52a8c5

File tree

2 files changed

+187
-20
lines changed

2 files changed

+187
-20
lines changed

itest/assets_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,9 @@ func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64,
714714

715715
result, err := getAssetPaymentResult(stream, false)
716716
require.NoError(t, err)
717+
if result.Status == lnrpc.Payment_FAILED {
718+
t.Logf("Failure reason: %v", result.FailureReason)
719+
}
717720
require.Equal(t, expectedStatus, result.Status)
718721

719722
expectedReason := failReason.UnwrapOr(

itest/litd_custom_channels_test.go

Lines changed: 184 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package itest
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"math"
@@ -18,12 +19,14 @@ import (
1819
"github.com/lightninglabs/taproot-assets/tapchannel"
1920
"github.com/lightninglabs/taproot-assets/taprpc"
2021
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
22+
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
2123
tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
2224
"github.com/lightninglabs/taproot-assets/taprpc/universerpc"
2325
"github.com/lightninglabs/taproot-assets/tapscript"
2426
"github.com/lightningnetwork/lnd/fn"
2527
"github.com/lightningnetwork/lnd/lnrpc"
2628
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
29+
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
2730
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
2831
"github.com/lightningnetwork/lnd/lntest"
2932
"github.com/lightningnetwork/lnd/lntest/port"
@@ -47,7 +50,7 @@ var (
4750

4851
shortTimeout = time.Second * 5
4952

50-
defaultPaymentStatus = fn.None[lnrpc.Payment_PaymentStatus]()
53+
defaultPaymentStatusOpt = fn.None[lnrpc.Payment_PaymentStatus]()
5154
)
5255

5356
var (
@@ -227,7 +230,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness,
227230
)
228231
payInvoiceWithAssets(
229232
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, false,
230-
defaultPaymentStatus,
233+
defaultPaymentStatusOpt,
231234
)
232235
logBalance(t.t, nodes, assetID, "after invoice")
233236

@@ -242,7 +245,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness,
242245

243246
payInvoiceWithAssets(
244247
t.t, fabia, erin, invoiceResp2.PaymentRequest, assetID, false,
245-
defaultPaymentStatus,
248+
defaultPaymentStatusOpt,
246249
)
247250
logBalance(t.t, nodes, assetID, "after invoice 2")
248251

@@ -253,7 +256,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness,
253256
)
254257
payInvoiceWithAssets(
255258
t.t, charlie, dave, invoiceResp3.PaymentRequest, assetID, false,
256-
defaultPaymentStatus,
259+
defaultPaymentStatusOpt,
257260
)
258261
logBalance(t.t, nodes, assetID, "after invoice 3")
259262

@@ -445,7 +448,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness,
445448
)
446449
payInvoiceWithAssets(
447450
t.t, dave, charlie, invoiceResp.PaymentRequest, assetID, true,
448-
defaultPaymentStatus,
451+
defaultPaymentStatusOpt,
449452
)
450453
logBalance(t.t, nodes, assetID, "after invoice back")
451454

@@ -510,7 +513,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness,
510513
)
511514
payInvoiceWithAssets(
512515
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
513-
defaultPaymentStatus,
516+
defaultPaymentStatusOpt,
514517
)
515518
logBalance(t.t, nodes, assetID, "after invoice")
516519

@@ -555,7 +558,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness,
555558
)
556559
payInvoiceWithAssets(
557560
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
558-
defaultPaymentStatus,
561+
defaultPaymentStatusOpt,
559562
)
560563
logBalance(t.t, nodes, assetID, "after invoice")
561564

@@ -592,7 +595,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness,
592595
)
593596
payInvoiceWithAssets(
594597
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
595-
defaultPaymentStatus,
598+
defaultPaymentStatusOpt,
596599
)
597600
logBalance(t.t, nodes, assetID, "after invoice")
598601

@@ -613,7 +616,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness,
613616
)
614617
payInvoiceWithAssets(
615618
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
616-
defaultPaymentStatus,
619+
defaultPaymentStatusOpt,
617620
)
618621
logBalance(t.t, nodes, assetID, "after asset-to-asset")
619622

@@ -949,7 +952,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness,
949952
)
950953
payInvoiceWithAssets(
951954
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
952-
defaultPaymentStatus,
955+
defaultPaymentStatusOpt,
953956
)
954957
logBalance(t.t, nodes, assetID, "after invoice")
955958

@@ -987,7 +990,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness,
987990
)
988991
payInvoiceWithAssets(
989992
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
990-
defaultPaymentStatus,
993+
defaultPaymentStatusOpt,
991994
)
992995
logBalance(t.t, nodes, assetID, "after invoice")
993996

@@ -1024,7 +1027,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness,
10241027
)
10251028
payInvoiceWithAssets(
10261029
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
1027-
defaultPaymentStatus,
1030+
defaultPaymentStatusOpt,
10281031
)
10291032
logBalance(t.t, nodes, assetID, "after invoice")
10301033

@@ -1045,7 +1048,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness,
10451048
)
10461049
payInvoiceWithAssets(
10471050
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, true,
1048-
defaultPaymentStatus,
1051+
defaultPaymentStatusOpt,
10491052
)
10501053
logBalance(t.t, nodes, assetID, "after asset-to-asset")
10511054

@@ -1805,7 +1808,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
18051808
SatPerVByte: 5,
18061809
},
18071810
)
1808-
defer closeChannelAndAssert(t, net, dave, channelOp, false)
1811+
defer closeChannelAndAssert(t, net, dave, channelOp, true)
18091812

18101813
// This is the only public channel, we need everyone to be aware of it.
18111814
assertChannelKnown(t.t, charlie, channelOp)
@@ -1958,7 +1961,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
19581961

19591962
payInvoiceWithAssets(
19601963
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, false,
1961-
defaultPaymentStatus,
1964+
defaultPaymentStatusOpt,
19621965
)
19631966

19641967
logBalance(t.t, nodes, assetID, "after big asset payment (asset "+
@@ -2004,7 +2007,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20042007

20052008
payInvoiceWithAssets(
20062009
t.t, yara, dave, invoiceResp.PaymentRequest, assetID, false,
2007-
defaultPaymentStatus,
2010+
defaultPaymentStatusOpt,
20082011
)
20092012

20102013
logBalance(t.t, nodes, assetID, "after big asset payment (asset "+
@@ -2020,10 +2023,11 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20202023
logBalance(t.t, nodes, assetID, "after small payment (asset "+
20212024
"invoice, <354sats)")
20222025

2023-
// Edge case: Now Charlie creates an asset invoice to be paid for by
2026+
// Edge case: Now Dave creates an asset invoice to be paid for by
20242027
// Yara with satoshi. For the last hop we try to settle the invoice in
2025-
// satoshi, where we will check whether Charlie's strict forwarding
2026-
// works as expected.
2028+
// satoshi, where we will check whether Dave's strict forwarding works
2029+
// as expected. Charlie is only used as a dummy RFQ peer in this case,
2030+
// Yara totally ignored the RFQ hint and pays agnostically with sats.
20272031
invoiceResp = createAssetInvoice(
20282032
t.t, charlie, dave, 1, assetID,
20292033
)
@@ -2046,6 +2050,166 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20462050

20472051
logBalance(t.t, nodes, assetID, "after failed payment (asset "+
20482052
"invoice, strict forwarding)")
2053+
2054+
// Edge case: Fabia gets all the asset liquidity on her side. Then
2055+
// generates an asset invoice to be paid for with assets by Charlie.
2056+
// Charlie will unleash multiple shards towards Fabia, exhausting the
2057+
// liquidity in the Erin<->Fabia channel. Dave should be able to detect
2058+
// those failures and account for those cancelled HTLCs in the RFQ
2059+
// tracking mechanism. Eventually we will slosh some funds from Fabia
2060+
// back to Erin in order for Erin to be able to forward the rest of the
2061+
// HTLCs, leading to the release of the preimage.
2062+
2063+
// Erin starts by sending half of the assets to Fabia's side. He also
2064+
// sends some raw sats to be used by Fabia later for the sloshing.
2065+
sendAssetKeySendPayment(
2066+
t.t, erin, fabia, 150_000, assetID,
2067+
fn.None[int64](), lnrpc.Payment_SUCCEEDED,
2068+
fn.None[lnrpc.PaymentFailureReason](),
2069+
)
2070+
sendKeySendPayment(t.t, erin, fabia, 20_000)
2071+
2072+
logBalance(t.t, nodes, assetID, "balance after 1st slosh")
2073+
2074+
// Fabia creates an asset invoice of 100k assets. There is currently
2075+
// not enough asset liquidity on Erin's side to forward HTLCs to satisfy
2076+
// this invoice.
2077+
invoiceResp = createAssetInvoice(t.t, erin, fabia, 100_000, assetID)
2078+
2079+
// We set the waiting period for the slosh payment to occur. This is set
2080+
// to half of the default payment timeout, as we want it to occur half
2081+
// way through the in-flight payment.
2082+
sloshWait := PaymentTimeout / 3
2083+
2084+
go func() {
2085+
// After a small delay (less than the payment timeout) Fabia
2086+
// sloshes back the asset liquidity to Erin. This should allow
2087+
// the payment by Charlie to eventually complete.
2088+
time.Sleep(sloshWait)
2089+
sendAssetKeySendPayment(
2090+
t.t, fabia, erin, 75_000, assetID,
2091+
fn.None[int64](), lnrpc.Payment_SUCCEEDED,
2092+
fn.None[lnrpc.PaymentFailureReason](),
2093+
)
2094+
}()
2095+
2096+
// To avoid goroutine uncertainty, we wait as much as the above routine
2097+
// minus a small delta. This is enough for us to be sure that the
2098+
// payment will complete due to the slosh.
2099+
timeoutChan = time.After(sloshWait - time.Millisecond*250)
2100+
done = make(chan bool, 1)
2101+
2102+
go func() {
2103+
// Now Charlie pays the invoice with assets. What happens on the
2104+
// Charlie-Dave channel doesn't matter. This payment will block
2105+
// until the previous slosh payment completes. That slosh will
2106+
// allow for the rest of the HTLCs to be forwarded.
2107+
payInvoiceWithAssets(
2108+
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID,
2109+
true, defaultPaymentStatusOpt,
2110+
)
2111+
2112+
done <- true
2113+
}()
2114+
2115+
select {
2116+
case <-done:
2117+
// If the payment completes before the slosh payment occurs then
2118+
// something went wrong, this is not the expected test case
2119+
// behavior.
2120+
t.Fatalf("payment completed before expected block period")
2121+
case <-timeoutChan:
2122+
// The expected time delay for the payment to complete has been
2123+
// passed, so now we wait for the payment to complete.
2124+
<-done
2125+
}
2126+
2127+
logBalance(t.t, nodes, assetID, "after htlc track cancel")
2128+
2129+
// Edge case: Charlie negotiates a quote with Dave which has a low max
2130+
// amount (~170k sats). Then Charlie creates an invoice with a total
2131+
// amount slightly larger than the max allowed in the quote (200k sats).
2132+
// Erin will try to pay that invoice with sats, in shards of max size
2133+
// 80k sats. Dave will eventually stop forwarding HTLCs as the RFQ HTLC
2134+
// tracking mechanism should stop them from being forwarded, as they
2135+
// violate the maximum allowed amount of the quote.
2136+
2137+
// Charlie starts by negotiating the quote.
2138+
res, err := charlieTap.RfqClient.AddAssetBuyOrder(
2139+
ctxb, &rfqrpc.AddAssetBuyOrderRequest{
2140+
AssetSpecifier: &rfqrpc.AssetSpecifier{
2141+
Id: &rfqrpc.AssetSpecifier_AssetId{
2142+
AssetId: assetID,
2143+
},
2144+
},
2145+
AssetMaxAmt: 10_000,
2146+
Expiry: uint64(time.Now().Add(time.Hour).Unix()),
2147+
PeerPubKey: dave.PubKey[:],
2148+
TimeoutSeconds: 10,
2149+
},
2150+
)
2151+
require.NoError(t.t, err)
2152+
2153+
quote, ok := res.Response.(*rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote)
2154+
require.True(t.t, ok)
2155+
2156+
// We now manually add the invoice in order to inject the above,
2157+
// manually generated, quote.
2158+
iResp, err := charlie.AddInvoice(ctxb, &lnrpc.Invoice{
2159+
Memo: "",
2160+
Value: 200_000,
2161+
RPreimage: bytes.Repeat([]byte{11}, 32),
2162+
CltvExpiry: 60,
2163+
RouteHints: []*lnrpc.RouteHint{
2164+
&lnrpc.RouteHint{
2165+
HopHints: []*lnrpc.HopHint{
2166+
&lnrpc.HopHint{
2167+
NodeId: dave.PubKeyStr,
2168+
ChanId: quote.AcceptedQuote.Scid,
2169+
},
2170+
},
2171+
},
2172+
},
2173+
})
2174+
require.NoError(t.t, err)
2175+
2176+
// Now Erin tries to pay the invoice. Since the multipart payment will
2177+
// have some of its shards failing the pathfinding logic will keep going
2178+
// and we won't see a payment failure but a timeout. If a final outcome
2179+
// is not produced within a reasonable amount of time, we assume the
2180+
// payment is still trying to find a route, therefore the HTLC rejection
2181+
// works.
2182+
timeoutChan = time.After(PaymentTimeout / 2)
2183+
done = make(chan bool, 1)
2184+
2185+
ctxc, cancel := context.WithCancel(context.Background())
2186+
2187+
//nolint:lll
2188+
go func() {
2189+
// payInvoiceWithSatoshi(t.t, erin, iResp, lnrpc.Payment_FAILED)
2190+
sendReq := &routerrpc.SendPaymentRequest{
2191+
PaymentRequest: iResp.PaymentRequest,
2192+
TimeoutSeconds: int32(PaymentTimeout.Seconds()),
2193+
MaxShardSizeMsat: 80_000_000,
2194+
FeeLimitMsat: 1_000_000,
2195+
}
2196+
stream, err := erin.RouterClient.SendPaymentV2(ctxc, sendReq)
2197+
if err == nil {
2198+
_, _ = getPaymentResult(stream)
2199+
}
2200+
2201+
done <- true
2202+
}()
2203+
2204+
select {
2205+
case <-done:
2206+
t.Fatalf("Payment should not produce a final outcome")
2207+
2208+
case <-timeoutChan:
2209+
cancel()
2210+
}
2211+
2212+
logBalance(t.t, nodes, assetID, "after small manual rfq")
20492213
}
20502214

20512215
// testCustomChannelsBalanceConsistency is a test that test the balance of nodes
@@ -2563,7 +2727,7 @@ func testCustomChannelsOraclePricing(_ context.Context,
25632727

25642728
numUnits, rate := payInvoiceWithAssets(
25652729
t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, false,
2566-
defaultPaymentStatus,
2730+
defaultPaymentStatusOpt,
25672731
)
25682732
logBalance(t.t, nodes, assetID, "after invoice")
25692733

0 commit comments

Comments
 (0)