Skip to content

Commit b09ac15

Browse files
committed
fixup! 0a36a79
1 parent 0a36a79 commit b09ac15

File tree

7 files changed

+175
-139
lines changed

7 files changed

+175
-139
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sim-cli/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ anyhow = { version = "1.0.69", features = ["backtrace"] }
1414
clap = { version = "4.1.6", features = ["derive", "env", "std", "help", "usage", "error-context", "suggestions"], default-features = false }
1515
dialoguer = "0.11.0"
1616
log = "0.4.20"
17+
triggered = "0.1.2"
1718
serde = "1.0.183"
1819
serde_json = "1.0.104"
1920
simple_logger = "4.2.0"

sim-cli/src/main.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use anyhow::anyhow;
12
use clap::Parser;
23
use log::LevelFilter;
3-
use sim_cli::parsing::{create_simulation, Cli};
4+
use sim_cli::parsing::{create_simulation, create_simulation_with_network, read_sim_path, Cli};
45
use simple_logger::SimpleLogger;
6+
use tokio_util::task::TaskTracker;
57

68
#[tokio::main]
79
async fn main() -> anyhow::Result<()> {
@@ -20,7 +22,25 @@ async fn main() -> anyhow::Result<()> {
2022
.init()
2123
.unwrap();
2224

23-
let (sim, sim_network) = create_simulation(&cli).await?;
25+
let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file.clone()).await?;
26+
let sim_params = serde_json::from_str(&std::fs::read_to_string(sim_path)?).map_err(|e| {
27+
anyhow!(
28+
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {}).",
29+
e.line(),
30+
e.column(),
31+
e.to_string()
32+
)
33+
})?;
34+
35+
cli.validate(&sim_params)?;
36+
37+
let tasks = TaskTracker::new();
38+
39+
let sim = if sim_params.sim_network.is_empty() {
40+
create_simulation(&cli, &sim_params, tasks.clone()).await?
41+
} else {
42+
create_simulation_with_network(&cli, &sim_params, tasks.clone()).await?
43+
};
2444
let sim2 = sim.clone();
2545

2646
ctrlc::set_handler(move || {
@@ -29,10 +49,7 @@ async fn main() -> anyhow::Result<()> {
2949
})?;
3050

3151
sim.run().await?;
32-
33-
if let Some(network) = sim_network {
34-
network.lock().await.tasks.wait().await;
35-
}
52+
tasks.wait().await;
3653

3754
Ok(())
3855
}

sim-cli/src/parsing.rs

+144-94
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ 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;
6+
use simln_lib::sim_node::{
7+
ln_node_from_graph, node_info, populate_network_graph, ChannelPolicy, SimGraph,
8+
SimulatedChannel,
9+
};
810
use simln_lib::{
911
cln, cln::ClnNode, eclair, eclair::EclairNode, lnd, lnd::LndNode, serializers,
1012
ActivityDefinition, Amount, Interval, LightningError, LightningNode, NodeId, NodeInfo,
1113
Simulation, SimulationCfg, WriteResults,
1214
};
15+
use simln_lib::{ShortChannelID, SimulationError};
1316
use std::collections::HashMap;
1417
use std::fs;
1518
use std::ops::AsyncFn;
@@ -24,7 +27,7 @@ pub const DEFAULT_DATA_DIR: &str = ".";
2427
/// The default simulation file to be used by the simulator.
2528
pub const DEFAULT_SIM_FILE: &str = "sim.json";
2629

27-
/// The default expected payment amount for the simulation, around ~$10 at the time of writing.
30+
/// The default expected payment amount for the simulation, around ~$10 at the time of w.
2831
pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000;
2932

3033
/// The number of times over each node in the network sends its total deployed capacity in a calendar month.
@@ -83,10 +86,29 @@ pub struct Cli {
8386
pub fix_seed: Option<u64>,
8487
}
8588

