@@ -3,6 +3,8 @@ 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
8
use simln_lib:: {
7
9
cln, cln:: ClnNode , eclair, eclair:: EclairNode , lnd, lnd:: LndNode , serializers,
8
10
ActivityDefinition , Amount , Interval , LightningError , LightningNode , NodeId , NodeInfo ,
@@ -83,8 +85,11 @@ pub struct Cli {
83
85
84
86
#[ derive( Debug , Serialize , Deserialize , Clone ) ]
85
87
struct SimParams {
88
+ #[ serde( default ) ]
86
89
pub nodes : Vec < NodeConnection > ,
87
90
#[ serde( default ) ]
91
+ pub sim_network : Vec < NetworkParser > ,
92
+ #[ serde( default ) ]
88
93
pub activity : Vec < ActivityParser > ,
89
94
}
90
95
@@ -96,6 +101,27 @@ enum NodeConnection {
96
101
Eclair ( eclair:: EclairConnection ) ,
97
102
}
98
103
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.
106
+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
107
+ pub struct NetworkParser {
108
+ pub scid : ShortChannelID ,
109
+ pub capacity_msat : u64 ,
110
+ pub node_1 : ChannelPolicy ,
111
+ pub node_2 : ChannelPolicy ,
112
+ }
113
+
114
+ impl From < NetworkParser > for SimulatedChannel {
115
+ fn from ( network_parser : NetworkParser ) -> Self {
116
+ SimulatedChannel :: new (
117
+ network_parser. capacity_msat ,
118
+ network_parser. scid ,
119
+ network_parser. node_1 ,
120
+ network_parser. node_2 ,
121
+ )
122
+ }
123
+ }
124
+
99
125
/// Data structure used to parse information from the simulation file. It allows source and destination to be
100
126
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
101
127
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -142,11 +168,13 @@ impl TryFrom<&Cli> for SimulationCfg {
142
168
143
169
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
144
170
/// any activity described in the simulation file.
145
- pub async fn create_simulation ( cli : & Cli ) -> Result < Simulation , anyhow:: Error > {
171
+ pub async fn create_simulation (
172
+ cli : & Cli ,
173
+ ) -> Result < ( Simulation , Option < Arc < Mutex < SimGraph > > > ) , anyhow:: Error > {
146
174
let cfg: SimulationCfg = SimulationCfg :: try_from ( cli) ?;
147
175
148
176
let sim_path = read_sim_path ( cli. data_dir . clone ( ) , cli. sim_file . clone ( ) ) . await ?;
149
- let SimParams { nodes, activity } = serde_json:: from_str ( & std:: fs:: read_to_string ( sim_path) ?)
177
+ let SimParams { nodes, sim_network , activity } = serde_json:: from_str ( & std:: fs:: read_to_string ( sim_path) ?)
150
178
. map_err ( |e| {
151
179
anyhow ! (
152
180
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {})." ,
@@ -156,23 +184,74 @@ pub async fn create_simulation(cli: &Cli) -> Result<Simulation, anyhow::Error> {
156
184
)
157
185
} ) ?;
158
186
159
- let ( clients, clients_info) = get_clients ( nodes) . await ?;
160
- // We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
161
- // nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
162
- let get_node = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
163
- if let Some ( c) = clients. values ( ) . next ( ) {
164
- return c. lock ( ) . await . get_node_info ( pk) . await ;
165
- }
166
-
167
- Err ( LightningError :: GetNodeInfoError (
168
- "no nodes for query" . to_string ( ) ,
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( ) ,
169
192
) )
170
- } ;
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
+
207
+ Err ( LightningError :: GetNodeInfoError (
208
+ "no nodes for query" . to_string ( ) ,
209
+ ) )
210
+ } ;
171
211
172
- let validated_activities = validate_activities ( activity, & clients_info, get_node) . await ?;
173
- let tasks = TaskTracker :: new ( ) ;
212
+ let validated_activities = validate_activities ( activity, & clients_info, get_node) . await ?;
213
+ let tasks = TaskTracker :: new ( ) ;
174
214
175
- Ok ( Simulation :: new ( cfg, clients, validated_activities, tasks) )
215
+ Ok ( (
216
+ Simulation :: new ( cfg, clients, validated_activities, tasks) ,
217
+ None ,
218
+ ) )
219
+ } else {
220
+ // Convert nodes representation for parsing to SimulatedChannel
221
+ let channels = sim_network
222
+ . clone ( )
223
+ . into_iter ( )
224
+ . map ( SimulatedChannel :: from)
225
+ . collect :: < Vec < SimulatedChannel > > ( ) ;
226
+
227
+ let mut nodes_info = HashMap :: new ( ) ;
228
+ for sim_channel in sim_network {
229
+ nodes_info. insert (
230
+ sim_channel. node_1 . pubkey ,
231
+ node_info ( sim_channel. node_1 . pubkey ) ,
232
+ ) ;
233
+ nodes_info. insert (
234
+ sim_channel. node_2 . pubkey ,
235
+ node_info ( sim_channel. node_2 . pubkey ) ,
236
+ ) ;
237
+ }
238
+ let get_node_info = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
239
+ if let Some ( node) = nodes_info. get ( pk) {
240
+ Ok ( node_info ( node. pubkey ) )
241
+ } else {
242
+ Err ( LightningError :: GetNodeInfoError ( format ! (
243
+ "node not found in simulated network: {}" ,
244
+ pk
245
+ ) ) )
246
+ }
247
+ } ;
248
+ let validated_activities =
249
+ validate_activities ( activity, & nodes_info, get_node_info) . await ?;
250
+ let tasks = TaskTracker :: new ( ) ;
251
+ let ( simulation, graph) =
252
+ Simulation :: new_with_sim_network ( cfg, channels, validated_activities, tasks) . await ?;
253
+ Ok ( ( simulation, Some ( graph) ) )
254
+ }
176
255
}
177
256
178
257
/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and
0 commit comments