@@ -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,11 @@ 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 ( cli : & Cli ) -> Result < ( Simulation , Option < Arc < Mutex < SimGraph > > > ) , anyhow:: Error > {
146
172
let cfg: SimulationCfg = SimulationCfg :: try_from ( cli) ?;
147
173
148
174
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) ?)
175
+ let SimParams { nodes, sim_network , activity } = serde_json:: from_str ( & std:: fs:: read_to_string ( sim_path) ?)
150
176
. map_err ( |e| {
151
177
anyhow ! (
152
178
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {})." ,
@@ -156,23 +182,71 @@ pub async fn create_simulation(cli: &Cli) -> Result<Simulation, anyhow::Error> {
156
182
)
157
183
} ) ?;
158
184
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 ( ) ,
185
+ // Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field.
186
+ if !nodes. is_empty ( ) && !sim_network. is_empty ( ) {
187
+ Err ( anyhow ! (
188
+ "Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real
189
+ or simulated nodes not both." , nodes. len( ) , sim_network. len( ) ,
169
190
) )
170
- } ;
191
+ } else if nodes. is_empty ( ) && sim_network. is_empty ( ) {
192
+ Err ( anyhow ! (
193
+ "Simulation file must contain nodes to run with real lightning nodes or sim_graph to run with
194
+ simulated nodes" ,
195
+ ) )
196
+ } else if !nodes. is_empty ( ) {
197
+ let ( clients, clients_info) = get_clients ( nodes) . await ?;
198
+ // We need to be able to look up destination nodes in the graph, because we allow defined activities to send to
199
+ // nodes that we do not control. To do this, we can just grab the first node in our map and perform the lookup.
200
+ let get_node = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
201
+ if let Some ( c) = clients. values ( ) . next ( ) {
202
+ return c. lock ( ) . await . get_node_info ( pk) . await ;
203
+ }
171
204
172
- let validated_activities = validate_activities ( activity, & clients_info, get_node) . await ?;
173
- let tasks = TaskTracker :: new ( ) ;
205
+ Err ( LightningError :: GetNodeInfoError (
206
+ "no nodes for query" . to_string ( ) ,
207
+ ) )
208
+ } ;
174
209
175
- Ok ( Simulation :: new ( cfg, clients, validated_activities, tasks) )
210
+ let validated_activities = validate_activities ( activity, & clients_info, get_node) . await ?;
211
+ let tasks = TaskTracker :: new ( ) ;
212
+
213
+ Ok ( ( Simulation :: new ( cfg, clients, validated_activities, tasks) , None ) )
214
+ } else {
215
+ // Convert nodes representation for parsing to SimulatedChannel
216
+ let channels = sim_network
217
+ . clone ( )
218
+ . into_iter ( )
219
+ . map ( SimulatedChannel :: from)
220
+ . collect :: < Vec < SimulatedChannel > > ( ) ;
221
+
222
+ let mut nodes_info = HashMap :: new ( ) ;
223
+ for sim_channel in sim_network {
224
+ nodes_info. insert (
225
+ sim_channel. node_1 . pubkey ,
226
+ node_info ( sim_channel. node_1 . pubkey ) ,
227
+ ) ;
228
+ nodes_info. insert (
229
+ sim_channel. node_2 . pubkey ,
230
+ node_info ( sim_channel. node_2 . pubkey ) ,
231
+ ) ;
232
+ }
233
+ let get_node_info = async |pk : & PublicKey | -> Result < NodeInfo , LightningError > {
234
+ if let Some ( node) = nodes_info. get ( pk) {
235
+ Ok ( node_info ( node. pubkey ) )
236
+ } else {
237
+ Err ( LightningError :: GetNodeInfoError ( format ! (
238
+ "node not found in simulated network: {}" ,
239
+ pk
240
+ ) ) )
241
+ }
242
+ } ;
243
+ let validated_activities =
244
+ validate_activities ( activity, & nodes_info, get_node_info) . await ?;
245
+ let tasks = TaskTracker :: new ( ) ;
246
+ let ( simulation, graph) =
247
+ Simulation :: new_with_sim_network ( cfg, channels, validated_activities, tasks) . await ?;
248
+ Ok ( ( simulation, Some ( graph) ) )
249
+ }
176
250
}
177
251
178
252
/// Connects to the set of nodes providing, returning a map of node public keys to LightningNode implementations and
0 commit comments