@@ -3,13 +3,16 @@ use bitcoin::secp256k1::PublicKey;
3
3
use clap:: { builder:: TypedValueParser , Parser } ;
4
4
use log:: LevelFilter ;
5
5
use serde:: { Deserialize , Serialize } ;
6
- use simln_lib:: sim_node:: { node_info, ChannelPolicy , SimGraph , SimulatedChannel } ;
7
- use simln_lib:: ShortChannelID ;
6
+ use simln_lib:: sim_node:: {
7
+ ln_node_from_graph, node_info, populate_network_graph, ChannelPolicy , SimGraph ,
8
+ SimulatedChannel ,
9
+ } ;
8
10
use simln_lib:: {
9
11
cln, cln:: ClnNode , eclair, eclair:: EclairNode , lnd, lnd:: LndNode , serializers,
10
12
ActivityDefinition , Amount , Interval , LightningError , LightningNode , NodeId , NodeInfo ,
11
13
Simulation , SimulationCfg , WriteResults ,
12
14
} ;
15
+ use simln_lib:: { ShortChannelID , SimulationError } ;
13
16
use std:: collections:: HashMap ;
14
17
use std:: fs;
15
18
use std:: ops:: AsyncFn ;
@@ -24,7 +27,7 @@ pub const DEFAULT_DATA_DIR: &str = ".";
24
27
/// The default simulation file to be used by the simulator.
25
28
pub const DEFAULT_SIM_FILE : & str = "sim.json" ;
26
29
27
- /// The default expected payment amount for the simulation, around ~$10 at the time of writing .
30
+ /// The default expected payment amount for the simulation, around ~$10 at the time of w .
28
31
pub const DEFAULT_EXPECTED_PAYMENT_AMOUNT : u64 = 3_800_000 ;
29
32
30
33
/// The number of times over each node in the network sends its total deployed capacity in a calendar month.
@@ -83,10 +86,29 @@ pub struct Cli {
83
86
pub fix_seed : Option < u64 > ,
84
87
}
85
88
89
+ impl Cli {
90
+ pub fn validate ( & self , sim_params : & SimParams ) -> Result < ( ) , anyhow:: Error > {
91
+ // Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field.
92
+ if !sim_params. nodes . is_empty ( ) && !sim_params. sim_network . is_empty ( ) {
93
+ return Err ( anyhow ! (
94
+ "Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real
95
+ or simulated nodes not both." , sim_params. nodes. len( ) , sim_params. sim_network. len( ) ,
96
+ ) ) ;
97
+ }
98
+ if sim_params. nodes . is_empty ( ) && sim_params. sim_network . is_empty ( ) {
99
+ return Err ( anyhow ! (
100
+ "Simulation file must contain nodes to run with real lightning nodes or sim_graph to run with
101
+ simulated nodes" ,
102
+ ) ) ;
103
+ }
104
+ Ok ( ( ) )
105
+ }
106
+ }
107
+
86
108
#[ derive( Debug , Serialize , Deserialize , Clone ) ]
87
- struct SimParams {
109
+ pub struct SimParams {
88
110
#[ serde( default ) ]
89
- pub nodes : Vec < NodeConnection > ,
111
+ nodes : Vec < NodeConnection > ,
90
112
#[ serde( default ) ]
91
113
pub sim_network : Vec < NetworkParser > ,
92
114
#[ serde( default ) ]
@@ -101,8 +123,8 @@ enum NodeConnection {
101
123
Eclair ( eclair:: EclairConnection ) ,
102
124
}
103
125
104
- /// Data structure that is used to parse information from the simulation file, used to pair two node policies together
105
- /// without the other internal state that is used in our simulated network.
126
+ /// Data structure that is used to parse information from the simulation file. It is used to
127
+ /// create a mocked network
106
128
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
107
129
pub struct NetworkParser {
108
130
pub scid : ShortChannelID ,
@@ -125,7 +147,7 @@ impl From<NetworkParser> for SimulatedChannel {
125
147
/// Data structure used to parse information from the simulation file. It allows source and destination to be
126
148
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
127
149
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
128
- struct ActivityParser {
150
+ pub struct ActivityParser {
129
151
/// The source of the payment.
130
152
#[ serde( with = "serializers::serde_node_id" ) ]
131
153
pub source : NodeId ,
@@ -166,94 +188,121 @@ impl TryFrom<&Cli> for SimulationCfg {
166
188
}
167
189
}
168
190
191
+ pub async fn create_simulation_with_network (
192
+ cli : & Cli ,
193
+ sim_params : & SimParams ,
194
+ tasks : TaskTracker ,
195
+ ) -> Result < Simulation , anyhow:: Error > {
196
+ let cfg: SimulationCfg = SimulationCfg :: try_from ( cli) ?;
197
+ let SimParams {
198
+ nodes : _,
199
+ sim_network,
200
+ activity,
201
+ } = sim_params;
202
+
203
+ // Convert nodes representation for parsing to SimulatedChannel
204
+ let channels = sim_network
205
+ . clone ( )
206
+ . into_iter ( )
207
+ . map ( SimulatedChannel :: from)
208
+ . collect :: < Vec < SimulatedChannel > > ( ) ;
209
+
210
+ let mut nodes_info = HashMap :: new ( ) ;
211
+ for sim_channel in sim_network {
212
+ nodes_info. insert (
213
+ sim_channel. node_1 . pubkey ,
214
+ node_info ( sim_channel. node_1 . pubkey ) ,
215
+ ) ;
216
+ nodes_info. insert (
217
+ sim_channel. node_2 . pubkey ,
218
+ node_info ( sim_channel. node_2 . pubkey ) ,
219
+ ) ;
220
+ }
221
+ let get_node_info = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
222
+ if let Some ( node) = nodes_info. get ( pk) {
223
+ Ok ( node_info ( node. pubkey ) )
224
+ } else {
225
+ Err ( LightningError :: GetNodeInfoError ( format ! (
226
+ "node not found in simulated network: {}" ,
227
+ pk
228
+ ) ) )
229
+ }
230
+ } ;
231
+ let ( pk_node_map, alias_node_map) = add_node_to_maps ( & nodes_info) ?;
232
+ let validated_activities = validate_activities (
233
+ activity. to_vec ( ) ,
234
+ pk_node_map,
235
+ alias_node_map,
236
+ get_node_info,
237
+ )
238
+ . await ?;
239
+
240
+ let ( shutdown_trigger, shutdown_listener) = triggered:: trigger ( ) ;
241
+
242
+ // Setup a simulation graph that will handle propagation of payments through the network
243
+ let simulation_graph = Arc :: new ( Mutex :: new (
244
+ SimGraph :: new ( channels. clone ( ) , tasks. clone ( ) , shutdown_trigger. clone ( ) )
245
+ . map_err ( |e| SimulationError :: SimulatedNetworkError ( format ! ( "{:?}" , e) ) ) ?,
246
+ ) ) ;
247
+
248
+ // Copy all simulated channels into a read-only routing graph, allowing to pathfind for
249
+ // individual payments without locking th simulation graph (this is a duplication of the channels, but the performance tradeoff is worthwhile for concurrent pathfinding).
250
+ let routing_graph = Arc :: new (
251
+ populate_network_graph ( channels)
252
+ . map_err ( |e| SimulationError :: SimulatedNetworkError ( format ! ( "{:?}" , e) ) ) ?,
253
+ ) ;
254
+
255
+ let nodes = ln_node_from_graph ( simulation_graph. clone ( ) , routing_graph) . await ;
256
+
257
+ Ok ( Simulation :: new (
258
+ cfg,
259
+ nodes,
260
+ validated_activities,
261
+ tasks,
262
+ shutdown_trigger,
263
+ shutdown_listener,
264
+ ) )
265
+ }
266
+
169
267
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
170
268
/// any activity described in the simulation file.
171
269
pub async fn create_simulation (
172
270
cli : & Cli ,
173
- ) -> Result < ( Simulation , Option < Arc < Mutex < SimGraph > > > ) , anyhow:: Error > {
271
+ sim_params : & SimParams ,
272
+ tasks : TaskTracker ,
273
+ ) -> Result < Simulation , anyhow:: Error > {
174
274
let cfg: SimulationCfg = SimulationCfg :: try_from ( cli) ?;
175
-
176
- let sim_path = read_sim_path ( cli. data_dir . clone ( ) , cli. sim_file . clone ( ) ) . await ?;
177
- let SimParams { nodes, sim_network, activity} = serde_json:: from_str ( & std:: fs:: read_to_string ( sim_path) ?)
178
- . map_err ( |e| {
179
- anyhow ! (
180
- "Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {})." ,
181
- e. line( ) ,
182
- e. column( ) ,
183
- e. to_string( )
184
- )
185
- } ) ?;
186
-
187
- // Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field.
188
- if !nodes. is_empty ( ) && !sim_network. is_empty ( ) {
189
- Err ( anyhow ! (
190
- "Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real
191
- or simulated nodes not both." , nodes. len( ) , sim_network. len( ) ,
192
- ) )
193
- } else if nodes. is_empty ( ) && sim_network. is_empty ( ) {
194
- Err ( anyhow ! (
195
- "Simulation file must contain nodes to run with real lightning nodes or sim_graph to run with
196
- simulated nodes" ,
197
- ) )
198
- } else if !nodes. is_empty ( ) {
199
- let ( clients, clients_info) = get_clients ( nodes) . await ?;
200
- // We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
201
- // nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
202
- let get_node = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
203
- if let Some ( c) = clients. values ( ) . next ( ) {
204
- return c. lock ( ) . await . get_node_info ( pk) . await ;
205
- }
206
- Err ( LightningError :: GetNodeInfoError (
207
- "no nodes for query" . to_string ( ) ,
208
- ) )
209
- } ;
210
-
211
- let ( pk_node_map, alias_node_map) = add_node_to_maps ( & clients_info) . await ?;
212
- let validated_activities =
213
- validate_activities ( activity, pk_node_map, alias_node_map, get_node) . await ?;
214
- let tasks = TaskTracker :: new ( ) ;
215
-
216
- Ok ( (
217
- Simulation :: new ( cfg, clients, validated_activities, tasks) ,
218
- None ,
219
- ) )
220
- } else {
221
- // Convert nodes representation for parsing to SimulatedChannel
222
- let channels = sim_network
223
- . clone ( )
224
- . into_iter ( )
225
- . map ( SimulatedChannel :: from)
226
- . collect :: < Vec < SimulatedChannel > > ( ) ;
227
-
228
- let mut nodes_info = HashMap :: new ( ) ;
229
- for sim_channel in sim_network {
230
- nodes_info. insert (
231
- sim_channel. node_1 . pubkey ,
232
- node_info ( sim_channel. node_1 . pubkey ) ,
233
- ) ;
234
- nodes_info. insert (
235
- sim_channel. node_2 . pubkey ,
236
- node_info ( sim_channel. node_2 . pubkey ) ,
237
- ) ;
275
+ let SimParams {
276
+ nodes,
277
+ sim_network : _,
278
+ activity,
279
+ } = sim_params;
280
+
281
+ let ( clients, clients_info) = get_clients ( nodes. to_vec ( ) ) . await ?;
282
+ // We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
283
+ // nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
284
+ let get_node = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
285
+ if let Some ( c) = clients. values ( ) . next ( ) {
286
+ return c. lock ( ) . await . get_node_info ( pk) . await ;
238
287
}
239
- let get_node_info = async | pk : & PublicKey | -> Result < NodeInfo , LightningError > {
240
- if let Some ( node ) = nodes_info . get ( pk ) {
241
- Ok ( node_info ( node . pubkey ) )
242
- } else {
243
- Err ( LightningError :: GetNodeInfoError ( format ! (
244
- "node not found in simulated network: {}" ,
245
- pk
246
- ) ) )
247
- }
248
- } ;
249
- let ( pk_node_map , alias_node_map ) = add_node_to_maps ( & nodes_info ) . await ? ;
250
- let validated_activities =
251
- validate_activities ( activity , pk_node_map , alias_node_map , get_node_info ) . await ? ;
252
- let tasks = TaskTracker :: new ( ) ;
253
- let ( simulation , graph ) =
254
- Simulation :: new_with_sim_network ( cfg , channels , validated_activities , tasks ) . await ? ;
255
- Ok ( ( simulation , Some ( graph ) ) )
256
- }
288
+ Err ( LightningError :: GetNodeInfoError (
289
+ "no nodes for query" . to_string ( ) ,
290
+ ) )
291
+ } ;
292
+
293
+ let ( pk_node_map , alias_node_map ) = add_node_to_maps ( & clients_info ) ? ;
294
+ let validated_activities =
295
+ validate_activities ( activity . to_vec ( ) , pk_node_map , alias_node_map , get_node ) . await ? ;
296
+ let ( shutdown_trigger , shutdown_listener ) = triggered :: trigger ( ) ;
297
+
298
+ Ok ( Simulation :: new (
299
+ cfg ,
300
+ clients ,
301
+ validated_activities ,
302
+ tasks ,
303
+ shutdown_trigger ,
304
+ shutdown_listener ,
305
+ ) )
257
306
}
258
307
259
308
/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and
@@ -289,11 +338,12 @@ async fn get_clients(
289
338
Ok ( ( clients, clients_info) )
290
339
}
291
340
341
+ type NodeMapping =
342
+ Result < ( HashMap < PublicKey , NodeInfo > , HashMap < String , NodeInfo > ) , LightningError > ;
343
+
292
344
/// Adds a lightning node to a client map and tracking maps used to lookup node pubkeys and aliases for activity
293
345
/// validation.
294
- async fn add_node_to_maps (
295
- nodes : & HashMap < PublicKey , NodeInfo > ,
296
- ) -> Result < ( HashMap < PublicKey , NodeInfo > , HashMap < String , NodeInfo > ) , LightningError > {
346
+ fn add_node_to_maps ( nodes : & HashMap < PublicKey , NodeInfo > ) -> NodeMapping {
297
347
let mut pk_node_map = HashMap :: new ( ) ;
298
348
let mut alias_node_map = HashMap :: new ( ) ;
299
349
@@ -387,7 +437,7 @@ async fn validate_activities(
387
437
Ok ( validated_activities)
388
438
}
389
439
390
- async fn read_sim_path ( data_dir : PathBuf , sim_file : PathBuf ) -> anyhow:: Result < PathBuf > {
440
+ pub async fn read_sim_path ( data_dir : PathBuf , sim_file : PathBuf ) -> anyhow:: Result < PathBuf > {
391
441
if sim_file. exists ( ) {
392
442
Ok ( sim_file)
393
443
} else if sim_file. is_relative ( ) {
0 commit comments