Skip to content

Commit 17e0cb6

Browse files
committed
Add pathfinder trait
1 parent 466fea5 commit 17e0cb6

File tree

3 files changed

+112
-77
lines changed

3 files changed

+112
-77
lines changed

sim-cli/src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use simln_lib::{
1010
use simple_logger::SimpleLogger;
1111
use tokio_util::task::TaskTracker;
1212

13+
// Import the pathfinder types
14+
use simln_lib::sim_node::DefaultPathFinder;
15+
1316
#[tokio::main]
1417
async fn main() -> anyhow::Result<()> {
1518
// Enable tracing if building in developer mode.
@@ -31,6 +34,9 @@ async fn main() -> anyhow::Result<()> {
3134
cli.validate(&sim_params)?;
3235

3336
let tasks = TaskTracker::new();
37+
38+
// Create the pathfinder instance
39+
let pathfinder = DefaultPathFinder;
3440

3541
let (sim, validated_activities) = if sim_params.sim_network.is_empty() {
3642
create_simulation(&cli, &sim_params, tasks.clone()).await?
@@ -46,6 +52,7 @@ async fn main() -> anyhow::Result<()> {
4652
&sim_params,
4753
tasks.clone(),
4854
interceptors,
55+
pathfinder,
4956
CustomRecords::default(),
5057
)
5158
.await?
@@ -60,4 +67,4 @@ async fn main() -> anyhow::Result<()> {
6067
sim.run(&validated_activities).await?;
6168

6269
Ok(())
63-
}
70+
}

sim-cli/src/parsing.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use log::LevelFilter;
55
use serde::{Deserialize, Serialize};
66
use simln_lib::clock::SimulationClock;
77
use simln_lib::sim_node::{
8-
ln_node_from_graph, populate_network_graph, ChannelPolicy, CustomRecords, Interceptor,
8+
ln_node_from_graph, populate_network_graph, ChannelPolicy, CustomRecords, Interceptor, PathFinder,
99
SimGraph, SimulatedChannel,
1010
};
11+
1112
use simln_lib::{
1213
cln, cln::ClnNode, eclair, eclair::EclairNode, lnd, lnd::LndNode, serializers,
1314
ActivityDefinition, Amount, Interval, LightningError, LightningNode, NodeId, NodeInfo,
@@ -224,12 +225,13 @@ struct NodeMapping {
224225
alias_node_map: HashMap<String, NodeInfo>,
225226
}
226227

227-
pub async fn create_simulation_with_network(
228+
pub async fn create_simulation_with_network<P: for<'a> PathFinder<'a> + Clone + 'static>(
228229
cli: &Cli,
229230
sim_params: &SimParams,
230231
tasks: TaskTracker,
231232
interceptors: Vec<Arc<dyn Interceptor>>,
232233
custom_records: CustomRecords,
234+
pathfinder: P,
233235
) -> Result<(Simulation<SimulationClock>, Vec<ActivityDefinition>), anyhow::Error> {
234236
let cfg: SimulationCfg = SimulationCfg::try_from(cli)?;
235237
let SimParams {
@@ -276,7 +278,8 @@ pub async fn create_simulation_with_network(
276278
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?,
277279
);
278280

279-
let nodes = ln_node_from_graph(simulation_graph.clone(), routing_graph).await;
281+
// Pass the pathfinder to ln_node_from_graph
282+
let nodes = ln_node_from_graph(simulation_graph.clone(), routing_graph, pathfinder).await;
280283
let validated_activities =
281284
get_validated_activities(&nodes, nodes_info, sim_params.activity.clone()).await?;
282285

@@ -293,6 +296,7 @@ pub async fn create_simulation_with_network(
293296
))
294297
}
295298

299+
296300
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
297301
/// any activity described in the simulation file.
298302
pub async fn create_simulation(

simln-lib/src/sim_node.rs

Lines changed: 97 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -484,11 +484,57 @@ trait SimNetwork: Send + Sync {
484484
fn list_nodes(&self) -> Result<Vec<NodeInfo>, LightningError>;
485485
}
486486

487+
/// A trait for custom pathfinding implementations.
488+
pub trait PathFinder<'a>: Send + Sync {
489+
fn find_route(
490+
&self,
491+
source: &PublicKey,
492+
dest: PublicKey,
493+
amount_msat: u64,
494+
pathfinding_graph: &NetworkGraph<&'a WrappedLog>,
495+
scorer: &ProbabilisticScorer<Arc<NetworkGraph<&'a WrappedLog>>, &'a WrappedLog>,
496+
) -> Result<Route, SimulationError>;
497+
}
498+
499+
/// Default pathfinder that uses LDK's pathfinding algorithm.
500+
#[derive(Clone)]
501+
pub struct DefaultPathFinder;
502+
503+
impl<'a> PathFinder<'a> for DefaultPathFinder {
504+
fn find_route(
505+
&self,
506+
source: &PublicKey,
507+
dest: PublicKey,
508+
amount_msat: u64,
509+
pathfinding_graph: &NetworkGraph<&'a WrappedLog>,
510+
scorer: &ProbabilisticScorer<Arc<NetworkGraph<&'a WrappedLog>>, &'a WrappedLog>,
511+
) -> Result<Route, SimulationError> {
512+
find_route(
513+
source,
514+
&RouteParameters {
515+
payment_params: PaymentParameters::from_node_id(dest, 0)
516+
.with_max_total_cltv_expiry_delta(u32::MAX)
517+
.with_max_path_count(1)
518+
.with_max_channel_saturation_power_of_half(1),
519+
final_value_msat: amount_msat,
520+
max_total_routing_fee_msat: None,
521+
},
522+
pathfinding_graph,
523+
None,
524+
&WrappedLog {},
525+
scorer,
526+
&Default::default(),
527+
&[0; 32],
528+
)
529+
.map_err(|e| SimulationError::SimulatedNetworkError(e.err))
530+
}
531+
}
532+
487533
/// A wrapper struct used to implement the LightningNode trait (can be thought of as "the" lightning node). Passes
488534
/// all functionality through to a coordinating simulation network. This implementation contains both the [`SimNetwork`]
489535
/// implementation that will allow us to dispatch payments and a read-only NetworkGraph that is used for pathfinding.
490536
/// While these two could be combined, we re-use the LDK-native struct to allow re-use of their pathfinding logic.
491-
struct SimNode<'a, T: SimNetwork> {
537+
struct SimNode<'a, T: SimNetwork, P: PathFinder<'a> = DefaultPathFinder> {
492538
info: NodeInfo,
493539
/// The underlying execution network that will be responsible for dispatching payments.
494540
network: Arc<Mutex<T>>,
@@ -499,15 +545,18 @@ struct SimNode<'a, T: SimNetwork> {
499545
/// Probabilistic scorer used to rank paths through the network for routing. This is reused across
500546
/// multiple payments to maintain scoring state.
501547
scorer: ProbabilisticScorer<Arc<NetworkGraph<&'a WrappedLog>>, &'a WrappedLog>,
548+
/// The pathfinder implementation to use for finding routes
549+
pathfinder: P,
502550
}
503551

504-
impl<'a, T: SimNetwork> SimNode<'a, T> {
552+
impl<'a, T: SimNetwork, P: PathFinder<'a>> SimNode<'a, T, P> {
505553
/// Creates a new simulation node that refers to the high level network coordinator provided to process payments
506554
/// on its behalf. The pathfinding graph is provided separately so that each node can handle its own pathfinding.
507555
pub fn new(
508556
pubkey: PublicKey,
509557
payment_network: Arc<Mutex<T>>,
510558
pathfinding_graph: Arc<NetworkGraph<&'a WrappedLog>>,
559+
pathfinder: P,
511560
) -> Self {
512561
// Initialize the probabilistic scorer with default parameters for learning from payment
513562
// history. These parameters control how much successful/failed payments affect routing
@@ -524,6 +573,7 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
524573
in_flight: HashMap::new(),
525574
pathfinding_graph,
526575
scorer,
576+
pathfinder,
527577
}
528578
}
529579
}
@@ -541,39 +591,8 @@ fn node_info(pubkey: PublicKey) -> NodeInfo {
541591
}
542592
}
543593

544-
/// Uses LDK's pathfinding algorithm with default parameters to find a path from source to destination, with no
545-
/// restrictions on fee budget.
546-
fn find_payment_route<'a>(
547-
source: &PublicKey,
548-
dest: PublicKey,
549-
amount_msat: u64,
550-
pathfinding_graph: &NetworkGraph<&'a WrappedLog>,
551-
scorer: &ProbabilisticScorer<Arc<NetworkGraph<&'a WrappedLog>>, &'a WrappedLog>,
552-
) -> Result<Route, SimulationError> {
553-
find_route(
554-
source,
555-
&RouteParameters {
556-
payment_params: PaymentParameters::from_node_id(dest, 0)
557-
.with_max_total_cltv_expiry_delta(u32::MAX)
558-
// TODO: set non-zero value to support MPP.
559-
.with_max_path_count(1)
560-
// Allow sending htlcs up to 50% of the channel's capacity.
561-
.with_max_channel_saturation_power_of_half(1),
562-
final_value_msat: amount_msat,
563-
max_total_routing_fee_msat: None,
564-
},
565-
pathfinding_graph,
566-
None,
567-
&WrappedLog {},
568-
scorer,
569-
&Default::default(),
570-
&[0; 32],
571-
)
572-
.map_err(|e| SimulationError::SimulatedNetworkError(e.err))
573-
}
574-
575594
#[async_trait]
576-
impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
595+
impl<'a, T: SimNetwork, P: PathFinder<'a>> LightningNode for SimNode<'a, T, P> {
577596
fn get_info(&self) -> &NodeInfo {
578597
&self.info
579598
}
@@ -589,8 +608,24 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
589608
dest: PublicKey,
590609
amount_msat: u64,
591610
) -> Result<PaymentHash, LightningError> {
592-
// Create a sender and receiver pair that will be used to report the results of the payment and add them to
593-
// our internal tracking state along with the chosen payment hash.
611+
// Use the stored scorer when finding a route
612+
let route = match self.pathfinder.find_route(
613+
&self.info.pubkey,
614+
dest,
615+
amount_msat,
616+
&self.pathfinding_graph,
617+
&self.scorer,
618+
) {
619+
Ok(route) => route,
620+
Err(e) => {
621+
log::warn!("No route found: {e}");
622+
return Err(LightningError::SendPaymentError(format!(
623+
"No route found: {e}"
624+
)));
625+
},
626+
};
627+
628+
// Create a channel to receive the payment result.
594629
let (sender, receiver) = channel();
595630
let preimage = PaymentPreimage(rand::random());
596631
let payment_hash = preimage.into();
@@ -607,36 +642,13 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
607642
},
608643
}
609644

