Skip to content

Commit 7f3ce11

Browse files
committed
Create probing service
Create new background task for probing, add its initialization to the builder. Add new trait ProobingStrategy with two default implementations. Total amount of sats in current non-finished probes is tracked, it is changed on ProbeSucceded and ProbeFailed events.
1 parent b55de44 commit 7f3ce11

5 files changed

Lines changed: 535 additions & 5 deletions

File tree

src/builder.rs

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use std::collections::HashMap;
99
use std::convert::TryInto;
1010
use std::default::Default;
1111
use std::path::PathBuf;
12+
use std::sync::atomic::AtomicU64;
1213
use std::sync::{Arc, Mutex, Once, RwLock};
13-
use std::time::SystemTime;
14+
use std::time::{Duration, SystemTime};
1415
use std::{fmt, fs};
1516

1617
use bdk_wallet::template::Bip84;
@@ -47,6 +48,7 @@ use crate::config::{
4748
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
4849
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig,
4950
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
51+
DEFAULT_MAX_PROBE_LOCKED_MSAT, DEFAULT_PROBING_INTERVAL_SECS, MIN_PROBE_AMOUNT_MSAT,
5052
};
5153
use crate::connection::ConnectionManager;
5254
use crate::entropy::NodeEntropy;
@@ -72,6 +74,8 @@ use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
7274
use crate::message_handler::NodeCustomMessageHandler;
7375
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
7476
use crate::peer_store::PeerStore;
77+
use crate::probing;
78+
use crate::probing::ProbingStrategy;
7579
use crate::runtime::{Runtime, RuntimeSpawner};
7680
use crate::tx_broadcaster::TransactionBroadcaster;
7781
use crate::types::{
@@ -150,6 +154,37 @@ impl std::fmt::Debug for LogWriterConfig {
150154
}
151155
}
152156

157+
enum ProbingStrategyKind {
158+
HighDegree { top_n: usize },
159+
Random { max_hops: usize },
160+
Custom(Arc<dyn probing::ProbingStrategy>),
161+
}
162+
163+
struct ProbingStrategyConfig {
164+
kind: ProbingStrategyKind,
165+
interval: Duration,
166+
max_locked_msat: u64,
167+
}
168+
169+
impl fmt::Debug for ProbingStrategyConfig {
170+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171+
let kind_str = match &self.kind {
172+
ProbingStrategyKind::HighDegree { top_n } => {
173+
format!("HighDegree {{ top_n: {} }}", top_n)
174+
},
175+
ProbingStrategyKind::Random { max_hops } => {
176+
format!("Random {{ max_hops: {} }}", max_hops)
177+
},
178+
ProbingStrategyKind::Custom(_) => "Custom(<probing strategy>)".to_string(),
179+
};
180+
f.debug_struct("ProbingStrategyConfig")
181+
.field("kind", &kind_str)
182+
.field("interval", &self.interval)
183+
.field("max_locked_msat", &self.max_locked_msat)
184+
.finish()
185+
}
186+
}
187+
153188
/// An error encountered during building a [`Node`].
154189
///
155190
/// [`Node`]: crate::Node
@@ -245,6 +280,7 @@ pub struct NodeBuilder {
245280
runtime_handle: Option<tokio::runtime::Handle>,
246281
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
247282
recovery_mode: bool,
283+
probing_strategy: Option<ProbingStrategyConfig>,
248284
}
249285

250286
impl NodeBuilder {
@@ -273,6 +309,7 @@ impl NodeBuilder {
273309
async_payments_role: None,
274310
pathfinding_scores_sync_config,
275311
recovery_mode,
312+
probing_strategy: None,
276313
}
277314
}
278315

@@ -557,6 +594,64 @@ impl NodeBuilder {
557594
self
558595
}
559596

