Skip to content

Commit 3d2685b

Browse files
committed
simln-lib/feature: Add ability to run with mocked ln network
1 parent 2b30a3c commit 3d2685b

File tree

4 files changed

+142
-21
lines changed

4 files changed

+142
-21
lines changed

sim-cli/src/main.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async fn main() -> anyhow::Result<()> {
2020
.init()
2121
.unwrap();
2222

23-
let sim = create_simulation(&cli).await?;
23+
let (sim, sim_network) = create_simulation(&cli).await?;
2424
let sim2 = sim.clone();
2525

2626
ctrlc::set_handler(move || {
@@ -30,5 +30,9 @@ async fn main() -> anyhow::Result<()> {
3030

3131
sim.run().await?;
3232

33+
if let Some(network) = sim_network {
34+
network.lock().await.tasks.wait().await;
35+
}
36+
3337
Ok(())
3438
}

sim-cli/src/parsing.rs

+95-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use bitcoin::secp256k1::PublicKey;
33
use clap::{builder::TypedValueParser, Parser};
44
use log::LevelFilter;
55
use serde::{Deserialize, Serialize};
6+
use simln_lib::sim_node::{node_info, ChannelPolicy, SimGraph, SimulatedChannel};
7+
use simln_lib::ShortChannelID;
68
use simln_lib::{
79
cln, cln::ClnNode, eclair, eclair::EclairNode, lnd, lnd::LndNode, serializers,
810
ActivityDefinition, Amount, Interval, LightningError, LightningNode, NodeId, NodeInfo,
@@ -83,8 +85,11 @@ pub struct Cli {
8385

8486
#[derive(Debug, Serialize, Deserialize, Clone)]
8587
struct SimParams {
88+
#[serde(default)]
8689
pub nodes: Vec<NodeConnection>,
8790
#[serde(default)]
91+
pub sim_network: Vec<NetworkParser>,
92+
#[serde(default)]
8893
pub activity: Vec<ActivityParser>,
8994
}
9095

@@ -96,6 +101,27 @@ enum NodeConnection {
96101
Eclair(eclair::EclairConnection),
97102
}
98103

104+
/// Data structure that is used to parse information from the simulation file, used to pair two node policies together
105+
/// without the other internal state that is used in our simulated network.
106+
#[derive(Debug, Clone, Serialize, Deserialize)]
107+
pub struct NetworkParser {
108+
pub scid: ShortChannelID,
109+
pub capacity_msat: u64,
110+
pub node_1: ChannelPolicy,
111+
pub node_2: ChannelPolicy,
112+
}
113+
114+
impl From<NetworkParser> for SimulatedChannel {
115+
fn from(network_parser: NetworkParser) -> Self {
116+
SimulatedChannel::new(
117+
network_parser.capacity_msat,
118+
network_parser.scid,
119+
network_parser.node_1,
120+
network_parser.node_2,
121+
)
122+
}
123+
}
124+
99125
/// Data structure used to parse information from the simulation file. It allows source and destination to be
100126
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
101127
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -142,11 +168,13 @@ impl TryFrom<&Cli> for SimulationCfg {
142168

143169
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
144170
/// any activity described in the simulation file.
145-
pub async fn create_simulation(cli: &Cli) -> Result<Simulation, anyhow::Error> {
171+
pub async fn create_simulation(
172+
cli: &Cli,
173+
) -> Result<(Simulation, Option<Arc<Mutex<SimGraph>>>), anyhow::Error> {
146174
let cfg: SimulationCfg = SimulationCfg::try_from(cli)?;
147175

148176
let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file.clone()).await?;
149-
let SimParams { nodes, activity } = serde_json::from_str(&std::fs::read_to_string(sim_path)?)
177+
let SimParams { nodes, sim_network, activity} = serde_json::from_str(&std::fs::read_to_string(sim_path)?)
150178
.map_err(|e| {
151179
anyhow!(
152180
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {}).",
@@ -156,23 +184,74 @@ pub async fn create_simulation(cli: &Cli) -> Result<Simulation, anyhow::Error> {
156184
)
157185
})?;
158186

159-
let (clients, clients_info) = get_clients(nodes).await?;
160-
// We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
161-
// nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
162-
let get_node = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
163-
if let Some(c) = clients.values().next() {
164-
return c.lock().await.get_node_info(pk).await;
165-
}
166-
167-
Err(LightningError::GetNodeInfoError(
168-
"no nodes for query".to_string(),
187+
// Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field.
188+
if !nodes.is_empty() && !sim_network.is_empty() {
189+
Err(anyhow!(
190+
"Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real
191+
or simulated nodes not both.", nodes.len(), sim_network.len(),
169192
))
170-
};
193+
} else if nodes.is_empty() && sim_network.is_empty() {
194+
Err(anyhow!(
195+
"Simulation file must contain nodes to run with real lightning nodes or sim_graph to run with
196+
simulated nodes",
197+
))
198+
} else if !nodes.is_empty() {
199+
let (clients, clients_info) = get_clients(nodes).await?;
200+
// We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
201+
// nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
202+
let get_node = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
203+
if let Some(c) = clients.values().next() {
204+
return c.lock().await.get_node_info(pk).await;
205+
}
206+
207+
Err(LightningError::GetNodeInfoError(
208+
"no nodes for query".to_string(),
209+
))
210+
};
171211

172-
let validated_activities = validate_activities(activity, &clients_info, get_node).await?;
173-
let tasks = TaskTracker::new();
212+
let validated_activities = validate_activities(activity, &clients_info, get_node).await?;
213+
let tasks = TaskTracker::new();
174214

175-
Ok(Simulation::new(cfg, clients, validated_activities, tasks))
215+
Ok((
216+
Simulation::new(cfg, clients, validated_activities, tasks),
217+
None,
218+
))
219+
} else {
220+
// Convert nodes representation for parsing to SimulatedChannel
221+
let channels = sim_network
222+
.clone()
223+
.into_iter()
224+
.map(SimulatedChannel::from)
225+
.collect::<Vec<SimulatedChannel>>();
226+
227+
let mut nodes_info = HashMap::new();
228+
for sim_channel in sim_network {
229+
nodes_info.insert(
230+
sim_channel.node_1.pubkey,
231+
node_info(sim_channel.node_1.pubkey),
232+
);
233+
nodes_info.insert(
234+
sim_channel.node_2.pubkey,
235+
node_info(sim_channel.node_2.pubkey),
236+
);
237+
}
238+
let get_node_info = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
239+
if let Some(node) = nodes_info.get(pk) {
240+
Ok(node_info(node.pubkey))
241+
} else {
242+
Err(LightningError::GetNodeInfoError(format!(
243+
"node not found in simulated network: {}",
244+
pk
245+
)))
246+
}
247+
};
248+
let validated_activities =
249+
validate_activities(activity, &nodes_info, get_node_info).await?;
250+
let tasks = TaskTracker::new();
251+
let (simulation, graph) =
252+
Simulation::new_with_sim_network(cfg, channels, validated_activities, tasks).await?;
253+
Ok((simulation, Some(graph)))
254+
}
176255
}
177256

178257
/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and

simln-lib/src/lib.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rand::{Rng, RngCore, SeedableRng};
99
use rand_chacha::ChaCha8Rng;
1010
use random_activity::RandomActivityError;
1111
use serde::{Deserialize, Serialize};
12+
use sim_node::{ln_node_from_graph, populate_network_graph, SimGraph, SimulatedChannel};
1213
use std::collections::HashSet;
1314
use std::fmt::{Display, Formatter};
1415
use std::marker::Send;
@@ -90,7 +91,7 @@ impl std::fmt::Display for NodeId {
9091
}
9192

9293
/// Represents a short channel ID, expressed as a struct so that we can implement display for the trait.
93-
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
94+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
9495
pub struct ShortChannelID(u64);
9596

9697
/// Utility function to easily convert from u64 to `ShortChannelID`
@@ -527,6 +528,42 @@ impl Simulation {
527528
}
528529
}
529530

531+
pub async fn new_with_sim_network(
532+
cfg: SimulationCfg,
533+
channels: Vec<SimulatedChannel>,
534+
activity: Vec<ActivityDefinition>,
535+
tasks: TaskTracker,
536+
) -> Result<(Simulation, Arc<Mutex<SimGraph>>), SimulationError> {
537+
let (shutdown_trigger, shutdown_listener) = triggered::trigger();
538+
539+
// Setup a simulation graph that will handle propagation of payments through the network
540+
let simulation_graph = Arc::new(Mutex::new(
541+
SimGraph::new(channels.clone(), tasks.clone(), shutdown_trigger.clone())
542+
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?,
543+
));
544+
545+
// Copy all simulated channels into a read-only routing graph, allowing to pathfind for
546+
// individual payments without locking th simulation graph (this is a duplication of the channels, but the performance tradeoff is worthwhile for concurrent pathfinding).
547+
let routing_graph = Arc::new(
548+
populate_network_graph(channels)
549+
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?,
550+
);
551+
552+
let nodes = ln_node_from_graph(simulation_graph.clone(), routing_graph).await;
553+
Ok((
554+
Self {
555+
nodes,
556+
activity,
557+
results: Arc::new(Mutex::new(PaymentResultLogger::new())),
558+
tasks,
559+
shutdown_trigger,
560+
shutdown_listener,
561+
cfg,
562+
},
563+
simulation_graph,
564+
))
565+
}
566+
530567
/// validate_activity validates that the user-provided activity description is achievable for the network that
531568
/// we're working with. If no activity description is provided, then it ensures that we have configured a network
532569
/// that is suitable for random activity generation.

simln-lib/src/sim_node.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bitcoin::constants::ChainHash;
66
use bitcoin::secp256k1::PublicKey;
77
use bitcoin::{Network, ScriptBuf, TxOut};
88
use lightning::ln::chan_utils::make_funding_redeemscript;
9+
use serde::{Deserialize, Serialize};
910
use std::collections::{hash_map::Entry, HashMap};
1011
use std::sync::Arc;
1112
use std::time::{SystemTime, UNIX_EPOCH};
@@ -109,7 +110,7 @@ struct Htlc {
109110
/// Represents one node in the channel's forwarding policy and restrictions. Note that this doesn't directly map to
110111
/// a single concept in the protocol, a few things have been combined for the sake of simplicity. Used to manage the
111112
/// lightning "state machine" and check that HTLCs are added in accordance of the advertised policy.
112-
#[derive(Clone)]
113+
#[derive(Debug, Clone, Serialize, Deserialize)]
113114
pub struct ChannelPolicy {
114115
pub pubkey: PublicKey,
115116
pub max_htlc_count: u64,
@@ -488,7 +489,7 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
488489
}
489490

490491
/// Produces the node info for a mocked node, filling in the features that the simulator requires.
491-
fn node_info(pubkey: PublicKey) -> NodeInfo {
492+
pub fn node_info(pubkey: PublicKey) -> NodeInfo {
492493
// Set any features that the simulator requires here.
493494
let mut features = NodeFeatures::empty();
494495
features.set_keysend_optional();
@@ -654,7 +655,7 @@ pub struct SimGraph {
654655

655656
/// track all tasks spawned to process payments in the graph. Note that handling the shutdown of tasks
656657
/// in this tracker must be done externally.
657-
tasks: TaskTracker,
658+
pub tasks: TaskTracker,
658659

659660
/// trigger shutdown if a critical error occurs.
660661
shutdown_trigger: Trigger,

0 commit comments

Comments
 (0)