610-
// Use the stored scorer when finding a route
611-
let route = match find_payment_route(
612-
&self.info.pubkey,
613-
dest,
614-
amount_msat,
615-
&self.pathfinding_graph,
616-
&self.scorer,
617-
) {
618-
Ok(path) => path,
619-
// In the case that we can't find a route for the payment, we still report a successful payment *api call*
620-
// and report RouteNotFound to the tracking channel. This mimics the behavior of real nodes.
621-
Err(e) => {
622-
log::trace!("Could not find path for payment: {:?}.", e);
623-
624-
if let Err(e) = sender.send(Ok(PaymentResult {
625-
htlc_count: 0,
626-
payment_outcome: PaymentOutcome::RouteNotFound,
627-
})) {
628-
log::error!("Could not send payment result: {:?}.", e);
629-
}
630-
631-
return Ok(payment_hash);
632-
},
633-
};
634-
635-
// If we did successfully obtain a route, dispatch the payment through the network and then report success.
636-
self.network
637-
.lock()
638-
.await
639-
.dispatch_payment(self.info.pubkey, route, payment_hash, sender);
645+
// Dispatch the payment through the network
646+
self.network.lock().await.dispatch_payment(
647+
self.info.pubkey,
648+
route,
649+
payment_hash,
650+
sender,
651+
);
640652

