Skip to content

Commit 83ae595

Browse files
committed
Add dedicated probing builder
Change cursor of top nodes from HighDegreeStrategy to use cac: Create src/util.rs Add probe HTLC maximal lower bound Fix styling (config argument order), explicit Arc::clone instead of .clone() Change tests open_channel to reuse existing code
1 parent 200de80 commit 83ae595

File tree

8 files changed

+398
-330
lines changed

8 files changed

+398
-330
lines changed

src/builder.rs

Lines changed: 41 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::default::Default;
1111
use std::path::PathBuf;
1212
use std::sync::atomic::AtomicU64;
1313
use std::sync::{Arc, Mutex, Once, RwLock};
14-
use std::time::{Duration, SystemTime};
14+
use std::time::SystemTime;
1515
use std::{fmt, fs};
1616

1717
use bdk_wallet::template::Bip84;
@@ -48,8 +48,7 @@ use crate::config::{
4848
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
4949
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig,
5050
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
51-
DEFAULT_MAX_PROBE_AMOUNT_MSAT, DEFAULT_MAX_PROBE_LOCKED_MSAT, DEFAULT_PROBING_INTERVAL_SECS,
52-
MIN_PROBE_AMOUNT_MSAT,
51+
DEFAULT_MAX_PROBE_AMOUNT_MSAT, MIN_PROBE_AMOUNT_MSAT,
5352
};
5453
use crate::connection::ConnectionManager;
5554
use crate::entropy::NodeEntropy;
@@ -155,38 +154,6 @@ impl std::fmt::Debug for LogWriterConfig {
155154
}
156155
}
157156

158-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
159-
enum ProbingStrategyKind {
160-
HighDegree { top_n: usize },
161-
Random { max_hops: usize },
162-
Custom(Arc<dyn probing::ProbingStrategy>),
163-
}
164-
165-
struct ProbingStrategyConfig {
166-
kind: ProbingStrategyKind,
167-
interval: Duration,
168-
max_locked_msat: u64,
169-
}
170-
171-
impl fmt::Debug for ProbingStrategyConfig {
172-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173-
let kind_str = match &self.kind {
174-
ProbingStrategyKind::HighDegree { top_n } => {
175-
format!("HighDegree {{ top_n: {} }}", top_n)
176-
},
177-
ProbingStrategyKind::Random { max_hops } => {
178-
format!("Random {{ max_hops: {} }}", max_hops)
179-
},
180-
ProbingStrategyKind::Custom(_) => "Custom(<probing strategy>)".to_string(),
181-
};
182-
f.debug_struct("ProbingStrategyConfig")
183-
.field("kind", &kind_str)
184-
.field("interval", &self.interval)
185-
.field("max_locked_msat", &self.max_locked_msat)
186-
.finish()
187-
}
188-
}
189-
190157
/// An error encountered during building a [`Node`].
191158
///
192159
/// [`Node`]: crate::Node
@@ -317,8 +284,7 @@ pub struct NodeBuilder {
317284
runtime_handle: Option<tokio::runtime::Handle>,
318285
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
319286
recovery_mode: bool,
320-
probing_strategy: Option<ProbingStrategyConfig>,
321-
probing_diversity_penalty_msat: Option<u64>,
287+
probing_config: Option<probing::ProbingConfig>,
322288
}
323289