89+
impl Cli {
90+
pub fn validate(&self, sim_params: &SimParams) -> Result<(), anyhow::Error> {
91+
// Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field.
92+
if !sim_params.nodes.is_empty() && !sim_params.sim_network.is_empty() {
93+
return Err(anyhow!(
94+
"Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real
95+
or simulated nodes not both.", sim_params.nodes.len(), sim_params.sim_network.len(),
96+
));
97+
}
98+
if sim_params.nodes.is_empty() && sim_params.sim_network.is_empty() {
99+
return Err(anyhow!(
100+
"Simulation file must contain nodes to run with real lightning nodes or sim_graph to run with
101+
simulated nodes",
102+
));
103+
}
104+
Ok(())
105+
}
106+
}
107+
86108
#[derive(Debug, Serialize, Deserialize, Clone)]
87-
struct SimParams {
109+
pub struct SimParams {
88110
#[serde(default)]
89-
pub nodes: Vec<NodeConnection>,
111+
nodes: Vec<NodeConnection>,
90112
#[serde(default)]
91113
pub sim_network: Vec<NetworkParser>,
92114
#[serde(default)]
@@ -101,8 +123,8 @@ enum NodeConnection {
101123
Eclair(eclair::EclairConnection),
102124
}
103125

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.
126+
/// Data structure that is used to parse information from the simulation file. It is used to
127+
/// create a mocked network
106128
#[derive(Debug, Clone, Serialize, Deserialize)]
107129
pub struct NetworkParser {
108130
pub scid: ShortChannelID,
@@ -125,7 +147,7 @@ impl From<NetworkParser> for SimulatedChannel {
125147
/// Data structure used to parse information from the simulation file. It allows source and destination to be
126148
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
127149
#[derive(Debug, Clone, Serialize, Deserialize)]
128-
struct ActivityParser {
150+
pub struct ActivityParser {
129151
/// The source of the payment.
130152
#[serde(with = "serializers::serde_node_id")]
131153
pub source: NodeId,
@@ -166,94 +188,121 @@ impl TryFrom<&Cli> for SimulationCfg {
166188
}
167189
}
168190

191+
pub async fn create_simulation_with_network(
192+
cli: &Cli,
193+
sim_params: &SimParams,
194+
tasks: TaskTracker,
195+
) -> Result<Simulation, anyhow::Error> {
196+
let cfg: SimulationCfg = SimulationCfg::try_from(cli)?;
197+
let SimParams {
198+
nodes: _,
199+
sim_network,
200+
activity,
201+
} = sim_params;
202+
203+
// Convert nodes representation for parsing to SimulatedChannel
204+
let channels = sim_network
205+
.clone()
206+
.into_iter()
207+
.map(SimulatedChannel::from)
208+
.collect::<Vec<SimulatedChannel>>();
209+
210+
let mut nodes_info = HashMap::new();
211+
for sim_channel in sim_network {
212+
nodes_info.insert(
213+
sim_channel.node_1.pubkey,
214+
node_info(sim_channel.node_1.pubkey),
215+
);
216+
nodes_info.insert(
217+
sim_channel.node_2.pubkey,
218+
node_info(sim_channel.node_2.pubkey),
219+
);
220+
}
221+
let get_node_info = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
222+
if let Some(node) = nodes_info.get(pk) {
223+
Ok(node_info(node.pubkey))
224+
} else {
225+
Err(LightningError::GetNodeInfoError(format!(
226+
"node not found in simulated network: {}",
227+
pk
228+
)))
229+
}
230+
};
231+
let (pk_node_map, alias_node_map) = add_node_to_maps(&nodes_info)?;
232+
let validated_activities = validate_activities(
233+
activity.to_vec(),
234+
pk_node_map,
235+
alias_node_map,
236+
get_node_info,
237+
)
238+
.await?;
239+
240+
let (shutdown_trigger, shutdown_listener) = triggered::trigger();
241+
242+
// Setup a simulation graph that will handle propagation of payments through the network
243+
let simulation_graph = Arc::new(Mutex::new(
244+
SimGraph::new(channels.clone(), tasks.clone(), shutdown_trigger.clone())
245+
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?,
246+
));
247+
248+
// Copy all simulated channels into a read-only routing graph, allowing to pathfind for
249+
// individual payments without locking th simulation graph (this is a duplication of the channels, but the performance tradeoff is worthwhile for concurrent pathfinding).
250+
let routing_graph = Arc::new(
251+
populate_network_graph(channels)
252+
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?,
253+
);
254+
255+
let nodes = ln_node_from_graph(simulation_graph.clone(), routing_graph).await;
256+
257+
Ok(Simulation::new(
258+
cfg,
259+
nodes,
260+
validated_activities,
261+
tasks,
262+
shutdown_trigger,
263+
shutdown_listener,
264+
))
265+
}
266+
169267
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
170268
/// any activity described in the simulation file.
171269
pub async fn create_simulation(
172270
cli: &Cli,
173-
) -> Result<(Simulation, Option<Arc<Mutex<SimGraph>>>), anyhow::Error> {
271+
sim_params: &SimParams,
272+
tasks: TaskTracker,
273+
) -> Result<Simulation, anyhow::Error> {
174274
let cfg: SimulationCfg = SimulationCfg::try_from(cli)?;
175-
176-
let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file.clone()).await?;
177-
let SimParams { nodes, sim_network, activity} = serde_json::from_str(&std::fs::read_to_string(sim_path)?)
178-
.map_err(|e| {
179-
anyhow!(
180-
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {}).",
181-
e.line(),
182-
e.column(),
183-
e.to_string()
184-
)
185-
})?;
186-
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(),
192-
))
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-
Err(LightningError::GetNodeInfoError(
207-
"no nodes for query".to_string(),
208-
))
209-
};
210-
211-
let (pk_node_map, alias_node_map) = add_node_to_maps(&clients_info).await?;
212-
let validated_activities =
213-
validate_activities(activity, pk_node_map, alias_node_map, get_node).await?;
214-
let tasks = TaskTracker::new();
215-
216-
Ok((
217-
Simulation::new(cfg, clients, validated_activities, tasks),
218-
None,
219-
))
220-
} else {
221-
// Convert nodes representation for parsing to SimulatedChannel
222-
let channels = sim_network
223-
.clone()
224-
.into_iter()
225-
.map(SimulatedChannel::from)
226-
.collect::<Vec<SimulatedChannel>>();
227-
228-
let mut nodes_info = HashMap::new();
229-
for sim_channel in sim_network {
230-
nodes_info.insert(
231-
sim_channel.node_1.pubkey,
232-
node_info(sim_channel.node_1.pubkey),
233-
);
234-
nodes_info.insert(
235-
sim_channel.node_2.pubkey,
236-
node_info(sim_channel.node_2.pubkey),
237-
);
275+
let SimParams {
276+
nodes,
277+
sim_network: _,
278+
activity,
279+
} = sim_params;
280+
281+
let (clients, clients_info) = get_clients(nodes.to_vec()).await?;
282+
// We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
283+
// nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
284+
let get_node = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
285+
if let Some(c) = clients.values().next() {
286+
return c.lock().await.get_node_info(pk).await;
238287
}
239-
let get_node_info = async |pk: &PublicKey| -> Result<NodeInfo, LightningError> {
240-
if let Some(node) = nodes_info.get(pk) {
241-
Ok(node_info(node.pubkey))
242-
} else {
243-
Err(LightningError::GetNodeInfoError(format!(
244-
"node not found in simulated network: {}",
245-
pk
246-
)))
247-
}
248-
};
249-
let (pk_node_map, alias_node_map) = add_node_to_maps(&nodes_info).await?;
250-
let validated_activities =
251-
validate_activities(activity, pk_node_map, alias_node_map, get_node_info).await?;
252-
let tasks = TaskTracker::new();
253-
let (simulation, graph) =
254-
Simulation::new_with_sim_network(cfg, channels, validated_activities, tasks).await?;
255-
Ok((simulation, Some(graph)))
256-
}
288+
Err(LightningError::GetNodeInfoError(
289+
"no nodes for query".to_string(),
290+
))
291+
};
292+
293+
let (pk_node_map, alias_node_map) = add_node_to_maps(&clients_info)?;
294+
let validated_activities =
295+
validate_activities(activity.to_vec(), pk_node_map, alias_node_map, get_node).await?;
296+
let (shutdown_trigger, shutdown_listener) = triggered::trigger();
297+
298+
Ok(Simulation::new(
299+
cfg,
300+
clients,
301+
validated_activities,
302+
tasks,
303+
shutdown_trigger,
304+
shutdown_listener,
305+
))
257306
}
258307

259308
/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and
@@ -289,11 +338,12 @@ async fn get_clients(
289338
Ok((clients, clients_info))
290339
}
291340

341+
type NodeMapping =
342+
Result<(HashMap<PublicKey, NodeInfo>, HashMap<String, NodeInfo>), LightningError>;
343+
292344
/// Adds a lightning node to a client map and tracking maps used to lookup node pubkeys and aliases for activity
293345
/// validation.
294-
async fn add_node_to_maps(
295-
nodes: &HashMap<PublicKey, NodeInfo>,
296-
) -> Result<(HashMap<PublicKey, NodeInfo>, HashMap<String, NodeInfo>), LightningError> {
346+
fn add_node_to_maps(nodes: &HashMap<PublicKey, NodeInfo>) -> NodeMapping {
297347
let mut pk_node_map = HashMap::new();
298348
let mut alias_node_map = HashMap::new();
299349

@@ -387,7 +437,7 @@ async fn validate_activities(
387437
Ok(validated_activities)
388438
}
389439

390-
async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> {
440+
pub async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> {
391441
if sim_file.exists() {
392442
Ok(sim_file)
393443
} else if sim_file.is_relative() {

0 commit comments

Comments
 (0)