597+
/// Configures background probing toward the highest-degree nodes in the network graph.
598+
///
599+
/// `top_n` controls how many of the most-connected nodes are cycled through.
600+
pub fn set_high_degree_probing_strategy(&mut self, top_n: usize) -> &mut Self {
601+
let kind = ProbingStrategyKind::HighDegree { top_n };
602+
self.probing_strategy = Some(self.make_probing_config(kind));
603+
self
604+
}
605+
606+
/// Configures background probing via random graph walks of up to `max_hops` hops.
607+
pub fn set_random_probing_strategy(&mut self, max_hops: usize) -> &mut Self {
608+
let kind = ProbingStrategyKind::Random { max_hops };
609+
self.probing_strategy = Some(self.make_probing_config(kind));
610+
self
611+
}
612+
613+
/// Configures a custom probing strategy for background channel probing.
614+
///
615+
/// When set, the node will periodically call [`ProbingStrategy::next_probe`] and dispatch the
616+
/// returned probe via the channel manager.
617+
pub fn set_probing_strategy(
618+
&mut self, strategy: Arc<dyn probing::ProbingStrategy>,
619+
) -> &mut Self {
620+
let kind = ProbingStrategyKind::Custom(strategy);
621+
self.probing_strategy = Some(self.make_probing_config(kind));
622+
self
623+
}
624+
625+
/// Overrides the interval between probe attempts. Only has effect if a probing strategy is set.
626+
pub fn set_probing_interval(&mut self, interval: Duration) -> &mut Self {
627+
if let Some(cfg) = &mut self.probing_strategy {
628+
cfg.interval = interval;
629+
}
630+
self
631+
}
632+
633+
/// Overrides the maximum millisatoshis that may be locked in in-flight probes at any time.
634+
/// Only has effect if a probing strategy is set.
635+
pub fn set_max_probe_locked_msat(&mut self, max_msat: u64) -> &mut Self {
636+
if let Some(cfg) = &mut self.probing_strategy {
637+
cfg.max_locked_msat = max_msat;
638+
}
639+
self
640+
}
641+
642+
fn make_probing_config(&self, kind: ProbingStrategyKind) -> ProbingStrategyConfig {
643+
let existing = self.probing_strategy.as_ref();
644+
ProbingStrategyConfig {
645+
kind,
646+
interval: existing
647+
.map(|c| c.interval)
648+
.unwrap_or(Duration::from_secs(DEFAULT_PROBING_INTERVAL_SECS)),
649+
max_locked_msat: existing
650+
.map(|c| c.max_locked_msat)
651+
.unwrap_or(DEFAULT_MAX_PROBE_LOCKED_MSAT),
652+
}
653+
}
654+
560655
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
561656
/// previously configured.
562657
pub fn build(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
@@ -697,6 +792,7 @@ impl NodeBuilder {
697792
runtime,
698793
logger,
699794
Arc::new(DynStoreWrapper(kv_store)),
795+
self.probing_strategy.as_ref(),
700796
)
701797
}
702798
}
@@ -942,6 +1038,11 @@ impl ArcedNodeBuilder {
9421038
self.inner.write().unwrap().set_wallet_recovery_mode();
9431039
}
9441040

