Skip to content

Commit 6420f06

Browse files
Add ProbingService with pluggable ProbingStrategy
- Add optional ProbingService to run periodic probe payments for liquidity checks. - Configure probing behavior by providing a public `ProbingStrategy` trait implementation. - Targets and selection logic are fully customizable by the user via the trait (no fixed target count is passed by the node).
1 parent 1a134b4 commit 6420f06

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

src/builder.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
6666
use crate::message_handler::NodeCustomMessageHandler;
6767
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
6868
use crate::peer_store::PeerStore;
69+
use crate::probing::{ProbingService, ProbingStrategy};
6970
use crate::runtime::Runtime;
7071
use crate::tx_broadcaster::TransactionBroadcaster;
7172
use crate::types::{
@@ -129,6 +130,40 @@ struct LiquiditySourceConfig {
129130
lsps2_service: Option<LSPS2ServiceConfig>,
130131
}
131132

133+
#[derive(Clone, Debug)]
134+
struct ProbingServiceConfig {
135+
/// Time in seconds between consecutive probing attempts.
136+
probing_interval_secs: u64,
137+
138+
/// Amount in milli-satoshis used for each probe.
139+
probing_amount_msat: u64,
140+
141+
/// Configuration for the probing strategy as a shareable trait-object.
142+
strategy: ProbingStrategyConfig,
143+
}
144+
145+
pub enum ProbingStrategyConfig {
146+
Custom { strategy: Arc<dyn ProbingStrategy + Send + Sync> },
147+
}
148+
149+
impl fmt::Debug for ProbingStrategyConfig {
150+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151+
match self {
152+
Self::Custom { .. } => {
153+
f.debug_struct("Custom").field("strategy", &"<ProbingStrategy>").finish()
154+
},
155+
}
156+
}
157+
}
158+
159+
impl Clone for ProbingStrategyConfig {
160+
fn clone(&self) -> Self {
161+
match self {
162+
Self::Custom { strategy } => Self::Custom { strategy: Arc::clone(strategy) },
163+
}
164+
}
165+
}
166+
132167
#[derive(Clone)]
133168
enum LogWriterConfig {
134169
File { log_file_path: Option<String>, max_log_level: Option<LogLevel> },
@@ -253,6 +288,7 @@ pub struct NodeBuilder {
253288
async_payments_role: Option<AsyncPaymentsRole>,
254289
runtime_handle: Option<tokio::runtime::Handle>,
255290
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
291+
probing_service_config: Option<ProbingServiceConfig>,
256292
}
257293

258294
impl NodeBuilder {
@@ -271,6 +307,7 @@ impl NodeBuilder {
271307
let log_writer_config = None;
272308
let runtime_handle = None;
273309
let pathfinding_scores_sync_config = None;
310+
let probing_service_config = None;
274311
Self {
275312
config,
276313
entropy_source_config,
@@ -281,6 +318,7 @@ impl NodeBuilder {
281318
runtime_handle,
282319
async_payments_role: None,
283320
pathfinding_scores_sync_config,
321+
probing_service_config,
284322
}
285323
}
286324

@@ -488,6 +526,28 @@ impl NodeBuilder {
488526
self
489527
}
490528

529+
/// Configures the probing service with a custom target selection strategy.
530+
///
531+
/// This allows full control over how probing targets are selected by providing
532+
/// a custom implementation of the [`ProbingStrategy`] trait.
533+
///
534+
/// # Parameters
535+
/// * `probing_interval_secs` - Seconds between probing cycles
536+
/// * `probing_amount_msat` - Amount in milli-satoshis per probe
537+
/// * `strategy` - Custom [`ProbingStrategy`] implementation
538+
///
539+
/// [`ProbingStrategy`]: crate::probing::ProbingStrategy
540+
pub fn set_probing_service_with_custom_strategy<T: ProbingStrategy + Send + Sync + 'static>(
541+
&mut self, probing_interval_secs: u64, probing_amount_msat: u64, strategy: T,
542+
) -> &mut Self {
543+
self.probing_service_config = Some(ProbingServiceConfig {
544+
probing_interval_secs,
545+
probing_amount_msat,
546+
strategy: ProbingStrategyConfig::Custom { strategy: Arc::new(strategy) },
547+
});
548+
self
549+
}
550+
491551
/// Sets the used storage directory path.
492552
pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self {
493553
self.config.storage_dir_path = storage_dir_path;
@@ -744,6 +804,7 @@ impl NodeBuilder {
744804
runtime,
745805
logger,
746806
Arc::new(vss_store),
807+
self.probing_service_config.as_ref(),
747808
)
748809
}
749810

@@ -778,6 +839,7 @@ impl NodeBuilder {
778839
runtime,
779840
logger,
780841
kv_store,
842+
self.probing_service_config.as_ref(),
781843
)
782844
}
783845
}
@@ -977,6 +1039,27 @@ impl ArcedNodeBuilder {
9771039
self.inner.write().unwrap().set_liquidity_provider_lsps2(service_config);
9781040
}
9791041

