1
- use anyhow:: anyhow;
2
- use bitcoin:: secp256k1:: PublicKey ;
3
- use clap:: builder:: TypedValueParser ;
4
1
use clap:: Parser ;
5
2
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 } ;
10
4
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
- }
81
5
82
6
#[ tokio:: main]
83
7
async fn main ( ) -> anyhow:: Result < ( ) > {
@@ -96,133 +20,7 @@ async fn main() -> anyhow::Result<()> {
96
20
. init ( )
97
21
. unwrap ( ) ;
98
22
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 ?;
226
24
let sim2 = sim. clone ( ) ;
227
25
228
26
ctrlc:: set_handler ( move || {
@@ -234,57 +32,3 @@ async fn main() -> anyhow::Result<()> {
234
32
235
33
Ok ( ( ) )
236
34
}
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