1041+
/// Configures a probing strategy for background channel probing.
1042+
pub fn set_probing_strategy(&self, strategy: Arc<dyn probing::ProbingStrategy>) {
1043+
self.inner.write().unwrap().set_probing_strategy(strategy);
1044+
}
1045+
9451046
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
9461047
/// previously configured.
9471048
pub fn build(&self, node_entropy: Arc<NodeEntropy>) -> Result<Arc<Node>, BuildError> {
@@ -1058,6 +1159,7 @@ fn build_with_store_internal(
10581159
pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>,
10591160
async_payments_role: Option<AsyncPaymentsRole>, recovery_mode: bool, seed_bytes: [u8; 64],
10601161
runtime: Arc<Runtime>, logger: Arc<Logger>, kv_store: Arc<DynStore>,
1162+
probing_config: Option<&ProbingStrategyConfig>,
10611163
) -> Result<Node, BuildError> {
10621164
optionally_install_rustls_cryptoprovider();
10631165

@@ -1783,6 +1885,36 @@ fn build_with_store_internal(
17831885
_leak_checker.0.push(Arc::downgrade(&wallet) as Weak<dyn Any + Send + Sync>);
17841886
}
17851887

1888+
let prober = probing_config.map(|config| {
1889+
let strategy: Arc<dyn probing::ProbingStrategy> = match &config.kind {
1890+
ProbingStrategyKind::HighDegree { top_n } => {
1891+
Arc::new(probing::HighDegreeStrategy::new(
1892+
network_graph.clone(),
1893+
*top_n,
1894+
MIN_PROBE_AMOUNT_MSAT,
1895+
config.max_locked_msat,
1896+
))
1897+
},
1898+
ProbingStrategyKind::Random { max_hops } => Arc::new(probing::RandomStrategy::new(
1899+
network_graph.clone(),
1900+
channel_manager.clone(),
1901+
*max_hops,
1902+
MIN_PROBE_AMOUNT_MSAT,
1903+
config.max_locked_msat,
1904+
)),
1905+
ProbingStrategyKind::Custom(s) => s.clone(),
1906+
};
1907+
Arc::new(probing::Prober {
1908+
channel_manager: channel_manager.clone(),
1909+
logger: logger.clone(),
1910+
strategy,
1911+
interval: config.interval,
1912+
liquidity_limit_multiplier: None,
1913+
max_locked_msat: config.max_locked_msat,
1914+
locked_msat: Arc::new(AtomicU64::new(0)),
1915+
})
1916+
});
1917+
17861918
Ok(Node {
17871919
runtime,
17881920
stop_sender,
@@ -1815,6 +1947,7 @@ fn build_with_store_internal(
18151947
om_mailbox,
18161948
async_payments_role,
18171949
hrn_resolver,
1950+
prober,
18181951
#[cfg(cycle_tests)]
18191952
_leak_checker,
18201953
})

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80;
2727
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;
30+
pub(crate) const DEFAULT_PROBING_INTERVAL_SECS: u64 = 10;
31+
pub(crate) const DEFAULT_MAX_PROBE_LOCKED_MSAT: u64 = 100_000_000; // 100k sats
32+
pub(crate) const MIN_PROBE_AMOUNT_MSAT: u64 = 1_000_000; // 1k sats
3033
const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000;
3134

3235
// The default timeout after which we abort a wallet syncing operation.

src/event.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ 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};
1213
use std::sync::{Arc, Mutex};
1314

1415
use bitcoin::blockdata::locktime::absolute::LockTime;
@@ -494,6 +495,7 @@ where
494495
static_invoice_store: Option<StaticInvoiceStore>,
495496
onion_messenger: Arc<OnionMessenger>,
496497
om_mailbox: Option<Arc<OnionMessageMailbox>>,
498+
probe_locked_msat: Option<Arc<AtomicU64>>,
497499
}
498500

499501
impl<L: Deref + Clone + Sync + Send + 'static> EventHandler<L>
@@ -509,7 +511,7 @@ where
509511
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
510512
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
511513
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
512-
config: Arc<Config>,
514+
config: Arc<Config>, probe_locked_msat: Option<Arc<AtomicU64>>,
513515
) -> Self {
514516
Self {
515517
event_queue,
@@ -528,6 +530,7 @@ where
528530
static_invoice_store,
529531
onion_messenger,
530532
om_mailbox,
533+
probe_locked_msat,
531534
}
532535
}
533536

@@ -1111,8 +1114,22 @@ where
11111114

