Skip to content

Add probing service#815

Open
randomlogin wants to merge 10 commits intolightningdevkit:mainfrom
randomlogin:add-probing-service
Open

Add probing service#815
randomlogin wants to merge 10 commits intolightningdevkit:mainfrom
randomlogin:add-probing-service

Conversation

@randomlogin
Copy link
Copy Markdown
Contributor

@randomlogin randomlogin commented Mar 1, 2026

Added a probing service which is used to send probes to estimate channels' capacities.

Related issue: #765.

Probing is intended to be used in two ways:

  • on a 'normal' node that allocates some liquidity to probe channels;
  • on a probing node which doesn't make any real payments, just observes the network.


For probing a new abstraction Prober is defined and is (optionally) created during node building.
Prober periodically sends probes to feed the data to the scorer.
Prober sends probes using a ProbingStrategy.

ProbingStrategy trait has only one method: fn next_probe(&self) -> Option<Probe>; every tick it generates a probe, where Probe represents how to send a probe.

To accommodate two different ways the probing is used, we either construct a probing route manually (Probe::PrebuiltRoute) or rely on the router/scorer (Probe::Destination).

Prober tracks how much liquidity is locked in-flight in probes, prevents the new probes from firing if the cap is reached.

There are two probing strategies implemented:

  • Random probing strategy, it picks a random route from the current node, the route is probed via send_probe, thus ignores scoring parameters (what hops to pick), it also ignores liquidity_limit_multiplier which prohibits taking a hop if its capacity is too small. It is a true random route.

  • High degree probing strategy, it examines the graph and finds the nodes with the biggest number of (public) channels and probes routes to them using send_spontaneous_preflight_probes which uses the current router/scorer.

The former is meant to be used on payment nodes, while the latter on probing nodes. For the HighDegreeStrategy to work it is recommended to set probing_diversity_penalty_msat to some nonzero value to prevent routes reuse, however it may fail to find any available routes.

There are three tests added:

  • check the probing locked amount increases/decreases
  • check that the new probes are not fired if the current locked amount cap is reached
  • performance testing which sets up a network of nodes and 4 observing nodes

Example output (runs for ~1 minute, needs --nocapture flag):

SCID            Direction         Probing Random               Probing HiDeg                Probing HiDeg+P              Probing nostrat              Real outbound msat
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
114349209354241 C→Random          [0, 90 100 235]              unknown                      unknown                      unknown                      0
114349209354241 Random→C          [9 899 765, 100 000 000]     unknown                      unknown                      unknown                      917 184 187
114349209419777 D→HiDeg+P         unknown                      unknown                      [0, 91 127 725]              unknown                      0
114349209419777 HiDeg+P→D         unknown                      unknown                      [8 872 275, 100 000 000]     unknown                      905 313 697
114349209485312 B→F               [0, 3 418 855]               unknown                      unknown                      unknown                      160 288 291
114349209485312 F→B               [96 581 145, 100 000 000]    unknown                      unknown                      unknown                      819 711 709
114349209550848 B→D               [0, 6 141 420]               [0, 2 988 399]               [0, 91 127 725]              unknown                      30 052 300
114349209550848 D→B               [93 858 580, 100 000 000]    [97 011 601, 100 000 000]    [8 872 275, 100 000 000]     unknown                      874 555 512
114349209616384 E→HiDeg           [0, 2 097 950]               [0, 90 455 967]              unknown                      unknown                      0
114349209616384 HiDeg→E           [97 902 050, 100 000 000]    [9 544 033, 100 000 000]     unknown                      unknown                      913 366 836
114349209681920 B→C               [98 411 698, 100 000 000]    unknown                      unknown                      unknown                      86 511 108
114349209681920 C→B               [0, 1 588 302]               unknown                      unknown                      unknown                      812 521 355
114349209747457 B→E               [0, 6 625 595]               [0, 90 455 967]              [0, 4 795 311]               unknown                      0
114349209747457 E→B               [93 374 405, 100 000 000]    [9 544 033, 100 000 000]     [95 204 689, 100 000 000]    unknown                      907 339 924
114349209812993 C→nostrat         [0, 1 113 591]               unknown                      unknown                      unknown                      0
114349209812993 nostrat→C         [98 886 409, 100 000 000]    unknown                      unknown                      unknown                      990 000 000
114349209878528 C→E               [2 097 950, 5 335 740]       [0, 90 808 929]              unknown                      unknown                      55 569 306
114349209878528 E→C               [94 664 260, 97 902 050]     [9 191 071, 100 000 000]     unknown                      unknown                      878 423 826
114349209944065 B→C               [97 243 524, 100 000 000]    unknown                      [1 698 550, 6 086 572]       unknown                      938 012 264
114349209944065 C→B               [0, 2 756 476]               unknown                      [93 913 428, 98 301 450]     unknown                      34 367 637
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Known directions                   18/20                        8/20                         8/20                         0/20                        

For performance testing I had to expose the scoring data (scorer_channel_liquidity).
Also exposed scoring_fee_params: ProbabilisticScoringFeeParameters to Config.

