1- use anyhow:: anyhow;
2- use bitcoin:: secp256k1:: PublicKey ;
3- use clap:: builder:: TypedValueParser ;
4- use clap:: Parser ;
51use log:: LevelFilter ;
6- use simln_lib:: {
7- cln:: ClnNode , lnd:: LndNode , ActivityDefinition , LightningError , LightningNode , NodeConnection ,
8- NodeId , SimParams , Simulation , SimulationCfg , WriteResults ,
9- } ;
102use 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]
828async 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