11121115
LdkEvent::PaymentPathSuccessful { .. } => {},
11131116
LdkEvent::PaymentPathFailed { .. } => {},
1114-
LdkEvent::ProbeSuccessful { .. } => {},
1115-
LdkEvent::ProbeFailed { .. } => {},
1117+
LdkEvent::ProbeSuccessful { path, .. } => {
1118+
if let Some(counter) = &self.probe_locked_msat {
1119+
let amount = path.hops.last().map_or(0, |h| h.fee_msat);
1120+
let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
1121+
Some(v.saturating_sub(amount))
1122+
});
1123+
}
1124+
},
1125+
LdkEvent::ProbeFailed { path, .. } => {
1126+
if let Some(counter) = &self.probe_locked_msat {
1127+
let amount = path.hops.last().map_or(0, |h| h.fee_msat);
1128+
let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
1129+
Some(v.saturating_sub(amount))
1130+
});
1131+
}
1132+
},
11161133
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
11171134
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
11181135
liquidity_source.handle_htlc_handling_failed(failure_type).await;
@@ -1356,7 +1373,6 @@ where
13561373
);
13571374
}
13581375
}
1359-
13601376
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
13611377
let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0);
13621378
liquidity_source

src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub mod logger;
100100
mod message_handler;
101101
pub mod payment;
102102
mod peer_store;
103+
mod probing;
103104
mod runtime;
104105
mod scoring;
105106
mod tx_broadcaster;
@@ -157,6 +158,7 @@ use payment::{
157158
UnifiedPayment,
158159
};
159160
use peer_store::{PeerInfo, PeerStore};
161+
pub use probing::{HighDegreeStrategy, Probe, ProbingStrategy, RandomStrategy};
160162
use rand::Rng;
161163
use runtime::Runtime;
162164
use types::{
@@ -227,6 +229,7 @@ pub struct Node {
227229
om_mailbox: Option<Arc<OnionMessageMailbox>>,
228230
async_payments_role: Option<AsyncPaymentsRole>,
229231
hrn_resolver: Arc<HRNResolver>,
232+
prober: Option<Arc<probing::Prober>>,
230233
#[cfg(cycle_tests)]
231234
_leak_checker: LeakChecker,
232235
}
@@ -563,6 +566,7 @@ impl Node {
563566
None
564567
};
565568

569+
let probe_locked_msat = self.prober.as_ref().map(|p| Arc::clone(&p.locked_msat));
566570
let event_handler = Arc::new(EventHandler::new(
567571
Arc::clone(&self.event_queue),
568572
Arc::clone(&self.wallet),
@@ -580,8 +584,16 @@ impl Node {
580584
Arc::clone(&self.runtime),
581585
Arc::clone(&self.logger),
582586
Arc::clone(&self.config),
587+
probe_locked_msat,
583588
));
584589

590+
if let Some(prober) = self.prober.clone() {
591+
let stop_rx = self.stop_sender.subscribe();
592+
self.runtime.spawn_cancellable_background_task(async move {
593+
prober.run(stop_rx).await;
594+
});
595+
}
596+
585597
// Setup background processing
586598
let background_persister = Arc::clone(&self.kv_store);
587599
let background_event_handler = Arc::clone(&event_handler);
@@ -1032,6 +1044,17 @@ impl Node {
10321044
))
10331045
}
10341046

1047+
/// Returns the total millisatoshis currently locked in in-flight probes, or `None` if no
1048+
/// probing strategy is configured.
1049+
pub fn probe_locked_msat(&self) -> Option<u64> {
1050+
self.prober.as_ref().map(|p| p.locked_msat.load(std::sync::atomic::Ordering::Relaxed))
1051+
}
1052+
1053+
/// Gives access to the scorer; needed to valuate the probing tests.
1054+
#[cfg(test)]
1055+
pub fn scorer(&self) -> &Arc<Mutex<Scorer>> { &self.scorer }
1056+
1057+
10351058
/// Retrieve a list of known channels.
10361059
pub fn list_channels(&self) -> Vec<ChannelDetails> {
10371060
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()

0 commit comments

Comments
 (0)