324290
impl NodeBuilder {
@@ -338,8 +304,7 @@ impl NodeBuilder {
338304
let pathfinding_scores_sync_config = None;
339305
let recovery_mode = false;
340306
let async_payments_role = None;
341-
let probing_strategy = None;
342-
let probing_diversity_penalty_msat = None;
307+
let probing_config = None;
343308
Self {
344309
config,
345310
chain_data_source_config,
@@ -350,8 +315,7 @@ impl NodeBuilder {
350315
async_payments_role,
351316
pathfinding_scores_sync_config,
352317
recovery_mode,
353-
probing_strategy,
354-
probing_diversity_penalty_msat,
318+
probing_config,
355319
}
356320
}
357321

@@ -657,87 +621,23 @@ impl NodeBuilder {
657621
self
658622
}
659623

660-
/// Configures background probing toward the highest-degree nodes in the network graph.
661-
///
662-
/// `top_n` controls how many of the most-connected nodes are cycled through.
663-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
664-
pub fn set_high_degree_probing_strategy(&mut self, top_n: usize) -> &mut Self {
665-
let kind = ProbingStrategyKind::HighDegree { top_n };
666-
self.probing_strategy = Some(self.make_probing_config(kind));
667-
self
668-
}
669-
670-
/// Configures background probing via random graph walks of up to `max_hops` hops.
671-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
672-
pub fn set_random_probing_strategy(&mut self, max_hops: usize) -> &mut Self {
673-
let kind = ProbingStrategyKind::Random { max_hops };
674-
self.probing_strategy = Some(self.make_probing_config(kind));
675-
self
676-
}
677-
678-
/// Configures a custom probing strategy for background channel probing.
679-
///
680-
/// When set, the node will periodically call [`probing::ProbingStrategy::next_probe`] and dispatch the
681-
/// returned probe via the channel manager.
682-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
683-
pub fn set_custom_probing_strategy(
684-
&mut self, strategy: Arc<dyn probing::ProbingStrategy>,
685-
) -> &mut Self {
686-
let kind = ProbingStrategyKind::Custom(strategy);
687-
self.probing_strategy = Some(self.make_probing_config(kind));
688-
self
689-
}
690-
691-
/// Overrides the interval between probe attempts. Only has effect if a probing strategy is set.
692-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
693-
pub fn set_probing_interval(&mut self, interval: Duration) -> &mut Self {
694-
if let Some(cfg) = &mut self.probing_strategy {
695-
cfg.interval = interval;
696-
}
697-
self
698-
}
699-
700-
/// Overrides the maximum millisatoshis that may be locked in in-flight probes at any time.
701-
/// Only has effect if a probing strategy is set.
702-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
703-
pub fn set_max_probe_locked_msat(&mut self, max_msat: u64) -> &mut Self {
704-
if let Some(cfg) = &mut self.probing_strategy {
705-
cfg.max_locked_msat = max_msat;
706-
}
707-
self
708-
}
709-
710-
/// Sets the probing diversity penalty applied by the probabilistic scorer.
624+
/// Configures background probing.
711625
///
712-
/// When set, the scorer will penalize channels that have been recently probed,
713-
/// encouraging path diversity during background probing. The penalty decays
714-
/// quadratically over 24 hours.
626+
/// Use [`probing::ProbingConfig`] to build the configuration:
627+
/// ```ignore
628+
/// use ldk_node::probing::ProbingConfig;
715629
///
716-
/// This is only useful for probing strategies that route through the scorer
717-
/// (e.g., [`probing::HighDegreeStrategy`]). Strategies that build paths manually
718-
/// (e.g., [`probing::RandomStrategy`]) bypass the scorer entirely.
719-
///
720-
/// If unset, LDK's default of `0` (no penalty) is used.
721-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
722-
pub fn set_probing_diversity_penalty_msat(&mut self, penalty_msat: u64) -> &mut Self {
723-
self.probing_diversity_penalty_msat = Some(penalty_msat);
630+
/// builder.set_probing_config(
631+
/// ProbingConfig::high_degree(100)
632+
/// .interval(Duration::from_secs(30))
633+
/// .build()
634+
/// );
635+
/// ```
636+
pub fn set_probing_config(&mut self, config: probing::ProbingConfig) -> &mut Self {
637+
self.probing_config = Some(config);
724638
self
725639
}
726640

727-
#[cfg_attr(feature = "uniffi", allow(dead_code))]
728-
fn make_probing_config(&self, kind: ProbingStrategyKind) -> ProbingStrategyConfig {
729-
let existing = self.probing_strategy.as_ref();
730-
ProbingStrategyConfig {
731-
kind,
732-
interval: existing
733-
.map(|c| c.interval)
734-
.unwrap_or(Duration::from_secs(DEFAULT_PROBING_INTERVAL_SECS)),
735-
max_locked_msat: existing
736-
.map(|c| c.max_locked_msat)
737-
.unwrap_or(DEFAULT_MAX_PROBE_LOCKED_MSAT),
738-
}
739-
}
740-
741641
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
742642
/// previously configured.
743643
pub fn build(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
@@ -909,14 +809,13 @@ impl NodeBuilder {
909809
self.gossip_source_config.as_ref(),
910810
self.liquidity_source_config.as_ref(),
911811
self.pathfinding_scores_sync_config.as_ref(),
812+
self.probing_config.as_ref(),
912813
self.async_payments_role,
913814
self.recovery_mode,
914815
seed_bytes,
915816
runtime,
916817
logger,
917818
Arc::new(DynStoreWrapper(kv_store)),
918-
self.probing_strategy.as_ref(),
919-
self.probing_diversity_penalty_msat,
920819
)
921820
}
922821
}
@@ -1207,34 +1106,11 @@ impl ArcedNodeBuilder {
12071106
self.inner.write().unwrap().set_wallet_recovery_mode();
12081107
}
12091108

1210-
/// Configures background probing toward the highest-degree nodes in the network graph.
1211-
pub fn set_high_degree_probing_strategy(&self, top_n: usize) {
1212-
self.inner.write().unwrap().set_high_degree_probing_strategy(top_n);
1213-
}
1214-
1215-
/// Configures background probing via random graph walks of up to `max_hops` hops.
1216-
pub fn set_random_probing_strategy(&self, max_hops: usize) {
1217-
self.inner.write().unwrap().set_random_probing_strategy(max_hops);
1218-
}
1219-
1220-
/// Configures a custom probing strategy for background channel probing.
1221-
pub fn set_custom_probing_strategy(&self, strategy: Arc<dyn probing::ProbingStrategy>) {
1222-
self.inner.write().unwrap().set_custom_probing_strategy(strategy);
1223-
}
1224-
1225-
/// Overrides the interval between probe attempts.
1226-
pub fn set_probing_interval(&self, interval: Duration) {
1227-
self.inner.write().unwrap().set_probing_interval(interval);
1228-
}
1229-
1230-
/// Overrides the maximum millisatoshis that may be locked in in-flight probes at any time.
1231-
pub fn set_max_probe_locked_msat(&self, max_msat: u64) {
1232-
self.inner.write().unwrap().set_max_probe_locked_msat(max_msat);
1233-
}
1234-
1235-
/// Sets the probing diversity penalty applied by the probabilistic scorer.
1236-
pub fn set_probing_diversity_penalty_msat(&self, penalty_msat: u64) {
1237-
self.inner.write().unwrap().set_probing_diversity_penalty_msat(penalty_msat);
1109+
/// Configures background probing.
1110+
///
1111+
/// See [`probing::ProbingConfig`] for details.
1112+
pub fn set_probing_config(&self, config: probing::ProbingConfig) {
1113+
self.inner.write().unwrap().set_probing_config(config);
12381114
}
12391115

12401116
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
@@ -1380,9 +1256,9 @@ fn build_with_store_internal(
13801256
gossip_source_config: Option<&GossipSourceConfig>,
13811257
liquidity_source_config: Option<&LiquiditySourceConfig>,
13821258
pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>,
1259+
probing_config: Option<&probing::ProbingConfig>,
13831260
async_payments_role: Option<AsyncPaymentsRole>, recovery_mode: bool, seed_bytes: [u8; 64],
13841261
runtime: Arc<Runtime>, logger: Arc<Logger>, kv_store: Arc<DynStore>,
1385-
probing_config: Option<&ProbingStrategyConfig>, probing_diversity_penalty_msat: Option<u64>,
13861262
) -> Result<Node, BuildError> {
13871263
optionally_install_rustls_cryptoprovider();
13881264

@@ -1784,7 +1660,7 @@ fn build_with_store_internal(
17841660
}
17851661

17861662
let mut scoring_fee_params = ProbabilisticScoringFeeParameters::default();
1787-
if let Some(penalty) = probing_diversity_penalty_msat {
1663+
if let Some(penalty) = probing_config.and_then(|c| c.diversity_penalty_msat) {
17881664
scoring_fee_params.probing_diversity_penalty_msat = penalty;
17891665
}
17901666
let router = Arc::new(DefaultRouter::new(
@@ -2127,26 +2003,29 @@ fn build_with_store_internal(
21272003

21282004
let prober = probing_config.map(|probing_cfg| {
21292005
let strategy: Arc<dyn probing::ProbingStrategy> = match &probing_cfg.kind {
2130-
ProbingStrategyKind::HighDegree { top_n } => {
2006+
probing::ProbingStrategyKind::HighDegree { top_node_count } => {
21312007
Arc::new(probing::HighDegreeStrategy::new(
2132-
network_graph.clone(),
2133-
*top_n,
2008+
Arc::clone(&network_graph),
2009+
*top_node_count,
2010+
MIN_PROBE_AMOUNT_MSAT,
2011+
DEFAULT_MAX_PROBE_AMOUNT_MSAT,
2012+
probing_cfg.cooldown,
2013+
))
2014+
},
2015+
probing::ProbingStrategyKind::Random { max_hops } => {
2016+
Arc::new(probing::RandomStrategy::new(
2017+
Arc::clone(&network_graph),
2018+
Arc::clone(&channel_manager),
2019+
*max_hops,
21342020
MIN_PROBE_AMOUNT_MSAT,
21352021
DEFAULT_MAX_PROBE_AMOUNT_MSAT,
21362022
))
21372023
},
2138-
ProbingStrategyKind::Random { max_hops } => Arc::new(probing::RandomStrategy::new(
2139-
network_graph.clone(),
2140-
channel_manager.clone(),
2141-
*max_hops,
2142-
MIN_PROBE_AMOUNT_MSAT,
2143-
DEFAULT_MAX_PROBE_AMOUNT_MSAT,
2144-
)),
2145-
ProbingStrategyKind::Custom(s) => s.clone(),
2024+
probing::ProbingStrategyKind::Custom(s) => Arc::clone(s),
21462025
};
21472026
Arc::new(probing::Prober {
2148-
channel_manager: channel_manager.clone(),
2149-
logger: logger.clone(),
2027+
channel_manager: Arc::clone(&channel_manager),
2028+
logger: Arc::clone(&logger),
21502029
strategy,
21512030
interval: probing_cfg.interval,
21522031
liquidity_limit_multiplier: Some(config.probing_liquidity_limit_multiplier),

src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30;
2828
const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10;
2929
const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3;
3030
pub(crate) const DEFAULT_PROBING_INTERVAL_SECS: u64 = 10;
31+
pub(crate) const DEFAULT_PROBED_NODE_COOLDOWN_SECS: u64 = 60 * 60; // 1 hour
3132
pub(crate) const DEFAULT_MAX_PROBE_LOCKED_MSAT: u64 = 100_000_000; // 100k sats
3233
pub(crate) const MIN_PROBE_AMOUNT_MSAT: u64 = 1_000_000; // 1k sats
3334
pub(crate) const DEFAULT_MAX_PROBE_AMOUNT_MSAT: u64 = 10_000_000; // 10k sats

src/event.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use core::future::Future;
99
use core::task::{Poll, Waker};
1010
use std::collections::VecDeque;
1111
use std::ops::Deref;
12-
use std::sync::atomic::{AtomicU64, Ordering};
1312
use std::sync::{Arc, Mutex};
1413

1514
use bitcoin::blockdata::locktime::absolute::LockTime;
@@ -53,6 +52,7 @@ use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
5352
use crate::payment::store::{
5453
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
5554
};
55+
use crate::probing::Prober;
5656
use crate::runtime::Runtime;
5757
use crate::types::{
5858
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet,
@@ -516,7 +516,7 @@ where
516516
static_invoice_store: Option<StaticInvoiceStore>,
517517
onion_messenger: Arc<OnionMessenger>,
518518
om_mailbox: Option<Arc<OnionMessageMailbox>>,
519-
probe_locked_msat: Option<Arc<AtomicU64>>,
519+
prober: Option<Arc<Prober>>,
520520
}
521521

522522
impl<L: Deref + Clone + Sync + Send + 'static> EventHandler<L>
@@ -532,8 +532,7 @@ where
532532
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
533533
keys_manager: Arc<KeysManager>, static_invoice_store: Option<StaticInvoiceStore>,
534534
onion_messenger: Arc<OnionMessenger>, om_mailbox: Option<Arc<OnionMessageMailbox>>,
535-
runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
536-
probe_locked_msat: Option<Arc<AtomicU64>>,
535+
runtime: Arc<Runtime>, logger: L, config: Arc<Config>, prober: Option<Arc<Prober>>,
537536
) -> Self {
538537
Self {
539538
event_queue,
@@ -553,7 +552,7 @@ where
553552
static_invoice_store,
554553
onion_messenger,
555554
om_mailbox,
556-
probe_locked_msat,
555+
prober,
557556
}
558557
}
559558

@@ -1140,19 +1139,13 @@ where
11401139
LdkEvent::PaymentPathSuccessful { .. } => {},
11411140
LdkEvent::PaymentPathFailed { .. } => {},
11421141
LdkEvent::ProbeSuccessful { path, .. } => {
1143-
if let Some(counter) = &self.probe_locked_msat {
1144-
let amount: u64 = path.hops.iter().map(|h| h.fee_msat).sum();
1145-
let _ = counter.fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
1146-
Some(v.saturating_sub(amount))
1147-
});
1142+
if let Some(prober) = &self.prober {
1143+
prober.handle_probe_successful(&path);
11481144
}
11491145
},
11501146
LdkEvent::ProbeFailed { path, .. } => {
1151-
if let Some(counter) = &self.probe_locked_msat {
1152-
let amount: u64 = path.hops.iter().map(|h| h.fee_msat).sum();
1153-
let _ = counter.fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
1154-
Some(v.saturating_sub(amount))
1155-
});
1147+
if let Some(prober) = &self.prober {
1148+
prober.handle_probe_failed(&path);
11561149
}
11571150
},
11581151
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {

0 commit comments

Comments
 (0)