1042+
/// Configures the probing service with a custom target selection strategy.
1043+
///
1044+
/// This allows full control over how probing targets are selected by providing
1045+
/// a custom implementation of the [`ProbingStrategy`] trait.
1046+
///
1047+
/// # Parameters
1048+
/// * `probing_interval_secs` - Seconds between probing cycles
1049+
/// * `probing_amount_msat` - Amount in milli-satoshis per probe
1050+
/// * `strategy` - Custom [`ProbingStrategy`] implementation
1051+
///
1052+
/// [`ProbingStrategy`]: crate::probing::ProbingStrategy
1053+
pub fn set_probing_service_with_custom_strategy<T: ProbingStrategy + Send + Sync + 'static>(
1054+
&self, probing_interval_secs: u64, probing_amount_msat: u64, strategy: T,
1055+
) {
1056+
self.inner.write().unwrap().set_probing_service_with_custom_strategy(
1057+
probing_interval_secs,
1058+
probing_amount_msat,
1059+
strategy,
1060+
);
1061+
}
1062+
9801063
/// Sets the used storage directory path.
9811064
pub fn set_storage_dir_path(&self, storage_dir_path: String) {
9821065
self.inner.write().unwrap().set_storage_dir_path(storage_dir_path);
@@ -1142,6 +1225,7 @@ fn build_with_store_internal(
11421225
pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>,
11431226
async_payments_role: Option<AsyncPaymentsRole>, seed_bytes: [u8; 64], runtime: Arc<Runtime>,
11441227
logger: Arc<Logger>, kv_store: Arc<DynStore>,
1228+
probing_service_config: Option<&ProbingServiceConfig>,
11451229
) -> Result<Node, BuildError> {
11461230
optionally_install_rustls_cryptoprovider();
11471231

@@ -1767,6 +1851,25 @@ fn build_with_store_internal(
17671851

17681852
let pathfinding_scores_sync_url = pathfinding_scores_sync_config.map(|c| c.url.clone());
17691853

1854+
let probing_service = if let Some(pro_ser) = probing_service_config {
1855+
let strategy: Arc<dyn ProbingStrategy + Send + Sync> = match &pro_ser.strategy {
1856+
ProbingStrategyConfig::Custom { strategy } => Arc::clone(strategy),
1857+
};
1858+
Some(Arc::new(ProbingService::new(
1859+
pro_ser.probing_interval_secs,
1860+
pro_ser.probing_amount_msat,
1861+
Arc::clone(&strategy),
1862+
Arc::clone(&config),
1863+
Arc::clone(&logger),
1864+
Arc::clone(&channel_manager),
1865+
Arc::clone(&keys_manager),
1866+
Arc::clone(&is_running),
1867+
Arc::clone(&payment_store),
1868+
)))
1869+
} else {
1870+
None
1871+
};
1872+
17701873
Ok(Node {
17711874
runtime,
17721875
stop_sender,
@@ -1797,6 +1900,7 @@ fn build_with_store_internal(
17971900
node_metrics,
17981901
om_mailbox,
17991902
async_payments_role,
1903+
probing_service,
18001904
})
18011905
}
18021906

src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub mod logger;
9696
mod message_handler;
9797
pub mod payment;
9898
mod peer_store;
99+
mod probing;
99100
mod runtime;
100101
mod scoring;
101102
mod tx_broadcaster;
@@ -149,6 +150,8 @@ use payment::{
149150
UnifiedQrPayment,
150151
};
151152
use peer_store::{PeerInfo, PeerStore};
153+
use probing::ProbingService;
154+
pub use probing::ProbingStrategy;
152155
use rand::Rng;
153156
use runtime::Runtime;
154157
use types::{
@@ -196,6 +199,7 @@ pub struct Node {
196199
scorer: Arc<Mutex<Scorer>>,
197200
peer_store: Arc<PeerStore<Arc<Logger>>>,
198201
payment_store: Arc<PaymentStore>,
202+
probing_service: Option<Arc<ProbingService>>,
199203
is_running: Arc<RwLock<bool>>,
200204
node_metrics: Arc<RwLock<NodeMetrics>>,
201205
om_mailbox: Option<Arc<OnionMessageMailbox>>,
@@ -625,6 +629,32 @@ impl Node {
625629
});
626630
}
627631

632+
if let Some(probing_service) = self.probing_service.as_ref() {
633+
let mut stop_probing_service = self.stop_sender.subscribe();
634+
let probing_service = Arc::clone(probing_service);
635+
let probing_logger = Arc::clone(&self.logger);
636+
637+
self.runtime.spawn_cancellable_background_task(async move {
638+
let mut interval = tokio::time::interval(Duration::from_secs(
639+
probing_service.probing_interval_secs,
640+
));
641+
loop {
642+
tokio::select! {
643+
_ = stop_probing_service.changed() => {
644+
log_debug!(
645+
probing_logger,
646+
"Stopping probing service.",
647+
);
648+
return;
649+
}
650+
_ = interval.tick() => {
651+
probing_service.handle_probing();
652+
}
653+
}
654+
}
655+
});
656+
}
657+
628658
log_info!(self.logger, "Startup complete.");
629659
*is_running_lock = true;
630660
Ok(())

src/probing.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use bitcoin::secp256k1::PublicKey;
2+
use std::sync::{Arc, RwLock};
3+
4+
use crate::{
5+
config::Config,
6+
logger::{log_debug, log_error, LdkLogger, Logger},
7+
payment::SpontaneousPayment,
8+
types::{ChannelManager, KeysManager, PaymentStore},
9+
};
10+
11+
/// Trait for probing strategies to select targets for liquidity assessment.
12+
pub trait ProbingStrategy {
13+
/// Loads the targets to be used in the current probing cycle.
14+
///
15+
/// Called at the start of each probing cycle before sending probes.
16+
fn load_targets(&self);
17+
18+
/// Returns the next target public key for probing, or None if no more targets are available.
19+
fn next_target(&self) -> Option<PublicKey>;
20+
}
21+
22+
/// Configuration for the probing service used to evaluate channel liquidity by sending pre-flight
23+
/// probes to peers and routes.
24+
pub struct ProbingService {
25+
pub probing_interval_secs: u64,
26+
probing_amount_msat: u64,
27+
strategy: Arc<dyn ProbingStrategy + Send + Sync>,
28+
config: Arc<Config>,
29+
logger: Arc<Logger>,
30+
channel_manager: Arc<ChannelManager>,
31+
keys_manager: Arc<KeysManager>,
32+
is_running: Arc<RwLock<bool>>,
33+
payment_store: Arc<PaymentStore>,
34+
}
35+
36+
impl ProbingService {
37+
/// Creates a new probing service with the given configuration and dependencies.
38+
pub fn new(
39+
probing_interval_secs: u64, probing_amount_msat: u64,
40+
strategy: Arc<dyn ProbingStrategy + Send + Sync>, config: Arc<Config>, logger: Arc<Logger>,
41+
channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
42+
is_running: Arc<RwLock<bool>>, payment_store: Arc<PaymentStore>,
43+
) -> Self {
44+
Self {
45+
probing_interval_secs,
46+
probing_amount_msat,
47+
strategy,
48+
config,
49+
logger,
50+
channel_manager,
51+
keys_manager,
52+
is_running,
53+
payment_store,
54+
}
55+
}
56+
57+
pub fn handle_probing(&self) {
58+
self.strategy.load_targets();
59+
loop {
60+
if let Some(target) = self.strategy.next_target() {
61+
let spontaneous_payment = self.spontaneous_payment();
62+
match spontaneous_payment.send_probes(self.probing_amount_msat, target) {
63+
Ok(_) => {
64+
log_debug!(self.logger, "Probing service sent probe to target: {}", target)
65+
},
66+
Err(e) => log_error!(
67+
self.logger,
68+
"Probing service failed to send probe to target {}: {}",
69+
target,
70+
e
71+
),
72+
}
73+
} else {
74+
break;
75+
}
76+
}
77+
}
78+
79+
fn spontaneous_payment(&self) -> SpontaneousPayment {
80+
SpontaneousPayment::new(
81+
Arc::clone(&self.channel_manager),
82+
Arc::clone(&self.keys_manager),
83+
Arc::clone(&self.payment_store),
84+
Arc::clone(&self.config),
85+
Arc::clone(&self.is_running),
86+
Arc::clone(&self.logger),
87+
)
88+
}
89+
}

0 commit comments

Comments
 (0)