Skip to content

Commit c99b10b

Browse files
rootroot
root
authored and
root
committed
sim-ln/refactor: move parsing into its own module
1 parent a8b1e0b commit c99b10b

File tree

8 files changed

+368
-301
lines changed

8 files changed

+368
-301
lines changed

Cargo.lock

100644100755
File mode changed.

sim-cli/Cargo.toml

100644100755
File mode changed.

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
+2-258
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,7 @@
1-
use anyhow::anyhow;
2-
use bitcoin::secp256k1::PublicKey;
3-
use clap::builder::TypedValueParser;
41
use clap::Parser;
52
use log::LevelFilter;
6-
use simln_lib::{
7-
cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection,
8-
NodeId, SimParams, Simulation, SimulationCfg, WriteResults,
9-
};
3+
use sim_cli::parsing::{create_simulation, Cli};
104
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-
use tokio_util::task::TaskTracker;
16-
17-
/// The default directory where the simulation files are stored and where the results will be written to.
18-
pub const DEFAULT_DATA_DIR: &str = ".";
19-
20-
/// The default simulation file to be used by the simulator.
21-
pub const DEFAULT_SIM_FILE: &str = "sim.json";
22-
23-
/// The default expected payment amount for the simulation, around ~$10 at the time of writing.
24-
pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000;
25-
26-
/// The number of times over each node in the network sends its total deployed capacity in a calendar month.
27-
pub const DEFAULT_ACTIVITY_MULTIPLIER: f64 = 2.0;
28-
29-
/// Default batch size to flush result data to disk
30-
const DEFAULT_PRINT_BATCH_SIZE: u32 = 500;
31-
32-
/// Deserializes a f64 as long as it is positive and greater than 0.
33-
fn deserialize_f64_greater_than_zero(x: String) -> Result<f64, String> {
34-
match x.parse::<f64>() {
35-
Ok(x) => {
36-
if x > 0.0 {
37-
Ok(x)
38-
} else {
39-
Err(format!(
40-
"capacity_multiplier must be higher than 0. {x} received."
41-
))
42-
}
43-
},
44-
Err(e) => Err(e.to_string()),
45-
}
46-
}
47-
48-
#[derive(Parser)]
49-
#[command(version, about)]
50-
struct Cli {
51-
/// Path to a directory containing simulation files, and where simulation results will be stored
52-
#[clap(long, short, default_value = DEFAULT_DATA_DIR)]
53-
data_dir: PathBuf,
54-
/// Path to the simulation file to be used by the simulator
55-
/// This can either be an absolute path, or relative path with respect to data_dir
56-
#[clap(long, short, default_value = DEFAULT_SIM_FILE)]
57-
sim_file: PathBuf,
58-
/// Total time the simulator will be running
59-
#[clap(long, short)]
60-
total_time: Option<u32>,
61-
/// Number of activity results to batch together before printing to csv file [min: 1]
62-
#[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::<u32>::new().range(1..u32::MAX as u64))]
63-
print_batch_size: u32,
64-
/// Level of verbosity of the messages displayed by the simulator.
65-
/// Possible values: [off, error, warn, info, debug, trace]
66-
#[clap(long, short, verbatim_doc_comment, default_value = "info")]
67-
log_level: LevelFilter,
68-
/// Expected payment amount for the random activity generator
69-
#[clap(long, short, default_value_t = DEFAULT_EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::<u64>::new().range(1..u64::MAX))]
70-
expected_pmt_amt: u64,
71-
/// Multiplier of the overall network capacity used by the random activity generator
72-
#[clap(long, short, default_value_t = DEFAULT_ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))]
73-
capacity_multiplier: f64,
74-
/// Do not create an output file containing the simulations results
75-
#[clap(long, default_value_t = false)]
76-
no_results: bool,
77-
/// Seed to run random activity generator deterministically
78-
#[clap(long, short)]
79-
fix_seed: Option<u64>,
80-
}
815

826
#[tokio::main]
837
async fn main() -> anyhow::Result<()> {
@@ -96,133 +20,7 @@ async fn main() -> anyhow::Result<()> {
9620
.init()
9721
.unwrap();
9822

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

22826
ctrlc::set_handler(move || {
@@ -234,57 +32,3 @@ async fn main() -> anyhow::Result<()> {
23432

23533
Ok(())
23634
}
237-
238-
async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> {
239-
if sim_file.exists() {
240-
Ok(sim_file)
241-
} else if sim_file.is_relative() {
242-
let sim_path = data_dir.join(sim_file);
243-
if sim_path.exists() {
244-
Ok(sim_path)
245-
} else {
246-
log::info!("Simulation file '{}' does not exist.", sim_path.display());
247-
select_sim_file(data_dir).await
248-
}
249-
} else {
250-
log::info!("Simulation file '{}' does not exist.", sim_file.display());
251-
select_sim_file(data_dir).await
252-
}
253-
}
254-
255-
async fn select_sim_file(data_dir: PathBuf) -> anyhow::Result<PathBuf> {
256-
let sim_files = std::fs::read_dir(data_dir.clone())?
257-
.filter_map(|f| {
258-
f.ok().and_then(|f| {
259-
if f.path().extension()?.to_str()? == "json" {
260-
f.file_name().into_string().ok()
261-
} else {
262-
None
263-
}
264-
})
265-
})
266-
.collect::<Vec<_>>();
267-
268-
if sim_files.is_empty() {
269-
anyhow::bail!(
270-
"no simulation files found in {}.",
271-
data_dir.canonicalize()?.display()
272-
);
273-
}
274-
275-
let selection = dialoguer::Select::new()
276-
.with_prompt(format!(
277-
"Select a simulation file. Found these in {}",
278-
data_dir.canonicalize()?.display()
279-
))
280-
.items(&sim_files)
281-
.default(0)
282-
.interact()?;
283-
284-
Ok(data_dir.join(sim_files[selection].clone()))
285-
}
286-
287-
async fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> {
288-
tokio::fs::create_dir_all(&dir).await?;
289-
Ok(dir)
290-
}

0 commit comments

Comments
 (0)