Skip to content

Commit cedd8c1

Browse files
rootroot
root
authored and
root
committed
refactor: move parsing into its own module
1 parent a1ca5ac commit cedd8c1

File tree

7 files changed

+478
-297
lines changed

7 files changed

+478
-297
lines changed

Cargo.lock

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

sim-cli/Cargo.toml

100644100755
+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ rand = "0.8.5"
2525
hex = {version = "0.4.3"}
2626
futures = "0.3.30"
2727
console-subscriber = { version = "0.4.0", optional = true}
28+
triggered = "0.1.2"
2829

2930
[features]
3031
dev = ["console-subscriber"]

sim-cli/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod parsing;

sim-cli/src/main.rs

100644100755
+7-255
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,8 @@
1-
use anyhow::anyhow;
2-
use bitcoin::secp256k1::PublicKey;
3-
use clap::builder::TypedValueParser;
4-
use clap::Parser;
51
use log::LevelFilter;
6-
use simln_lib::{
7-
cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection,
8-
NodeId, SimParams, Simulation, SimulationCfg, WriteResults,
9-
};
102
use simple_logger::SimpleLogger;
11-
use std::collections::HashMap;
12-
use std::path::PathBuf;
13-
use std::sync::Arc;
14-
use tokio::sync::Mutex;
15-
16-
/// The default directory where the simulation files are stored and where the results will be written to.
17-
pub const DEFAULT_DATA_DIR: &str = ".";
18-
19-
/// The default simulation file to be used by the simulator.
20-
pub const DEFAULT_SIM_FILE: &str = "sim.json";
21-
22-
/// The default expected payment amount for the simulation, around ~$10 at the time of writing.
23-
pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000;
24-
25-
/// The number of times over each node in the network sends its total deployed capacity in a calendar month.
26-
pub const DEFAULT_ACTIVITY_MULTIPLIER: f64 = 2.0;
27-
28-
/// Default batch size to flush result data to disk
29-
const DEFAULT_PRINT_BATCH_SIZE: u32 = 500;
30-
31-
/// Deserializes a f64 as long as it is positive and greater than 0.
32-
fn deserialize_f64_greater_than_zero(x: String) -> Result<f64, String> {
33-
match x.parse::<f64>() {
34-
Ok(x) => {
35-
if x > 0.0 {
36-
Ok(x)
37-
} else {
38-
Err(format!(
39-
"capacity_multiplier must be higher than 0. {x} received."
40-
))
41-
}
42-
},
43-
Err(e) => Err(e.to_string()),
44-
}
45-
}
3+
use sim_cli::parsing::{Cli, create_simulation};
4+
use clap::Parser;
465

47-
#[derive(Parser)]
48-
#[command(version, about)]
49-
struct Cli {
50-
/// Path to a directory containing simulation files, and where simulation results will be stored
51-
#[clap(long, short, default_value = DEFAULT_DATA_DIR)]
52-
data_dir: PathBuf,
53-
/// Path to the simulation file to be used by the simulator
54-
/// This can either be an absolute path, or relative path with respect to data_dir
55-
#[clap(long, short, default_value = DEFAULT_SIM_FILE)]
56-
sim_file: PathBuf,
57-
/// Total time the simulator will be running
58-
#[clap(long, short)]
59-
total_time: Option<u32>,
60-
/// Number of activity results to batch together before printing to csv file [min: 1]
61-
#[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::<u32>::new().range(1..u32::MAX as u64))]
62-
print_batch_size: u32,
63-
/// Level of verbosity of the messages displayed by the simulator.
64-
/// Possible values: [off, error, warn, info, debug, trace]
65-
#[clap(long, short, verbatim_doc_comment, default_value = "info")]
66-
log_level: LevelFilter,
67-
/// Expected payment amount for the random activity generator
68-
#[clap(long, short, default_value_t = DEFAULT_EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::<u64>::new().range(1..u64::MAX))]
69-
expected_pmt_amt: u64,
70-
/// Multiplier of the overall network capacity used by the random activity generator
71-
#[clap(long, short, default_value_t = DEFAULT_ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))]
72-
capacity_multiplier: f64,
73-
/// Do not create an output file containing the simulations results
74-
#[clap(long, default_value_t = false)]
75-
no_results: bool,
76-
/// Seed to run random activity generator deterministically
77-
#[clap(long, short)]
78-
fix_seed: Option<u64>,
79-
}
806

817
#[tokio::main]
828
async fn main() -> anyhow::Result<()> {
@@ -95,131 +21,7 @@ async fn main() -> anyhow::Result<()> {
9521
.init()
9622
.unwrap();
9723

98-
let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file).await?;
99-
let SimParams { nodes, activity } =
100-
serde_json::from_str(&std::fs::read_to_string(sim_path)?)
101-
.map_err(|e| anyhow!("Could not deserialize node connection data or activity description from simulation file (line {}, col {}).", e.line(), e.column()))?;
102-
103-
let mut clients: HashMap<PublicKey, Arc<Mutex<dyn LightningNode>>> = HashMap::new();
104-
let mut pk_node_map = HashMap::new();
105-
let mut alias_node_map = HashMap::new();
106-
107-
for connection in nodes {
108-
// TODO: Feels like there should be a better way of doing this without having to Arc<Mutex<T>>> it at this time.
109-
// Box sort of works, but we won't know the size of the dyn LightningNode at compile time so the compiler will
110-
// scream at us when trying to create the Arc<Mutex>> later on while adding the node to the clients map
111-
let node: Arc<Mutex<dyn LightningNode>> = match connection {
112-
NodeConnection::LND(c) => Arc::new(Mutex::new(LndNode::new(c).await?)),
113-
NodeConnection::CLN(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)),
114-
};
115-
116-
let node_info = node.lock().await.get_info().clone();
117-
118-
log::info!(
119-
"Connected to {} - Node ID: {}.",
120-
node_info.alias,
121-
node_info.pubkey
122-
);
123-
124-
if clients.contains_key(&node_info.pubkey) {
125-
anyhow::bail!(LightningError::ValidationError(format!(
126-
"duplicated node: {}.",
127-
node_info.pubkey
128-
)));
129-
}
130-
131-
if alias_node_map.contains_key(&node_info.alias) {
132-
anyhow::bail!(LightningError::ValidationError(format!(
133-
"duplicated node: {}.",
134-
node_info.alias
135-
)));
136-
}
137-
138-
clients.insert(node_info.pubkey, node);
139-
pk_node_map.insert(node_info.pubkey, node_info.clone());
140-
alias_node_map.insert(node_info.alias.clone(), node_info);
141-
}
142-
143-
let mut validated_activities = vec![];
144-
// Make all the activities identifiable by PK internally
145-
for act in activity.into_iter() {
146-
// We can only map aliases to nodes we control, so if either the source or destination alias
147-
// is not in alias_node_map, we fail
148-
let source = if let Some(source) = match &act.source {
149-
NodeId::PublicKey(pk) => pk_node_map.get(pk),
150-
NodeId::Alias(a) => alias_node_map.get(a),
151-
} {
152-
source.clone()
153-
} else {
154-
anyhow::bail!(LightningError::ValidationError(format!(
155-
"activity source {} not found in nodes.",
156-
act.source
157-
)));
158-
};
159-
160-
let destination = match &act.destination {
161-
NodeId::Alias(a) => {
162-
if let Some(info) = alias_node_map.get(a) {
163-
info.clone()
164-
} else {
165-
anyhow::bail!(LightningError::ValidationError(format!(
166-
"unknown activity destination: {}.",
167-
act.destination
168-
)));
169-
}
170-
},
171-
NodeId::PublicKey(pk) => {
172-
if let Some(info) = pk_node_map.get(pk) {
173-
info.clone()
174-
} else {
175-
clients
176-
.get(&source.pubkey)
177-
.unwrap()
178-
.lock()
179-
.await
180-
.get_node_info(pk)
181-
.await
182-
.map_err(|e| {
183-
log::debug!("{}", e);
184-
LightningError::ValidationError(format!(
185-
"Destination node unknown or invalid: {}.",
186-
pk,
187-
))
188-
})?
189-
}
190-
},
191-
};
192-
193-
validated_activities.push(ActivityDefinition {
194-
source,
195-
destination,
196-
start_secs: act.start_secs,
197-
count: act.count,
198-
interval_secs: act.interval_secs,
199-
amount_msat: act.amount_msat,
200-
});
201-
}
202-
203-
let write_results = if !cli.no_results {
204-
Some(WriteResults {
205-
results_dir: mkdir(cli.data_dir.join("results")).await?,
206-
batch_size: cli.print_batch_size,
207-
})
208-
} else {
209-
None
210-
};
211-
212-
let sim = Simulation::new(
213-
SimulationCfg::new(
214-
cli.total_time,
215-
cli.expected_pmt_amt,
216-
cli.capacity_multiplier,
217-
write_results,
218-
cli.fix_seed,
219-
),
220-
clients,
221-
validated_activities,
222-
);
24+
let (sim, sim_network) = create_simulation(&cli).await?;
22325
let sim2 = sim.clone();
22426

22527
ctrlc::set_handler(move || {
@@ -229,59 +31,9 @@ async fn main() -> anyhow::Result<()> {
22931

23032
sim.run().await?;
23133

232-
Ok(())
233-
}
234-
235-
async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> {
236-
if sim_file.exists() {
237-
Ok(sim_file)
238-
} else if sim_file.is_relative() {
239-
let sim_path = data_dir.join(sim_file);
240-
if sim_path.exists() {
241-
Ok(sim_path)
242-
} else {
243-
log::info!("Simulation file '{}' does not exist.", sim_path.display());
244-
select_sim_file(data_dir).await
245-
}
246-
} else {
247-
log::info!("Simulation file '{}' does not exist.", sim_file.display());
248-
select_sim_file(data_dir).await
249-
}
250-
}
251-
252-
async fn select_sim_file(data_dir: PathBuf) -> anyhow::Result<PathBuf> {
253-
let sim_files = std::fs::read_dir(data_dir.clone())?
254-
.filter_map(|f| {
255-
f.ok().and_then(|f| {
256-
if f.path().extension()?.to_str()? == "json" {
257-
f.file_name().into_string().ok()
258-
} else {
259-
None
260-
}
261-
})
262-
})
263-
.collect::<Vec<_>>();
264-
265-
if sim_files.is_empty() {
266-
anyhow::bail!(
267-
"no simulation files found in {}.",
268-
data_dir.canonicalize()?.display()
269-
);
34+
if let Some(network) = sim_network {
35+
network.lock().await.wait_for_shutdown().await;
27036
}
27137

272-
let selection = dialoguer::Select::new()
273-
.with_prompt(format!(
274-
"Select a simulation file. Found these in {}",
275-
data_dir.canonicalize()?.display()
276-
))
277-
.items(&sim_files)
278-
.default(0)
279-
.interact()?;
280-
281-
Ok(data_dir.join(sim_files[selection].clone()))
282-
}
283-
284-
async fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> {
285-
tokio::fs::create_dir_all(&dir).await?;
286-
Ok(dir)
287-
}
38+
Ok(())
39+
}

0 commit comments

Comments
 (0)