@@ -59,18 +59,38 @@ type estimateRouteFeeTestCase struct {
5959}
6060
6161// testEstimateRouteFee tests the estimation of routing fees using either graph
62- // data or sending out a probe payment.
62+ // data or sending out a probe payment. This test validates graph-based fee
63+ // estimation, probe-based fee estimation with single LSP, probe-based fee
64+ // estimation with multiple route hints to same LSP (worst-case fee selection),
65+ // probe-based fee estimation with multiple different public LSPs (worst-case
66+ // fee selection across LSPs, up to MaxLspsToProbe), and non-LSP probing (all
67+ // private destination hops).
68+ //
69+ // Note: We test with exactly MaxLspsToProbe (3) LSPs. Testing with more LSPs
70+ // is not feasible because the LSP selection uses map iteration, which has
71+ // non-deterministic order in Go, making it impossible to predict which LSPs
72+ // will be probed.
6373func testEstimateRouteFee (ht * lntest.HarnessTest ) {
74+ // Ensure MaxLspsToProbe is set to 3 as expected by this test. The test
75+ // uses exactly 3 LSPs in the multi-LSP test case. If MaxLspsToProbe
76+ // changes, this assertion will fail as a reminder to update the test.
77+ require .Equal (ht , 3 , routerrpc .MaxLspsToProbe ,
78+ "MaxLspsToProbe should be 3" )
79+
6480 mts := newMppTestScenario (ht )
6581
66- // We extend the regular mpp test scenario with a new node Paula. Paula
67- // is connected to Bob and Eve through private channels.
82+ // We extend the regular mpp test scenario with two new nodes:
83+ // - Paula: connected to Bob and Eve through private channels
84+ // - Frank: connected to Dave through a private channel
85+ //
6886 // /-------------\
6987 // _ Eve _ (private) \
7088 // / \ \
7189 // Alice -- Carol ---- Bob --------- Paula
7290 // \ / (private)
7391 // \__ Dave ____/
92+ // \
93+ // \__ Frank (private)
7494 //
7595 req := & mppOpenChannelRequest {
7696 amtAliceCarol : 200_000 ,
@@ -88,6 +108,7 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
88108 probeInitiator = mts .alice
89109
90110 paula := ht .NewNode ("Paula" , nil )
111+ frank := ht .NewNode ("Frank" , nil )
91112
92113 // The channel from Bob to Paula actually doesn't have enough liquidity
93114 // to carry out the probe. We assume in normal operation that hop hints
@@ -106,6 +127,13 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
106127 Amt : 1_000_000 ,
107128 })
108129
130+ // Frank is a private node connected to Dave (public LSP).
131+ ht .EnsureConnected (mts .dave , frank )
132+ ht .OpenChannel (mts .dave , frank , lntest.OpenChannelParams {
133+ Private : true ,
134+ Amt : 1_000_000 ,
135+ })
136+
109137 bobsPrivChannels := mts .bob .RPC .ListChannels (& lnrpc.ListChannelsRequest {
110138 PrivateOnly : true ,
111139 })
@@ -118,6 +146,14 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
118146 require .Len (ht , evesPrivChannels .Channels , 1 )
119147 evePaulaChanID := evesPrivChannels .Channels [0 ].ChanId
120148
149+ davesPrivChannels := mts .dave .RPC .ListChannels (
150+ & lnrpc.ListChannelsRequest {
151+ PrivateOnly : true ,
152+ },
153+ )
154+ require .Len (ht , davesPrivChannels .Channels , 1 )
155+ daveFrankChanID := davesPrivChannels .Channels [0 ].ChanId
156+
121157 // Let's disable the paths from Alice to Bob through Dave and Eve with
122158 // high fees. This ensures that the path estimates are based on Carol's
123159 // channel to Bob for the first set of tests.
@@ -196,6 +232,33 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
196232 },
197233 },
198234 }
235+
236+ daveHopHint = & lnrpc.HopHint {
237+ NodeId : mts .dave .PubKeyStr ,
238+ FeeBaseMsat : 3_000 ,
239+ FeeProportionalMillionths : 3_000 ,
240+ CltvExpiryDelta : 120 ,
241+ ChanId : daveFrankChanID ,
242+ }
243+
244+ // Multiple different public LSPs (Bob, Eve, Dave).
245+ multipleLspsRouteHints = []* lnrpc.RouteHint {
246+ {
247+ HopHints : []* lnrpc.HopHint {
248+ bobHopHint ,
249+ },
250+ },
251+ {
252+ HopHints : []* lnrpc.HopHint {
253+ eveHopHint ,
254+ },
255+ },
256+ {
257+ HopHints : []* lnrpc.HopHint {
258+ daveHopHint ,
259+ },
260+ },
261+ }
199262 )
200263
201264 defaultTimelock := int64 (chainreg .DefaultBitcoinTimeLockDelta )
@@ -231,6 +294,14 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
231294 feeACEP := feeEP + feeCE
232295 deltaACEP := deltaCE + deltaEP
233296
297+ // For multiple LSPs test, the route with the highest fee should be
298+ // selected (Eve). Note that we return both fee and CLTV delta from
299+ // the same route (the highest-fee route), not the max fee and max
300+ // delta independently. This ensures the returned values represent an
301+ // actual viable route.
302+ highestFeeRouteFee := feeACEP
303+ highestFeeRouteDelta := deltaACEP
304+
234305 initialBlockHeight := int64 (mts .alice .RPC .GetInfo ().BlockHeight )
235306
236307 // Locktime is always composed of the initial block height and the
@@ -271,6 +342,19 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
271342 expectedCltvDelta : locktime + deltaCB ,
272343 expectedFailureReason : failureReasonNone ,
273344 },
345+ // Rule 1: Invoice target is public (Bob), even with public
346+ // destination hop hints. Should route directly to Bob, NOT
347+ // treat as LSP.
348+ {
349+ name : "probe based estimate, public " +
350+ "target with public hop hints" ,
351+ probing : true ,
352+ destination : mts .bob ,
353+ routeHints : singleRouteHint ,
354+ expectedRoutingFeesMsat : feeStandardSingleHop ,
355+ expectedCltvDelta : locktime + deltaCB ,
356+ expectedFailureReason : failureReasonNone ,
357+ },
274358 // We expect the previous probing results adjusted by Paula's
275359 // hop data.
276360 {
@@ -340,6 +424,23 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
340424 expectedCltvDelta : 0 ,
341425 expectedFailureReason : failureReasonNoRoute ,
342426 },
427+ // Test multiple different public LSPs. The worst-case (most
428+ // expensive) route should be returned. Eve has the highest
429+ // fees among the 3 LSPs tested. Note: We don't test with more
430+ // than MaxLspsToProbe LSPs because map iteration order in Go
431+ // is non-deterministic, making it impossible to predict which
432+ // LSPs will be selected for probing.
433+ {
434+ name : "probe based estimate, " +
435+ "multiple different public LSPs" ,
436+ probing : true ,
437+ destination : frank ,
438+ routeHints : multipleLspsRouteHints ,
439+ expectedRoutingFeesMsat : highestFeeRouteFee ,
440+ expectedCltvDelta : locktime +
441+ highestFeeRouteDelta ,
442+ expectedFailureReason : failureReasonNone ,
443+ },
343444 }
344445
345446 for _ , testCase := range testCases {
0 commit comments