641653
Ok(payment_hash)
642654
}
@@ -975,19 +987,24 @@ impl SimGraph {
975987
}
976988

977989
/// Produces a map of node public key to lightning node implementation to be used for simulations.
978-
pub async fn ln_node_from_graph(
990+
pub async fn ln_node_from_graph<P>(
979991
graph: Arc<Mutex<SimGraph>>,
980-
routing_graph: Arc<NetworkGraph<&'_ WrappedLog>>,
981-
) -> HashMap<PublicKey, Arc<Mutex<dyn LightningNode + '_>>> {
992+
routing_graph: Arc<NetworkGraph<&'static WrappedLog>>,
993+
pathfinder: P,
994+
) -> HashMap<PublicKey, Arc<Mutex<dyn LightningNode>>>
995+
where
996+
P: for<'a> PathFinder<'a> + Clone + 'static,
997+
{
982998
let mut nodes: HashMap<PublicKey, Arc<Mutex<dyn LightningNode>>> = HashMap::new();
983-
999+
9841000
for pk in graph.lock().await.nodes.keys() {
9851001
nodes.insert(
9861002
*pk,
9871003
Arc::new(Mutex::new(SimNode::new(
9881004
*pk,
9891005
graph.clone(),
9901006
routing_graph.clone(),
1007+
pathfinder.clone(),
9911008
))),
9921009
);
9931010
}
@@ -1864,7 +1881,7 @@ mod tests {
18641881

18651882
// Create a simulated node for the first channel in our network.
18661883
let pk = channels[0].node_1.policy.pubkey;
1867-
let mut node = SimNode::new(pk, sim_network.clone(), Arc::new(graph));
1884+
let mut node = SimNode::new(pk, sim_network.clone(), Arc::new(graph), DefaultPathFinder);
18681885

18691886
// Prime mock to return node info from lookup and assert that we get the pubkey we're expecting.
18701887
let lookup_pk = channels[3].node_1.policy.pubkey;
@@ -1955,6 +1972,7 @@ mod tests {
19551972
routing_graph: Arc<NetworkGraph<&'a WrappedLog>>,
19561973
scorer: ProbabilisticScorer<Arc<NetworkGraph<&'a WrappedLog>>, &'a WrappedLog>,
19571974
shutdown: (Trigger, Listener),
1975+
pathfinder: DefaultPathFinder,
19581976
}
19591977

19601978
impl DispatchPaymentTestKit<'_> {
@@ -2003,6 +2021,7 @@ mod tests {
20032021
routing_graph,
20042022
scorer,
20052023
shutdown: shutdown_clone,
2024+
pathfinder: DefaultPathFinder,
20062025
};
20072026

20082027
// Assert that our channel balance is all on the side of the channel opener when we start up.
@@ -2045,8 +2064,13 @@ mod tests {
20452064
dest: PublicKey,
20462065
amt: u64,
20472066
) -> (Route, Result<PaymentResult, LightningError>) {
2048-
let route =
2049-
find_payment_route(&source, dest, amt, &self.routing_graph, &self.scorer).unwrap();
2067+
let route = self.pathfinder.find_route(
2068+
&source,
2069+
dest,
2070+
amt,
2071+
&self.routing_graph,
2072+
&self.scorer,
2073+
).unwrap();
20502074

20512075
let (sender, receiver) = oneshot::channel();
20522076
self.graph

0 commit comments

Comments
 (0)