Description
Consider this specific configuration:
- Alice -> Bob 200k and 300k sat channels
- Bob -> Charlie 600k sat private channel
- Send a MPP payment Alice -> Charlie of size 350k sats
If Charlie includes just one route hint, the maximum payment amount for which LDK routing succeeds is around ~295.5k sats., i.e. it's not possible to send a MPP payment which uses more than one of Alice's channels.
Here's the relevant logs from testing this:
2025-03-25T23:36:49.040772Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Searching for a route from payer 031e18d11d9fdbc175f2fd73cfab07346146069fcf1ecfbeef6e55632a3bda7fe5 to payee node id 02deb1b2c0f91b77107faae1ce96820fbcb82ec541780db7e0306f92396770c669 with MPP and 2 first hops overriding the network graph of 0 nodes and 0 channels with a fee limit of 18446744073709551615 msat module="lightning::routing::router" line=2234 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.040799Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: First hop through 035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b/114349209354241 can send between 1msat and 197460000msat (inclusive). module="lightning::routing::router" line=2244 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.040818Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: First hop through 035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b/120946279120897 can send between 1msat and 296460000msat (inclusive). module="lightning::routing::router" line=2244 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.040858Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Building path from payee node id 02deb1b2c0f91b77107faae1ce96820fbcb82ec541780db7e0306f92396770c669 to payer 031e18d11d9fdbc175f2fd73cfab07346146069fcf1ecfbeef6e55632a3bda7fe5 for value 350000000 msat. module="lightning::routing::router" line=2362 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.040951Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Starting main path collection loop with 2 nodes pre-filled from first/last hops. module="lightning::routing::router" line=3052 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.040977Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Found a path back to us from the target with 2 hops contributing up to 295410000 msat:
[
PathBuildingHop {
source_node_id: NodeId(031e18d11d9fdbc175f2fd73cfab07346146069fcf1ecfbeef6e55632a3bda7fe5),
target_node_id: Some(
NodeId(035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b),
),
short_channel_id: Some(
120946279120897,
),
is_first_hop_target: false,
total_fee_msat: 1050000,
next_hops_fee_msat: 1050000,
hop_use_fee_msat: 0,
total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat): 0,
path_penalty_msat: 0,
path_htlc_minimum_msat: 1,
cltv_expiry_delta: 0,
},
PathBuildingHop {
source_node_id: NodeId(035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b),
target_node_id: Some(
NodeId(02deb1b2c0f91b77107faae1ce96820fbcb82ec541780db7e0306f92396770c669),
),
short_channel_id: Some(
1099588108288,
),
is_first_hop_target: true,
total_fee_msat: 1050000,
next_hops_fee_msat: 0,
hop_use_fee_msat: 0,
total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat): 1050000,
path_penalty_msat: 0,
path_htlc_minimum_msat: 0,
cltv_expiry_delta: 72,
},
] module="lightning::routing::router" line=3131 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041024Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Disabling route candidate route hint with SCID 1099588108288 for future path building iterations to avoid duplicates. module="lightning::routing::router" line=3176 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041047Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Ignoring route hint with SCID 1099588108288 due to insufficient value contribution (channel max Infinite). module="lightning::routing::router" line=2946 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041065Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Starting main path collection loop with 0 nodes pre-filled from first/last hops. module="lightning::routing::router" line=3052 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041085Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Ignoring route hint with SCID 1099588108288 due to insufficient value contribution (channel max Infinite). module="lightning::routing::router" line=2946 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041102Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Starting main path collection loop with 0 nodes pre-filled from first/last hops. module="lightning::routing::router" line=3052 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041118Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Have now collected 295410000 msat (seeking 1050000000 msat) in paths. Last path loop did not find a new path. module="lightning::routing::router" line=3232 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
2025-03-25T23:36:49.041134Z TRACE (node):(app-node-run-server):(req)(srv):(preflight-pay-invoice): ldk: Ignored 2 candidate hops due to insufficient value contribution, 0 due to path length limit, 0 due to CLTV delta limit, 0 due to previous payment failure, 0 due to htlc_minimum_msat limit, 0 to avoid overpaying, 0 due to maximum total fee limit. Total: 2 ignored candidates. module="lightning::routing::router" line=3255 user_pk=bbf6c8a9 user_idx=0 trace_id=AH0oacZBCk6jhsFN from=app method=POST url=/app/preflight_pay_invoice version=HTTP/2.0
I tracked the issue down to this block in router.rs
:
if !prevented_redundant_path_selection {
// If we weren't capped by hitting a liquidity limit on a channel in the path,
// we'll probably end up picking the same path again on the next iteration.
// Decrease the available liquidity of a hop in the middle of the path.
let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate;
let exhausted = u64::max_value();
log_trace!(logger,
"Disabling route candidate {} for future path building iterations to avoid duplicates.",
LoggedCandidateHop(victim_candidate));
if let Some(scid) = victim_candidate.short_channel_id() {
*used_liquidities.entry(CandidateHopId::Clear((scid, false))).or_default() = exhausted;
*used_liquidities.entry(CandidateHopId::Clear((scid, true))).or_default() = exhausted;
}
}
If I disable this block, the payment succeeds. In this case, payment_path.hops.len()
is 2
, so we disable payment_path.hops[1]
, which happens to be the Bob -> Charlie hop.
If there is only one path from a sender to a recipient, it seems wrong to disable any hop within it, since it prevents other MPP shards from finding any route at all. I understand the motivation for varying our paths for MPPs, but it shouldn't cost at the cost of routing reliability.