TODOs:

  • adjust default parameters
  • improve HighDegree strategy to take into account channel capacities
  • improve HighDegree strategy to cache results
  • improve performance test to better estimate real channel capacity

@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Mar 1, 2026

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@randomlogin randomlogin marked this pull request as ready for review March 1, 2026 12:10
@tnull tnull self-requested a review March 2, 2026 07:56
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 3rd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 4th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 5th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Copy Markdown
Contributor

@enigbe enigbe left a comment

Choose a reason for hiding this comment

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

Hi @randomlogin, thanks for the work on this! I've reviewed the first two commits:

  1. 7f3ce11: "Create probing service" and
  2. 6574bf9: "Add probing tests"

I've left a bunch of inline comments addressing configuration and public API, commit hygiene, testing infrastructure, and test flakiness.

In summary:

  1. A couple of items are exposed publicly that seem like they should be scoped to probing or gated for tests only (see scoring_fee_params in Config and scorer_channel_liquidity on Node).
  2. The probing tests duplicate existing test helpers (setup_node, MockLogFacadeLogger). Reusing and extending what's already in tests/common/ would reduce duplication and keep the test file focused on the tests themselves.
  3. test_probe_budget_blocks_when_node_offline has a race condition where the prober dispatches probes before the baseline capacity is measured, causing the assertion between the baseline and stuck capacities to fail. Details in the inline comment.
  4. A few nits about commit hygiene, import structure, and suggestions for renaming stuff.

Also needs to be rebased.

Comment thread src/builder.rs Outdated
Comment thread src/probing.rs Outdated
Comment thread src/probing.rs Outdated
Comment thread src/probing.rs Outdated
Comment thread src/builder.rs Outdated
Comment thread tests/probing_tests.rs Outdated
Comment thread tests/probing_tests.rs Outdated
Comment thread tests/probing_tests.rs Outdated
Comment thread tests/probing_tests.rs Outdated
Comment thread tests/probing_tests.rs Outdated
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@randomlogin
Copy link
Copy Markdown
Contributor Author

@enigbe, thanks for a review, the updates are incoming soon.

@randomlogin randomlogin force-pushed the add-probing-service branch 3 times, most recently from 436e4a3 to 07dfde4 Compare March 19, 2026 01:24
@randomlogin randomlogin requested a review from enigbe March 19, 2026 02:10
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 8th Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 9th Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 10th Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 3rd Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@randomlogin randomlogin force-pushed the add-probing-service branch from ff741c2 to c31f1ce Compare March 26, 2026 01:27
Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thanks for taking this on and excuse the delay here!

Did a first review pass and this already looks great! Here are some relatively minor comments, mostly concerning the API design.

Comment thread src/builder.rs Outdated
Comment thread src/builder.rs Outdated
Comment thread src/builder.rs Outdated
Comment thread src/builder.rs Outdated
Comment thread src/builder.rs
Comment thread src/probing.rs Outdated
Comment thread src/probing.rs
Comment thread src/probing.rs
Comment thread tests/common/mod.rs Outdated
Comment thread tests/probing_tests.rs Outdated
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 4th Reminder

Hey @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@randomlogin randomlogin requested a review from tnull March 28, 2026 15:19
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 5th Reminder

Hey @tnull @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Seems tests are failing right now:

thread 'exhausted_probe_budget_blocks_new_probes' (167312) panicked at tests/probing_tests.rs:381:5:
no probe dispatched within 15 s


failures:
    exhausted_probe_budget_blocks_new_probes
    probe_budget_increments_and_decrements

@randomlogin randomlogin force-pushed the add-probing-service branch from f99786b to 1e73e6e Compare March 31, 2026 12:51
@randomlogin randomlogin force-pushed the add-probing-service branch 3 times, most recently from fe64bd6 to b11cea0 Compare April 29, 2026 16:13
Add integration tests that verify the probing service fires probes on
the configured interval and respects the locked-msat budget cap.
Shared helpers in tests/common are extended with probing-aware setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@randomlogin randomlogin force-pushed the add-probing-service branch from b11cea0 to 1a8f945 Compare April 29, 2026 21:13
Changed the exhaust test to be statistical (locked amount never exceeds
the cap) instead of trying to turn intermediary routing node offline
when it hasn't yet forwarded the probe htlc.
@randomlogin randomlogin force-pushed the add-probing-service branch from 346e002 to 2431f88 Compare April 30, 2026 12:00
@randomlogin
Copy link
Copy Markdown
Contributor Author

Removed usage of unwrap(), reworked the exhaust test not to rely on a node stopping race condition.

@randomlogin randomlogin requested a review from tnull April 30, 2026 12:32
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 19th Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 5th Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 20th Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Excuse the delay here. Looks pretty good, but there are some minor things we might want to address before this can land.

Comment thread src/builder.rs

/// Configures background probing.
///
/// Use [`ProbingConfigBuilder`] to build the configuration:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should probably add another paragraph before this example that gives some context on what background probing is and why users would want to enable it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added documentation on the module level as well as expanded/corrected docs for particular objects (builder).

Comment thread src/lib.rs Outdated
use std::{any::Any, sync::Weak};

#[cfg(feature = "uniffi")]
use crate::probing::ProbingConfig;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: We do these uniffi-specific re-exports via use statements in ffi/types.rs.

Copy link
Copy Markdown
Contributor Author

@randomlogin randomlogin May 5, 2026

Choose a reason for hiding this comment

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

Moved to ffi/types.rs

Comment thread src/lib.rs Outdated
};
use peer_store::{PeerInfo, PeerStore};
#[cfg(feature = "uniffi")]
pub use probing::ArcedProbingConfigBuilder as ProbingConfigBuilder;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here, please expose this in ffi/types.rs

Copy link
Copy Markdown
Contributor Author

@randomlogin randomlogin May 5, 2026

Choose a reason for hiding this comment

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

Moved to ffi/types.rs

Comment thread src/probing.rs
}
}

/// Configuration for the background probing subsystem.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See above: given this is the main probing-related object it might be worth spending a paragraph here (or on the module docs) to explain what probing is and why users would want to enable it in the first place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added documentation on the module level as well as expanded/corrected docs for particular objects (builder).

Comment thread src/event.rs Outdated
LdkEvent::ProbeFailed { .. } => {},
LdkEvent::ProbeSuccessful { path, .. } => {
if let Some(prober) = &self.prober {
prober.handle_probe_successful(&path);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we handle all probes the same here to reduce the locked amount, we probably also need to mark any amounts that we send manually via preflight probes as locked, as otherwise the accounting is off? Or, maybe it would be preferable to keep track of which paths were for background probing and only reduced the locked amount for them?

Copy link
Copy Markdown
Contributor Author

@randomlogin randomlogin May 6, 2026

Choose a reason for hiding this comment

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

Addressed this by adding inflight_probes hashmap to track our probes.
Changed event handling to fire background probing events only on these (background) probes.
Code may be DRYed (as event handlers share parts of code), but I find the current version more clear.

Comment thread src/probing.rs Outdated
short_channel_id: via_scid,
channel_features,
fee_msat: amount_msat,
cltv_expiry_delta: 0,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Codex:

  • High: RandomStrategy builds invalid final-hop CLTVs. src/probing.rs:584 sets the last hop’s cltv_expiry_delta to 0, but LDK expects the last hop delta to be the destination final CLTV. For multi-hop random probes, the penultimate node can reject the forward as outgoing CLTV too soon, so
    the strategy mostly trains failures instead of probing liquidity. Use a real final CLTV delta, e.g. DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA.

Seems valid?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Comment thread src/probing.rs Outdated

impl ProbingStrategy for RandomStrategy {
fn next_probe(&self) -> Option<Path> {
let target_hops = random_range(1, self.max_hops as u64) as usize;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Codex:

  • Medium: RandomStrategy can produce one-hop paths that send_probe rejects. src/probing.rs:636 samples target_hops starting at 1, and random_walk(1) will only ever emit single-hop paths, which LDK rejects with “No need probing a path with less than two hops.” Clamp/sample to at least two
    hops or return None for that config.

Seems also valid, probably makes sense to start at 2?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 21st Reminder

Hey @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 7th Reminder

Hey @kaloudis @enigbe! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@randomlogin
Copy link
Copy Markdown
Contributor Author

Excuse the delay here. Looks pretty good, but there are some minor things we might want to address before this can land.

Addressed all issues mentioned, they have their respective commits. If they are fine, I'm going to squash them.

@randomlogin randomlogin requested a review from tnull May 6, 2026 02:00
@randomlogin randomlogin force-pushed the add-probing-service branch from a956232 to f2706d2 Compare May 6, 2026 06:22
Previously when calculated currently locked amount, we didn't account
for preflight probes sent on a payment which could result in an
incorrect value of probe locked_msat.

Now Prober saves the PaymentId of probes it sent and tracks them on
release, ignoring the user-sent ones.
@randomlogin randomlogin force-pushed the add-probing-service branch from f2706d2 to 8acd55d Compare May 6, 2026 06:57
Comment thread src/builder.rs

/// Configures background probing.
///
/// See [`ProbingConfig`] for details.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please make sure the docs on the ArcedNodeBuilder mirror the ones on the regular NodeBuilder.

Comment thread src/event.rs
LdkEvent::ProbeFailed { .. } => {},
LdkEvent::ProbeSuccessful { path, payment_id, .. } => {
if let Some(prober) = &self.prober {
if let Some(amount) =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe just move this check into handle_background_probe_successful/handle_background_probe_failed? Then you could keep inflight_probes visibility at the module level, too.

Comment thread src/probing.rs
pub interval: Duration,
/// Maximum total millisatoshis that may be locked in in-flight probes at any time.
pub max_locked_msat: u64,
pub(crate) locked_msat: Arc<AtomicU64>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't this redudant to the sum over all inflight_probes now? Probably it's better to keep one state for accounting purposes, to avoid the possibility for them to get out-of-sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants