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 ,  lnd:: LndNode ,  ActivityDefinition ,  LightningError ,  LightningNode ,  NodeConnection , 
8-     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,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