Skip to content

Router disables central node within route even if there is only one path #3685

Closed
@MaxFangX

Description

@MaxFangX

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions