Skip to content

routerrpc: isolate LSP route fee probes#10771

Open
yyforyongyu wants to merge 2 commits intolightningnetwork:masterfrom
yyforyongyu:fix-lsp-probe-hash
Open

routerrpc: isolate LSP route fee probes#10771
yyforyongyu wants to merge 2 commits intolightningnetwork:masterfrom
yyforyongyu:fix-lsp-probe-hash

Conversation

@yyforyongyu
Copy link
Copy Markdown
Member

@yyforyongyu yyforyongyu commented Apr 27, 2026

Summary

  • Use a fresh payment hash for each EstimateRouteFee LSP probe so later probes cannot reuse payment-level state from earlier probes.
  • Add deterministic unit coverage that stubs probe dispatch and verifies each LSP probe has a distinct payment hash and the expected final CLTV delta.
  • Add a release note for the multi-LSP EstimateRouteFee fix.

Fix #10677

Bug

CI flaked in PR #10689 on the bitcoind-etcd itest job:

https://github.com/lightningnetwork/lnd/actions/runs/24841352445/job/72716012767?pr=10689

Failed test:

TestLightningNetworkDaemon/tranche03/141-of-347/bitcoind/estimate_route_fee/probe_based_estimate,_multiple_different_public_LSPs

Failure:

Error:       Not equal:
             expected: 944
             actual  : 844
Messages:    cltv delta

The test creates three public LSP hints for a private destination:

  • Bob: high-level expected final LSP CLTV delta 100
  • Eve: highest-fee LSP, final LSP CLTV delta 200
  • Dave: lower-fee LSP, final LSP CLTV delta 120

The returned fee was Eve's fee, but the returned timelock was calculated with Bob's CLTV delta.

Root Cause

probePaymentRequest generated one random payment hash for the overall invoice probe request, then reused that same hash for every per-LSP probe.

The payment lifecycle keys payment state by payment hash. If Bob is probed first, the payment record is initialized with Bob's FinalCltvDelta=100. When Eve is probed later with the same payment hash, the router treats it as another attempt for the same payment and can reuse payment-level state from the first probe. The route can then carry Eve's fee but Bob's CLTV delta.

This is order-dependent because LSPs are iterated from a Go map. If Eve is probed first, the test passes. If Bob is probed first, Eve can inherit Bob's CLTV delta and the test fails.

Log Evidence

I downloaded the itest artifact logs-itest-bitcoind-etcd.zip from the failed run. Relevant log file:

itest/.logs-tranche3/22-estimate_route_fee-Alice-0223edc2.log

Passing two-LSP case: Eve is probed first, uses expiry/cltv 864, and the final result returns timelock: 944.

1884: 2026-04-23 14:53:05.208 [INF] RRPC router_server.go:615: Probing LSP with destination: 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880
1891: 2026-04-23 14:53:05.216 [DBG] CRTR payment_lifecycle.go:734: Attempt 6 for payment 2f2c715fe2abb8fed05b39cb5ce57e06ccb694655a3191928fc6a11dfd051db2 successfully sent to switch, route: 630020162846720 (101001201 mSAT) -> 630020162912256 (101000100 mSAT), cltv 864
1895: 2026-04-23 14:53:05.216 [DBG] PEER brontide.go:2809: Peer(03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d): Sending UpdateAddHTLC(chan_id=075f4a0a9a0e5bdfc7371304ba2c5a22342aea5c900110a4e3481ab69c704c62, id=5, amt=101001201 mSAT, expiry=864, hash=2f2c715fe2abb8fed05b39cb5ce57e06ccb694655a3191928fc6a11dfd051db2, blinding_point=, custom_records=map[106823:[0]]) to 03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d@127.0.0.1:11145
1920: 2026-04-23 14:53:05.575 [INF] RRPC router_server.go:677: Probe to LSP 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880 succeeded with fee: 1001201 msat
1965: 2026-04-23 14:53:05.991 [INF] RRPC router_server.go:711: Returning worst-case route via LSP 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880 with fee: 1001201 msat, timelock: 944

Failing three-LSP case: Bob is probed first with payment hash a539... and cltv 764. Eve is probed next with the same payment hash, logs Resuming HTLC attempt, still uses cltv 764, succeeds with Eve's fee, and returns timelock: 844.

1992: 2026-04-23 14:53:06.034 [INF] RRPC router_server.go:615: Probing LSP with destination: 031fea722979dc497b8f5cf6e3c240bfadcee7c1084636f6e2941676935ece12a9
1999: 2026-04-23 14:53:06.043 [DBG] CRTR payment_lifecycle.go:734: Attempt 8 for payment a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743 successfully sent to switch, route: 630020162846720 (100002200 mSAT) -> 630020162977792 (100001100 mSAT), cltv 764
2003: 2026-04-23 14:53:06.043 [DBG] PEER brontide.go:2809: Peer(03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d): Sending UpdateAddHTLC(chan_id=075f4a0a9a0e5bdfc7371304ba2c5a22342aea5c900110a4e3481ab69c704c62, id=7, amt=100002200 mSAT, expiry=764, hash=a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743, blinding_point=, custom_records=map[106823:[0]]) to 03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d@127.0.0.1:11145
2028: 2026-04-23 14:53:06.428 [INF] RRPC router_server.go:677: Probe to LSP 031fea722979dc497b8f5cf6e3c240bfadcee7c1084636f6e2941676935ece12a9 succeeded with fee: 2200 msat
2029: 2026-04-23 14:53:06.428 [INF] RRPC router_server.go:615: Probing LSP with destination: 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880
2031: 2026-04-23 14:53:06.432 [DBG] CRTR payment_lifecycle.go:1180: Payment a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743: status=Initiated, active_shards=0, rem_value=101000100 mSAT, fee_limit=100000000000 mSAT
2037: 2026-04-23 14:53:06.439 [INF] CRTR payment_lifecycle.go:1153: Resuming HTLC attempt 9 for payment a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743
2040: 2026-04-23 14:53:06.441 [DBG] CRTR payment_lifecycle.go:734: Attempt 9 for payment a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743 successfully sent to switch, route: 630020162846720 (101001201 mSAT) -> 630020162977792 (101000100 mSAT), cltv 764
2043: 2026-04-23 14:53:06.441 [DBG] PEER brontide.go:2809: Peer(03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d): Sending UpdateAddHTLC(chan_id=075f4a0a9a0e5bdfc7371304ba2c5a22342aea5c900110a4e3481ab69c704c62, id=8, amt=101001201 mSAT, expiry=764, hash=a5397f710a5edc3686412161ac38c38779b1f9ad4c62c15648feb883f6ea7743, blinding_point=, custom_records=map[106823:[0]]) to 03b85df66a282b9bcc9a636fb7a7948f2b5377072d55cfc8e1b15ba18851bc920d@127.0.0.1:11145
2076: 2026-04-23 14:53:06.816 [INF] RRPC router_server.go:677: Probe to LSP 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880 succeeded with fee: 1001201 msat
2124: 2026-04-23 14:53:07.067 [INF] RRPC router_server.go:711: Returning worst-case route via LSP 024b44330ad86737ecaa6f8a8bf1b10b7ab0d14d94631934bc02e9d3d4716d8880 with fee: 1001201 msat, timelock: 844

The 100 delta between expected 944 and actual 844 matches Bob's LSP CLTV delta being used where Eve's 200 should have been used.

Fix

Generate a fresh random payment hash for each LSP probe iteration. This makes each LSP probe an independent payment from the payment lifecycle's perspective, while keeping the existing single-hash behavior for the non-LSP probe path.

Testing

  • GOWORK=off go test ./lnrpc/routerrpc

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in the LSP route fee estimation process where multiple probes were incorrectly reusing payment-level state. By ensuring each probe utilizes a unique payment hash, the system now correctly isolates individual LSP probes. The changes also include architectural improvements to facilitate better test coverage for the probe dispatch mechanism.

Highlights

  • Payment Hash Isolation: Updated the LSP route fee probing logic to generate a unique payment hash for each probe, preventing state leakage between consecutive probes.
  • Testability Improvements: Introduced a test seam by refactoring the probe dispatch logic, allowing for deterministic verification of probe requests.
  • Regression Testing: Added a new unit test to verify that multiple LSP probes correctly use distinct payment hashes and maintain expected CLTV deltas.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the severity-high Requires knowledgeable engineer review label Apr 27, 2026
@yyforyongyu yyforyongyu marked this pull request as ready for review April 27, 2026 01:00
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an issue in EstimateRouteFee where multiple LSP probes were reusing the same payment hash and CLTV delta. The changes introduce a unique payment hash for each LSP probe and refactor probePaymentRequest to allow for better testability via a stubbed sender. A new unit test, TestProbePaymentRequestUsesUniqueHashPerLSP, has been added to verify this behavior. One piece of feedback was provided regarding an error message that incorrectly refers to a 'preimage' instead of a 'payment hash'.

Comment thread lnrpc/routerrpc/router_server.go
@github-actions github-actions Bot added severity-high Requires knowledgeable engineer review and removed severity-high Requires knowledgeable engineer review labels Apr 27, 2026
@github-actions
Copy link
Copy Markdown

🟠 PR Severity: HIGH

Automated classification | 2 files (excl. tests) | 39 lines changed (excl. tests)

🟠 High (1 file)
  • lnrpc/routerrpc/router_server.go - RPC/API server implementation under lnrpc/*
🟢 Low (1 file)
  • docs/release-notes/release-notes-0.21.0.md - release notes documentation

Analysis

The highest-severity file is lnrpc/routerrpc/router_server.go, which lives under lnrpc/* — the RPC/API definitions package, classified as HIGH. The accompanying test file (router_server_test.go) and the release-notes markdown are excluded from severity bump calculations.

With only 2 non-test, non-generated files changed and 39 lines of non-test/generated additions+deletions, none of the bump thresholds (>20 files, >500 lines, multiple critical packages) are triggered. Severity remains HIGH.

This PR touches the router RPC server, which handles payment routing API calls. A knowledgeable engineer familiar with the routerrpc layer and Lightning payment flows should review it.


To override, add a severity-override-{critical,high,medium,low} label.
<!-- pr-severity-bot -->

@saubyk saubyk assigned saubyk and yyforyongyu and unassigned saubyk Apr 27, 2026
@saubyk saubyk added this to lnd v0.22 Apr 27, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in lnd v0.22 Apr 27, 2026
@saubyk saubyk moved this from Backlog to In progress in lnd v0.22 Apr 27, 2026
@ziggie1984
Copy link
Copy Markdown
Collaborator

should be not needed if #10772 lands

@yyforyongyu
Copy link
Copy Markdown
Member Author

should be not needed if #10772 lands

I don't think so - why do we use the same hash for different probes?

@ziggie1984
Copy link
Copy Markdown
Collaborator

its a probe where the hash is not settleable anyways so why not reuse the dummy hash ?

@yyforyongyu
Copy link
Copy Markdown
Member Author

its a probe where the hash is not settleable anyways so why not reuse the dummy hash ?

But this doesn't mean the same hash is better than a separate hash? I also don't understand why we wanna use the same hash in the first place - do we wanna make it easy for other nodes to link the behaviors here? and it makes the logs/attempt tracking more difficult here if they just show the same hash? unless the caller intended retries of the exact same logical probe, i don't think we can benefit from using the same hash.

@ziggie1984 ziggie1984 self-requested a review April 30, 2026 17:02
Copy link
Copy Markdown
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@saubyk saubyk moved this from In progress to In review in lnd v0.22 May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-high Requires knowledgeable engineer review

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

estimate_route_fee failed with unexpected CLTV delta

3 participants