From 6b1b530c4baebc640f6849b5c73e4228e06d213e Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Mar 2025 11:50:16 +0100 Subject: [PATCH] sim-ln/refactor: move parsing into its own module --- .github/workflows/build-and-test.yml | 2 +- Cargo.lock | 0 sim-cli/Cargo.toml | 0 sim-cli/src/lib.rs | 1 + sim-cli/src/main.rs | 261 +------------------ sim-cli/src/parsing.rs | 361 +++++++++++++++++++++++++++ simln-lib/Cargo.toml | 0 simln-lib/src/lib.rs | 44 +--- simln-lib/src/sim_node.rs | 0 9 files changed, 368 insertions(+), 301 deletions(-) mode change 100644 => 100755 .github/workflows/build-and-test.yml mode change 100644 => 100755 Cargo.lock mode change 100644 => 100755 sim-cli/Cargo.toml create mode 100755 sim-cli/src/lib.rs mode change 100644 => 100755 sim-cli/src/main.rs create mode 100755 sim-cli/src/parsing.rs mode change 100644 => 100755 simln-lib/Cargo.toml mode change 100644 => 100755 simln-lib/src/lib.rs mode change 100644 => 100755 simln-lib/src/sim_node.rs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml old mode 100644 new mode 100755 index 6b2aa215..7da5f19f --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -13,7 +13,7 @@ jobs: - name: Install protoc run: sudo apt install -y protobuf-compiler - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.84.0 + - uses: dtolnay/rust-toolchain@1.85.0 - name: Install Clippy run: rustup component add clippy - name: Install Rustfmt diff --git a/Cargo.lock b/Cargo.lock old mode 100644 new mode 100755 diff --git a/sim-cli/Cargo.toml b/sim-cli/Cargo.toml old mode 100644 new mode 100755 diff --git a/sim-cli/src/lib.rs b/sim-cli/src/lib.rs new file mode 100755 index 00000000..29ec0bad --- /dev/null +++ b/sim-cli/src/lib.rs @@ -0,0 +1 @@ +pub mod parsing; diff --git a/sim-cli/src/main.rs b/sim-cli/src/main.rs old mode 100644 new mode 100755 index 76d6866b..8ae8477f --- a/sim-cli/src/main.rs +++ b/sim-cli/src/main.rs @@ -1,83 +1,7 @@ -use anyhow::anyhow; -use bitcoin::secp256k1::PublicKey; -use clap::builder::TypedValueParser; use clap::Parser; use log::LevelFilter; -use simln_lib::{ - cln::ClnNode, eclair::EclairNode, lnd::LndNode, ActivityDefinition, LightningError, - LightningNode, NodeConnection, NodeId, SimParams, Simulation, SimulationCfg, WriteResults, -}; +use sim_cli::parsing::{create_simulation, Cli}; use simple_logger::SimpleLogger; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::Mutex; -use tokio_util::task::TaskTracker; - -/// The default directory where the simulation files are stored and where the results will be written to. -pub const DEFAULT_DATA_DIR: &str = "."; - -/// The default simulation file to be used by the simulator. -pub const DEFAULT_SIM_FILE: &str = "sim.json"; - -/// The default expected payment amount for the simulation, around ~$10 at the time of writing. -pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000; - -/// The number of times over each node in the network sends its total deployed capacity in a calendar month. -pub const DEFAULT_ACTIVITY_MULTIPLIER: f64 = 2.0; - -/// Default batch size to flush result data to disk -const DEFAULT_PRINT_BATCH_SIZE: u32 = 500; - -/// Deserializes a f64 as long as it is positive and greater than 0. -fn deserialize_f64_greater_than_zero(x: String) -> Result { - match x.parse::() { - Ok(x) => { - if x > 0.0 { - Ok(x) - } else { - Err(format!( - "capacity_multiplier must be higher than 0. {x} received." - )) - } - }, - Err(e) => Err(e.to_string()), - } -} - -#[derive(Parser)] -#[command(version, about)] -struct Cli { - /// Path to a directory containing simulation files, and where simulation results will be stored - #[clap(long, short, default_value = DEFAULT_DATA_DIR)] - data_dir: PathBuf, - /// Path to the simulation file to be used by the simulator - /// This can either be an absolute path, or relative path with respect to data_dir - #[clap(long, short, default_value = DEFAULT_SIM_FILE)] - sim_file: PathBuf, - /// Total time the simulator will be running - #[clap(long, short)] - total_time: Option, - /// Number of activity results to batch together before printing to csv file [min: 1] - #[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64))] - print_batch_size: u32, - /// Level of verbosity of the messages displayed by the simulator. - /// Possible values: [off, error, warn, info, debug, trace] - #[clap(long, short, verbatim_doc_comment, default_value = "info")] - log_level: LevelFilter, - /// Expected payment amount for the random activity generator - #[clap(long, short, default_value_t = DEFAULT_EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u64::MAX))] - expected_pmt_amt: u64, - /// Multiplier of the overall network capacity used by the random activity generator - #[clap(long, short, default_value_t = DEFAULT_ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))] - capacity_multiplier: f64, - /// Do not create an output file containing the simulations results - #[clap(long, default_value_t = false)] - no_results: bool, - /// Seed to run random activity generator deterministically - #[clap(long, short)] - fix_seed: Option, -} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -96,134 +20,7 @@ async fn main() -> anyhow::Result<()> { .init() .unwrap(); - let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file).await?; - let SimParams { nodes, activity } = - serde_json::from_str(&std::fs::read_to_string(sim_path)?) - .map_err(|e| anyhow!("Could not deserialize node connection data or activity description from simulation file (line {}, col {}).", e.line(), e.column()))?; - - let mut clients: HashMap>> = HashMap::new(); - let mut pk_node_map = HashMap::new(); - let mut alias_node_map = HashMap::new(); - - for connection in nodes { - // TODO: Feels like there should be a better way of doing this without having to Arc>> it at this time. - // Box sort of works, but we won't know the size of the dyn LightningNode at compile time so the compiler will - // scream at us when trying to create the Arc> later on while adding the node to the clients map - let node: Arc> = match connection { - NodeConnection::LND(c) => Arc::new(Mutex::new(LndNode::new(c).await?)), - NodeConnection::CLN(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)), - NodeConnection::ECLAIR(c) => Arc::new(Mutex::new(EclairNode::new(c).await?)), - }; - - let node_info = node.lock().await.get_info().clone(); - - log::info!( - "Connected to {} - Node ID: {}.", - node_info.alias, - node_info.pubkey - ); - - if clients.contains_key(&node_info.pubkey) { - anyhow::bail!(LightningError::ValidationError(format!( - "duplicated node: {}.", - node_info.pubkey - ))); - } - - if alias_node_map.contains_key(&node_info.alias) { - anyhow::bail!(LightningError::ValidationError(format!( - "duplicated node: {}.", - node_info.alias - ))); - } - - clients.insert(node_info.pubkey, node); - pk_node_map.insert(node_info.pubkey, node_info.clone()); - alias_node_map.insert(node_info.alias.clone(), node_info); - } - - let mut validated_activities = vec![]; - // Make all the activities identifiable by PK internally - for act in activity.into_iter() { - // We can only map aliases to nodes we control, so if either the source or destination alias - // is not in alias_node_map, we fail - let source = if let Some(source) = match &act.source { - NodeId::PublicKey(pk) => pk_node_map.get(pk), - NodeId::Alias(a) => alias_node_map.get(a), - } { - source.clone() - } else { - anyhow::bail!(LightningError::ValidationError(format!( - "activity source {} not found in nodes.", - act.source - ))); - }; - - let destination = match &act.destination { - NodeId::Alias(a) => { - if let Some(info) = alias_node_map.get(a) { - info.clone() - } else { - anyhow::bail!(LightningError::ValidationError(format!( - "unknown activity destination: {}.", - act.destination - ))); - } - }, - NodeId::PublicKey(pk) => { - if let Some(info) = pk_node_map.get(pk) { - info.clone() - } else { - clients - .get(&source.pubkey) - .unwrap() - .lock() - .await - .get_node_info(pk) - .await - .map_err(|e| { - log::debug!("{}", e); - LightningError::ValidationError(format!( - "Destination node unknown or invalid: {}.", - pk, - )) - })? - } - }, - }; - - validated_activities.push(ActivityDefinition { - source, - destination, - start_secs: act.start_secs, - count: act.count, - interval_secs: act.interval_secs, - amount_msat: act.amount_msat, - }); - } - - let write_results = if !cli.no_results { - Some(WriteResults { - results_dir: mkdir(cli.data_dir.join("results")).await?, - batch_size: cli.print_batch_size, - }) - } else { - None - }; - - let tasks = TaskTracker::new(); - let sim = Simulation::new( - SimulationCfg::new( - cli.total_time, - cli.expected_pmt_amt, - cli.capacity_multiplier, - write_results, - cli.fix_seed, - ), - clients, - validated_activities, - tasks, - ); + let sim = create_simulation(&cli).await?; let sim2 = sim.clone(); ctrlc::set_handler(move || { @@ -235,57 +32,3 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result { - if sim_file.exists() { - Ok(sim_file) - } else if sim_file.is_relative() { - let sim_path = data_dir.join(sim_file); - if sim_path.exists() { - Ok(sim_path) - } else { - log::info!("Simulation file '{}' does not exist.", sim_path.display()); - select_sim_file(data_dir).await - } - } else { - log::info!("Simulation file '{}' does not exist.", sim_file.display()); - select_sim_file(data_dir).await - } -} - -async fn select_sim_file(data_dir: PathBuf) -> anyhow::Result { - let sim_files = std::fs::read_dir(data_dir.clone())? - .filter_map(|f| { - f.ok().and_then(|f| { - if f.path().extension()?.to_str()? == "json" { - f.file_name().into_string().ok() - } else { - None - } - }) - }) - .collect::>(); - - if sim_files.is_empty() { - anyhow::bail!( - "no simulation files found in {}.", - data_dir.canonicalize()?.display() - ); - } - - let selection = dialoguer::Select::new() - .with_prompt(format!( - "Select a simulation file. Found these in {}", - data_dir.canonicalize()?.display() - )) - .items(&sim_files) - .default(0) - .interact()?; - - Ok(data_dir.join(sim_files[selection].clone())) -} - -async fn mkdir(dir: PathBuf) -> anyhow::Result { - tokio::fs::create_dir_all(&dir).await?; - Ok(dir) -} diff --git a/sim-cli/src/parsing.rs b/sim-cli/src/parsing.rs new file mode 100755 index 00000000..bfa05449 --- /dev/null +++ b/sim-cli/src/parsing.rs @@ -0,0 +1,361 @@ +use anyhow::anyhow; +use bitcoin::secp256k1::PublicKey; +use clap::{builder::TypedValueParser, Parser}; +use log::LevelFilter; +use serde::{Deserialize, Serialize}; +use simln_lib::{ + cln, cln::ClnNode, eclair, eclair::EclairNode, lnd, lnd::LndNode, serializers, + ActivityDefinition, Amount, Interval, LightningError, LightningNode, NodeId, NodeInfo, + Simulation, SimulationCfg, WriteResults, +}; +use std::collections::HashMap; +use std::fs; +use std::ops::AsyncFn; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio_util::task::TaskTracker; + +/// The default directory where the simulation files are stored and where the results will be written to. +pub const DEFAULT_DATA_DIR: &str = "."; + +/// The default simulation file to be used by the simulator. +pub const DEFAULT_SIM_FILE: &str = "sim.json"; + +/// The default expected payment amount for the simulation, around ~$10 at the time of writing. +pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000; + +/// The number of times over each node in the network sends its total deployed capacity in a calendar month. +pub const DEFAULT_ACTIVITY_MULTIPLIER: f64 = 2.0; + +/// Default batch size to flush result data to disk +const DEFAULT_PRINT_BATCH_SIZE: u32 = 500; + +/// Deserializes a f64 as long as it is positive and greater than 0. +fn deserialize_f64_greater_than_zero(x: String) -> Result { + match x.parse::() { + Ok(x) => { + if x > 0.0 { + Ok(x) + } else { + Err(format!( + "capacity_multiplier must be higher than 0. {x} received." + )) + } + }, + Err(e) => Err(e.to_string()), + } +} + +#[derive(Parser)] +#[command(version, about)] +pub struct Cli { + /// Path to a directory containing simulation files, and where simulation results will be stored + #[clap(long, short, default_value = DEFAULT_DATA_DIR)] + pub data_dir: PathBuf, + /// Path to the simulation file to be used by the simulator + /// This can either be an absolute path, or relative path with respect to data_dir + #[clap(long, short, default_value = DEFAULT_SIM_FILE)] + pub sim_file: PathBuf, + /// Total time the simulator will be running + #[clap(long, short)] + pub total_time: Option, + /// Number of activity results to batch together before printing to csv file [min: 1] + #[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64))] + pub print_batch_size: u32, + /// Level of verbosity of the messages displayed by the simulator. + /// Possible values: [off, error, warn, info, debug, trace] + #[clap(long, short, verbatim_doc_comment, default_value = "info")] + pub log_level: LevelFilter, + /// Expected payment amount for the random activity generator + #[clap(long, short, default_value_t = DEFAULT_EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u64::MAX))] + pub expected_pmt_amt: u64, + /// Multiplier of the overall network capacity used by the random activity generator + #[clap(long, short, default_value_t = DEFAULT_ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))] + pub capacity_multiplier: f64, + /// Do not create an output file containing the simulations results + #[clap(long, default_value_t = false)] + pub no_results: bool, + /// Seed to run random activity generator deterministically + #[clap(long, short)] + pub fix_seed: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct SimParams { + pub nodes: Vec, + #[serde(default)] + pub activity: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +enum NodeConnection { + Lnd(lnd::LndConnection), + Cln(cln::ClnConnection), + Eclair(eclair::EclairConnection), +} + +/// Data structure used to parse information from the simulation file. It allows source and destination to be +/// [NodeId], which enables the use of public keys and aliases in the simulation description. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ActivityParser { + /// The source of the payment. + #[serde(with = "serializers::serde_node_id")] + pub source: NodeId, + /// The destination of the payment. + #[serde(with = "serializers::serde_node_id")] + pub destination: NodeId, + /// The time in the simulation to start the payment. + pub start_secs: Option, + /// The number of payments to send over the course of the simulation. + #[serde(default)] + pub count: Option, + /// The interval of the event, as in every how many seconds the payment is performed. + #[serde(with = "serializers::serde_value_or_range")] + pub interval_secs: Interval, + /// The amount of m_sat to used in this payment. + #[serde(with = "serializers::serde_value_or_range")] + pub amount_msat: Amount, +} + +impl TryFrom<&Cli> for SimulationCfg { + type Error = anyhow::Error; + + fn try_from(cli: &Cli) -> Result { + Ok(SimulationCfg::new( + cli.total_time, + cli.expected_pmt_amt, + cli.capacity_multiplier, + if !cli.no_results { + Some(WriteResults { + results_dir: mkdir(cli.data_dir.join("results"))?, + batch_size: cli.print_batch_size, + }) + } else { + None + }, + cli.fix_seed, + )) + } +} + +/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating +/// any activity described in the simulation file. +pub async fn create_simulation(cli: &Cli) -> Result { + let cfg: SimulationCfg = SimulationCfg::try_from(cli)?; + + let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file.clone()).await?; + let SimParams { nodes, activity } = serde_json::from_str(&std::fs::read_to_string(sim_path)?) + .map_err(|e| { + anyhow!( + "Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {}).", + e.line(), + e.column(), + e.to_string() + ) + })?; + + let (clients, clients_info) = get_clients(nodes).await?; + // We need to be able to look up destination nodes in the graph, because we allow defined activities to send to + // nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup. + let get_node = async |pk: &PublicKey| -> Result { + if let Some(c) = clients.values().next() { + return c.lock().await.get_node_info(pk).await; + } + + Err(LightningError::GetNodeInfoError( + "no nodes for query".to_string(), + )) + }; + + let validated_activities = validate_activities(activity, &clients_info, get_node).await?; + let tasks = TaskTracker::new(); + + Ok(Simulation::new(cfg, clients, validated_activities, tasks)) +} + +/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and +/// a map of public key to node info to be used for validation. +async fn get_clients( + nodes: Vec, +) -> Result< + ( + HashMap>>, + HashMap, + ), + LightningError, +> { + let mut clients: HashMap>> = HashMap::new(); + let mut clients_info: HashMap = HashMap::new(); + + for connection in nodes { + // TODO: Feels like there should be a better way of doing this without having to Arc>> it at this time. + // Box sort of works, but we won't know the size of the dyn LightningNode at compile time so the compiler will + // scream at us when trying to create the Arc> later on while adding the node to the clients map + let node: Arc> = match connection { + NodeConnection::Lnd(c) => Arc::new(Mutex::new(LndNode::new(c).await?)), + NodeConnection::Cln(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)), + NodeConnection::Eclair(c) => Arc::new(Mutex::new(EclairNode::new(c).await?)), + }; + + let node_info = node.lock().await.get_info().clone(); + + clients.insert(node_info.pubkey, node); + clients_info.insert(node_info.pubkey, node_info); + } + + Ok((clients, clients_info)) +} + +/// Adds a lightning node to a client map and tracking maps used to lookup node pubkeys and aliases for activity +/// validation. +async fn add_node_to_maps( + nodes: &HashMap, +) -> Result<(HashMap, HashMap), LightningError> { + let mut pk_node_map = HashMap::new(); + let mut alias_node_map = HashMap::new(); + + for node_info in nodes.values() { + log::info!( + "Connected to {} - Node ID: {}.", + node_info.alias, + node_info.pubkey + ); + + if pk_node_map.contains_key(&node_info.pubkey) { + return Err(LightningError::ValidationError(format!( + "duplicated node: {}.", + node_info.pubkey + ))); + } + + if !node_info.alias.is_empty() { + if alias_node_map.contains_key(&node_info.alias) { + return Err(LightningError::ValidationError(format!( + "duplicated alias: {}.", + node_info.alias + ))); + } + + alias_node_map.insert(node_info.alias.clone(), node_info.clone()); + } + + pk_node_map.insert(node_info.pubkey, node_info.clone()); + } + + Ok((pk_node_map, alias_node_map)) +} + +/// Validates a set of defined activities, cross-checking aliases and public keys against the set of clients that +/// have been configured. +async fn validate_activities( + activity: Vec, + nodes: &HashMap, + get_node_info: impl AsyncFn(&PublicKey) -> Result, +) -> Result, LightningError> { + let mut validated_activities = vec![]; + let (pk_node_map, alias_node_map) = add_node_to_maps(nodes).await?; + + // Make all the activities identifiable by PK internally + for act in activity.into_iter() { + // We can only map aliases to nodes we control, so if either the source or destination alias + // is not in alias_node_map, we fail + let source = if let Some(source) = match &act.source { + NodeId::PublicKey(pk) => pk_node_map.get(pk), + NodeId::Alias(a) => alias_node_map.get(a), + } { + source.clone() + } else { + return Err(LightningError::ValidationError(format!( + "activity source {} not found in nodes.", + act.source + ))); + }; + + let destination = match &act.destination { + NodeId::Alias(a) => { + if let Some(info) = alias_node_map.get(a) { + info.clone() + } else { + return Err(LightningError::ValidationError(format!( + "unknown activity destination: {}.", + act.destination + ))); + } + }, + NodeId::PublicKey(pk) => { + if let Some(info) = pk_node_map.get(pk) { + info.clone() + } else { + get_node_info(pk).await? + } + }, + }; + + validated_activities.push(ActivityDefinition { + source, + destination, + interval_secs: act.interval_secs, + start_secs: act.start_secs, + count: act.count, + amount_msat: act.amount_msat, + }); + } + + Ok(validated_activities) +} + +async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result { + if sim_file.exists() { + Ok(sim_file) + } else if sim_file.is_relative() { + let sim_path = data_dir.join(sim_file); + if sim_path.exists() { + Ok(sim_path) + } else { + log::info!("Simulation file '{}' does not exist.", sim_path.display()); + select_sim_file(data_dir).await + } + } else { + log::info!("Simulation file '{}' does not exist.", sim_file.display()); + select_sim_file(data_dir).await + } +} + +pub async fn select_sim_file(data_dir: PathBuf) -> anyhow::Result { + let sim_files = std::fs::read_dir(data_dir.clone())? + .filter_map(|f| { + f.ok().and_then(|f| { + if f.path().extension()?.to_str()? == "json" { + f.file_name().into_string().ok() + } else { + None + } + }) + }) + .collect::>(); + + if sim_files.is_empty() { + anyhow::bail!( + "no simulation files found in {}.", + data_dir.canonicalize()?.display() + ); + } + + let selection = dialoguer::Select::new() + .with_prompt(format!( + "Select a simulation file. Found these in {}", + data_dir.canonicalize()?.display() + )) + .items(&sim_files) + .default(0) + .interact()?; + + Ok(data_dir.join(sim_files[selection].clone())) +} + +fn mkdir(dir: PathBuf) -> anyhow::Result { + fs::create_dir_all(&dir)?; + Ok(dir) +} diff --git a/simln-lib/Cargo.toml b/simln-lib/Cargo.toml old mode 100644 new mode 100755 diff --git a/simln-lib/src/lib.rs b/simln-lib/src/lib.rs old mode 100644 new mode 100755 index f915940e..a83b6231 --- a/simln-lib/src/lib.rs +++ b/simln-lib/src/lib.rs @@ -31,19 +31,11 @@ mod defined_activity; pub mod eclair; pub mod lnd; mod random_activity; -mod serializers; +pub mod serializers; pub mod sim_node; #[cfg(test)] mod test_utils; -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum NodeConnection { - LND(lnd::LndConnection), - CLN(cln::ClnConnection), - ECLAIR(eclair::EclairConnection), -} - #[derive(Serialize, Debug, Clone)] pub enum NodeId { PublicKey(PublicKey), @@ -128,13 +120,6 @@ impl std::fmt::Display for ShortChannelID { } } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct SimParams { - pub nodes: Vec, - #[serde(default)] - pub activity: Vec, -} - /// Either a value or a range parsed from the simulation file. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(untagged)] @@ -172,32 +157,9 @@ where } /// The payment amount in msat. Either a value or a range. -type Amount = ValueOrRange; +pub type Amount = ValueOrRange; /// The interval of seconds between payments. Either a value or a range. -type Interval = ValueOrRange; - -/// Data structure used to parse information from the simulation file. It allows source and destination to be -/// [NodeId], which enables the use of public keys and aliases in the simulation description. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ActivityParser { - /// The source of the payment. - #[serde(with = "serializers::serde_node_id")] - pub source: NodeId, - /// The destination of the payment. - #[serde(with = "serializers::serde_node_id")] - pub destination: NodeId, - /// The time in the simulation to start the payment. - pub start_secs: Option, - /// The number of payments to send over the course of the simulation. - #[serde(default)] - pub count: Option, - /// The interval of the event, as in every how many seconds the payment is performed. - #[serde(with = "serializers::serde_value_or_range")] - pub interval_secs: Interval, - /// The amount of m_sat to used in this payment. - #[serde(with = "serializers::serde_value_or_range")] - pub amount_msat: Amount, -} +pub type Interval = ValueOrRange; /// Data structure used internally by the simulator. Both source and destination are represented as [PublicKey] here. /// This is constructed during activity validation and passed along to the [Simulation]. diff --git a/simln-lib/src/sim_node.rs b/simln-lib/src/sim_node.rs old mode 100644 new mode 100755