itest: enhance testEstimateRouteFee with multi-LSP scenarios
This commit enhances the integration test to validate the LSP heuristic end-to-end with real network topology and payment probing. Network topology additions: - Added Frank node as a private destination - Created multi-LSP test scenario with Bob, Eve, and Dave as LSPs New test cases: 1. "probe based estimate, public target with public hop hints" - Validates Rule 1: public invoice target routes directly - Even with public hop hints, direct routing is used - Expected: standard single-hop fees 2. "probe based estimate, multiple different public LSPs" - Validates multi-LSP worst-case selection - Frank has routes through Bob (low fee), Eve (HIGH fee), Dave (medium) - Expected: Eve's worst-case fees (most expensive) - Tests griefing protection (max 3 LSP probes)
This commit is contained in:
1 file changed
+104
-3
@@ -59,18 +59,38 @@ type estimateRouteFeeTestCase struct {
|
||||
}
|
||||
|
||||
// testEstimateRouteFee tests the estimation of routing fees using either graph
|
||||
// data or sending out a probe payment.
|
||||
// data or sending out a probe payment. This test validates graph-based fee
|
||||
// estimation, probe-based fee estimation with single LSP, probe-based fee
|
||||
// estimation with multiple route hints to same LSP (worst-case fee selection),
|
||||
// probe-based fee estimation with multiple different public LSPs (worst-case
|
||||
// fee selection across LSPs, up to MaxLspsToProbe), and non-LSP probing (all
|
||||
// private destination hops).
|
||||
//
|
||||
// Note: We test with exactly MaxLspsToProbe (3) LSPs. Testing with more LSPs
|
||||
// is not feasible because the LSP selection uses map iteration, which has
|
||||
// non-deterministic order in Go, making it impossible to predict which LSPs
|
||||
// will be probed.
|
||||
func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
// Ensure MaxLspsToProbe is set to 3 as expected by this test. The test
|
||||
// uses exactly 3 LSPs in the multi-LSP test case. If MaxLspsToProbe
|
||||
// changes, this assertion will fail as a reminder to update the test.
|
||||
require.Equal(ht, 3, routerrpc.MaxLspsToProbe,
|
||||
"MaxLspsToProbe should be 3")
|
||||
|
||||
mts := newMppTestScenario(ht)
|
||||
|
||||
// We extend the regular mpp test scenario with a new node Paula. Paula
|
||||
// is connected to Bob and Eve through private channels.
|
||||
// We extend the regular mpp test scenario with two new nodes:
|
||||
// - Paula: connected to Bob and Eve through private channels
|
||||
// - Frank: connected to Dave through a private channel
|
||||
//
|
||||
// /-------------\
|
||||
// _ Eve _ (private) \
|
||||
// / \ \
|
||||
// Alice -- Carol ---- Bob --------- Paula
|
||||
// \ / (private)
|
||||
// \__ Dave ____/
|
||||
// \
|
||||
// \__ Frank (private)
|
||||
//
|
||||
req := &mppOpenChannelRequest{
|
||||
amtAliceCarol: 200_000,
|
||||
@@ -88,6 +108,7 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
probeInitiator = mts.alice
|
||||
|
||||
paula := ht.NewNode("Paula", nil)
|
||||
frank := ht.NewNode("Frank", nil)
|
||||
|
||||
// The channel from Bob to Paula actually doesn't have enough liquidity
|
||||
// to carry out the probe. We assume in normal operation that hop hints
|
||||
@@ -106,6 +127,13 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
Amt: 1_000_000,
|
||||
})
|
||||
|
||||
// Frank is a private node connected to Dave (public LSP).
|
||||
ht.EnsureConnected(mts.dave, frank)
|
||||
ht.OpenChannel(mts.dave, frank, lntest.OpenChannelParams{
|
||||
Private: true,
|
||||
Amt: 1_000_000,
|
||||
})
|
||||
|
||||
bobsPrivChannels := mts.bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{
|
||||
PrivateOnly: true,
|
||||
})
|
||||
@@ -118,6 +146,14 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
require.Len(ht, evesPrivChannels.Channels, 1)
|
||||
evePaulaChanID := evesPrivChannels.Channels[0].ChanId
|
||||
|
||||
davesPrivChannels := mts.dave.RPC.ListChannels(
|
||||
&lnrpc.ListChannelsRequest{
|
||||
PrivateOnly: true,
|
||||
},
|
||||
)
|
||||
require.Len(ht, davesPrivChannels.Channels, 1)
|
||||
daveFrankChanID := davesPrivChannels.Channels[0].ChanId
|
||||
|
||||
// Let's disable the paths from Alice to Bob through Dave and Eve with
|
||||
// high fees. This ensures that the path estimates are based on Carol's
|
||||
// channel to Bob for the first set of tests.
|
||||
@@ -196,6 +232,33 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
daveHopHint = &lnrpc.HopHint{
|
||||
NodeId: mts.dave.PubKeyStr,
|
||||
FeeBaseMsat: 3_000,
|
||||
FeeProportionalMillionths: 3_000,
|
||||
CltvExpiryDelta: 120,
|
||||
ChanId: daveFrankChanID,
|
||||
}
|
||||
|
||||
// Multiple different public LSPs (Bob, Eve, Dave).
|
||||
multipleLspsRouteHints = []*lnrpc.RouteHint{
|
||||
{
|
||||
HopHints: []*lnrpc.HopHint{
|
||||
bobHopHint,
|
||||
},
|
||||
},
|
||||
{
|
||||
HopHints: []*lnrpc.HopHint{
|
||||
eveHopHint,
|
||||
},
|
||||
},
|
||||
{
|
||||
HopHints: []*lnrpc.HopHint{
|
||||
daveHopHint,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
defaultTimelock := int64(chainreg.DefaultBitcoinTimeLockDelta)
|
||||
@@ -231,6 +294,14 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
feeACEP := feeEP + feeCE
|
||||
deltaACEP := deltaCE + deltaEP
|
||||
|
||||
// For multiple LSPs test, the route with the highest fee should be
|
||||
// selected (Eve). Note that we return both fee and CLTV delta from
|
||||
// the same route (the highest-fee route), not the max fee and max
|
||||
// delta independently. This ensures the returned values represent an
|
||||
// actual viable route.
|
||||
highestFeeRouteFee := feeACEP
|
||||
highestFeeRouteDelta := deltaACEP
|
||||
|
||||
initialBlockHeight := int64(mts.alice.RPC.GetInfo().BlockHeight)
|
||||
|
||||
// Locktime is always composed of the initial block height and the
|
||||
@@ -271,6 +342,19 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
expectedCltvDelta: locktime + deltaCB,
|
||||
expectedFailureReason: failureReasonNone,
|
||||
},
|
||||
// Rule 1: Invoice target is public (Bob), even with public
|
||||
// destination hop hints. Should route directly to Bob, NOT
|
||||
// treat as LSP.
|
||||
{
|
||||
name: "probe based estimate, public " +
|
||||
"target with public hop hints",
|
||||
probing: true,
|
||||
destination: mts.bob,
|
||||
routeHints: singleRouteHint,
|
||||
expectedRoutingFeesMsat: feeStandardSingleHop,
|
||||
expectedCltvDelta: locktime + deltaCB,
|
||||
expectedFailureReason: failureReasonNone,
|
||||
},
|
||||
// We expect the previous probing results adjusted by Paula's
|
||||
// hop data.
|
||||
{
|
||||
@@ -340,6 +424,23 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
expectedCltvDelta: 0,
|
||||
expectedFailureReason: failureReasonNoRoute,
|
||||
},
|
||||
// Test multiple different public LSPs. The worst-case (most
|
||||
// expensive) route should be returned. Eve has the highest
|
||||
// fees among the 3 LSPs tested. Note: We don't test with more
|
||||
// than MaxLspsToProbe LSPs because map iteration order in Go
|
||||
// is non-deterministic, making it impossible to predict which
|
||||
// LSPs will be selected for probing.
|
||||
{
|
||||
name: "probe based estimate, " +
|
||||
"multiple different public LSPs",
|
||||
probing: true,
|
||||
destination: frank,
|
||||
routeHints: multipleLspsRouteHints,
|
||||
expectedRoutingFeesMsat: highestFeeRouteFee,
|
||||
expectedCltvDelta: locktime +
|
||||
highestFeeRouteDelta,
|
||||
expectedFailureReason: failureReasonNone,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
Reference in new issue
Block a user