@@ -59,18 +59,27 @@ 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, max 3 LSPs probed), and non-LSP probing (all
67+ // private destination hops).
6368func testEstimateRouteFee (ht * lntest.HarnessTest ) {
6469 mts := newMppTestScenario (ht )
6570
66- // We extend the regular mpp test scenario with a new node Paula. Paula
67- // is connected to Bob and Eve through private channels.
71+ // We extend the regular mpp test scenario with two new nodes:
72+ // - Paula: connected to Bob and Eve through private channels
73+ // - Frank: connected to Dave through a private channel
74+ //
6875 // /-------------\
6976 // _ Eve _ (private) \
7077 // / \ \
7178 // Alice -- Carol ---- Bob --------- Paula
7279 // \ / (private)
7380 // \__ Dave ____/
81+ // \
82+ // \__ Frank (private)
7483 //
7584 req := & mppOpenChannelRequest {
7685 amtAliceCarol : 200_000 ,
@@ -88,6 +97,7 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
8897 probeInitiator = mts .alice
8998
9099 paula := ht .NewNode ("Paula" , nil )
100+ frank := ht .NewNode ("Frank" , nil )
91101
92102 // The channel from Bob to Paula actually doesn't have enough liquidity
93103 // to carry out the probe. We assume in normal operation that hop hints
@@ -106,6 +116,13 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
106116 Amt : 1_000_000 ,
107117 })
108118
119+ // Frank is a private node connected to Dave (public LSP).
120+ ht .EnsureConnected (mts .dave , frank )
121+ ht .OpenChannel (mts .dave , frank , lntest.OpenChannelParams {
122+ Private : true ,
123+ Amt : 1_000_000 ,
124+ })
125+
109126 bobsPrivChannels := mts .bob .RPC .ListChannels (& lnrpc.ListChannelsRequest {
110127 PrivateOnly : true ,
111128 })
@@ -118,6 +135,14 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
118135 require .Len (ht , evesPrivChannels .Channels , 1 )
119136 evePaulaChanID := evesPrivChannels .Channels [0 ].ChanId
120137
138+ davesPrivChannels := mts .dave .RPC .ListChannels (
139+ & lnrpc.ListChannelsRequest {
140+ PrivateOnly : true ,
141+ },
142+ )
143+ require .Len (ht , davesPrivChannels .Channels , 1 )
144+ daveFrankChanID := davesPrivChannels .Channels [0 ].ChanId
145+
121146 // Let's disable the paths from Alice to Bob through Dave and Eve with
122147 // high fees. This ensures that the path estimates are based on Carol's
123148 // channel to Bob for the first set of tests.
@@ -196,6 +221,33 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
196221 },
197222 },
198223 }
224+
225+ daveHopHint = & lnrpc.HopHint {
226+ NodeId : mts .dave .PubKeyStr ,
227+ FeeBaseMsat : 3_000 ,
228+ FeeProportionalMillionths : 3_000 ,
229+ CltvExpiryDelta : 120 ,
230+ ChanId : daveFrankChanID ,
231+ }
232+
233+ // Multiple different public LSPs (Bob, Eve, Dave).
234+ multipleLspsRouteHints = []* lnrpc.RouteHint {
235+ {
236+ HopHints : []* lnrpc.HopHint {
237+ bobHopHint ,
238+ },
239+ },
240+ {
241+ HopHints : []* lnrpc.HopHint {
242+ eveHopHint ,
243+ },
244+ },
245+ {
246+ HopHints : []* lnrpc.HopHint {
247+ daveHopHint ,
248+ },
249+ },
250+ }
199251 )
200252
201253 defaultTimelock := int64 (chainreg .DefaultBitcoinTimeLockDelta )
@@ -231,6 +283,11 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
231283 feeACEP := feeEP + feeCE
232284 deltaACEP := deltaCE + deltaEP
233285
286+ // For multiple LSPs test, the worst-case (most expensive) route should
287+ // be selected. Eve has the highest fees.
288+ worstCaseFeeMultipleLsps := feeACEP
289+ worstCaseDeltaMultipleLsps := deltaACEP
290+
234291 initialBlockHeight := int64 (mts .alice .RPC .GetInfo ().BlockHeight )
235292
236293 // Locktime is always composed of the initial block height and the
@@ -271,6 +328,19 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
271328 expectedCltvDelta : locktime + deltaCB ,
272329 expectedFailureReason : failureReasonNone ,
273330 },
331+ // Rule 1: Invoice target is public (Bob), even with public
332+ // destination hop hints. Should route directly to Bob, NOT
333+ // treat as LSP.
334+ {
335+ name : "probe based estimate, public " +
336+ "target with public hop hints" ,
337+ probing : true ,
338+ destination : mts .bob ,
339+ routeHints : singleRouteHint ,
340+ expectedRoutingFeesMsat : feeStandardSingleHop ,
341+ expectedCltvDelta : locktime + deltaCB ,
342+ expectedFailureReason : failureReasonNone ,
343+ },
274344 // We expect the previous probing results adjusted by Paula's
275345 // hop data.
276346 {
@@ -340,6 +410,20 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
340410 expectedCltvDelta : 0 ,
341411 expectedFailureReason : failureReasonNoRoute ,
342412 },
413+ // Test multiple different public LSPs. The worst-case (most
414+ // expensive) route should be returned. Eve has the highest
415+ // fees.
416+ {
417+ name : "probe based estimate, " +
418+ "multiple different public LSPs" ,
419+ probing : true ,
420+ destination : frank ,
421+ routeHints : multipleLspsRouteHints ,
422+ expectedRoutingFeesMsat : worstCaseFeeMultipleLsps ,
423+ expectedCltvDelta : locktime +
424+ worstCaseDeltaMultipleLsps ,
425+ expectedFailureReason : failureReasonNone ,
426+ },
343427 }
344428
345429 for _ , testCase := range testCases {
0 commit comments