1- use anyhow:: anyhow;
2- use bitcoin:: secp256k1:: PublicKey ;
3- use clap:: builder:: TypedValueParser ;
41use clap:: Parser ;
52use log:: LevelFilter ;
6- use simln_lib:: {
7- cln:: ClnNode , eclair:: EclairNode , lnd:: LndNode , ActivityDefinition , LightningError ,
8- LightningNode , NodeConnection , NodeId , SimParams , Simulation , SimulationCfg , WriteResults ,
9- } ;
3+ use sim_cli:: parsing:: { create_simulation, Cli } ;
104use 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]
837async fn main ( ) -> anyhow:: Result < ( ) > {
@@ -96,134 +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- NodeConnection :: ECLAIR ( c) => Arc :: new ( Mutex :: new ( EclairNode :: new ( c) . await ?) ) ,
116- } ;
117-
118- let node_info = node. lock ( ) . await . get_info ( ) . clone ( ) ;
119-
120- log:: info!(
121- "Connected to {} - Node ID: {}." ,
122- node_info. alias,
123- node_info. pubkey
124- ) ;
125-
126- if clients. contains_key ( & node_info. pubkey ) {
127- anyhow:: bail!( LightningError :: ValidationError ( format!(
128- "duplicated node: {}." ,
129- node_info. pubkey
130- ) ) ) ;
131- }
132-
133- if alias_node_map. contains_key ( & node_info. alias ) {
134- anyhow:: bail!( LightningError :: ValidationError ( format!(
135- "duplicated node: {}." ,
136- node_info. alias
137- ) ) ) ;
138- }
139-
140- clients. insert ( node_info. pubkey , node) ;
141- pk_node_map. insert ( node_info. pubkey , node_info. clone ( ) ) ;
142- alias_node_map. insert ( node_info. alias . clone ( ) , node_info) ;
143- }
144-
145- let mut validated_activities = vec ! [ ] ;
146- // Make all the activities identifiable by PK internally
147- for act in activity. into_iter ( ) {
148- // We can only map aliases to nodes we control, so if either the source or destination alias
149- // is not in alias_node_map, we fail
150- let source = if let Some ( source) = match & act. source {
151- NodeId :: PublicKey ( pk) => pk_node_map. get ( pk) ,
152- NodeId :: Alias ( a) => alias_node_map. get ( a) ,
153- } {
154- source. clone ( )
155- } else {
156- anyhow:: bail!( LightningError :: ValidationError ( format!(
157- "activity source {} not found in nodes." ,
158- act. source
159- ) ) ) ;
160- } ;
161-
162- let destination = match & act. destination {
163- NodeId :: Alias ( a) => {
164- if let Some ( info) = alias_node_map. get ( a) {
165- info. clone ( )
166- } else {
167- anyhow:: bail!( LightningError :: ValidationError ( format!(
168- "unknown activity destination: {}." ,
169- act. destination
170- ) ) ) ;
171- }
172- } ,
173- NodeId :: PublicKey ( pk) => {
174- if let Some ( info) = pk_node_map. get ( pk) {
175- info. clone ( )
176- } else {
177- clients
178- . get ( & source. pubkey )
179- . unwrap ( )
180- . lock ( )
181- . await
182- . get_node_info ( pk)
183- . await
184- . map_err ( |e| {
185- log:: debug!( "{}" , e) ;
186- LightningError :: ValidationError ( format ! (
187- "Destination node unknown or invalid: {}." ,
188- pk,
189- ) )
190- } ) ?
191- }
192- } ,
193- } ;
194-
195- validated_activities. push ( ActivityDefinition {
196- source,
197- destination,
198- start_secs : act. start_secs ,
199- count : act. count ,
200- interval_secs : act. interval_secs ,
201- amount_msat : act. amount_msat ,
202- } ) ;
203- }
204-
205- let write_results = if !cli. no_results {
206- Some ( WriteResults {
207- results_dir : mkdir ( cli. data_dir . join ( "results" ) ) . await ?,
208- batch_size : cli. print_batch_size ,
209- } )
210- } else {
211- None
212- } ;
213-
214- let tasks = TaskTracker :: new ( ) ;
215- let sim = Simulation :: new (
216- SimulationCfg :: new (
217- cli. total_time ,
218- cli. expected_pmt_amt ,
219- cli. capacity_multiplier ,
220- write_results,
221- cli. fix_seed ,
222- ) ,
223- clients,
224- validated_activities,
225- tasks,
226- ) ;
23+ let sim = create_simulation ( & cli) . await ?;
22724 let sim2 = sim. clone ( ) ;
22825
22926 ctrlc:: set_handler ( move || {
@@ -235,57 +32,3 @@ async fn main() -> anyhow::Result<()> {
23532
23633 Ok ( ( ) )
23734}
238-
239- async fn read_sim_path ( data_dir : PathBuf , sim_file : PathBuf ) -> anyhow:: Result < PathBuf > {
240- if sim_file. exists ( ) {
241- Ok ( sim_file)
242- } else if sim_file. is_relative ( ) {
243- let sim_path = data_dir. join ( sim_file) ;
244- if sim_path. exists ( ) {
245- Ok ( sim_path)
246- } else {
247- log:: info!( "Simulation file '{}' does not exist." , sim_path. display( ) ) ;
248- select_sim_file ( data_dir) . await
249- }
250- } else {
251- log:: info!( "Simulation file '{}' does not exist." , sim_file. display( ) ) ;
252- select_sim_file ( data_dir) . await
253- }
254- }
255-
256- async fn select_sim_file ( data_dir : PathBuf ) -> anyhow:: Result < PathBuf > {
257- let sim_files = std:: fs:: read_dir ( data_dir. clone ( ) ) ?
258- . filter_map ( |f| {
259- f. ok ( ) . and_then ( |f| {
260- if f. path ( ) . extension ( ) ?. to_str ( ) ? == "json" {
261- f. file_name ( ) . into_string ( ) . ok ( )
262- } else {
263- None
264- }
265- } )
266- } )
267- . collect :: < Vec < _ > > ( ) ;
268-
269- if sim_files. is_empty ( ) {
270- anyhow:: bail!(
271- "no simulation files found in {}." ,
272- data_dir. canonicalize( ) ?. display( )
273- ) ;
274- }
275-
276- let selection = dialoguer:: Select :: new ( )
277- . with_prompt ( format ! (
278- "Select a simulation file. Found these in {}" ,
279- data_dir. canonicalize( ) ?. display( )
280- ) )
281- . items ( & sim_files)
282- . default ( 0 )
283- . interact ( ) ?;
284-
285- Ok ( data_dir. join ( sim_files[ selection] . clone ( ) ) )
286- }
287-
288- async fn mkdir ( dir : PathBuf ) -> anyhow:: Result < PathBuf > {
289- tokio:: fs:: create_dir_all ( & dir) . await ?;
290- Ok ( dir)
291- }
0 commit comments