3838 getLogger
3939)
4040from math import floor
41- from os import environ
41+ from os import (
42+ environ ,
43+ mkdir
44+ )
4245from os .path import (
4346 dirname ,
4447 exists as path_exists ,
5154from tempfile import mkdtemp
5255from time import time as _time
5356from typing import (
57+ Dict ,
58+ Iterable ,
5459 List ,
5560 Optional ,
5661 Set ,
@@ -132,7 +137,7 @@ def __init__(
132137
133138 # Required to interact with the network & the nodes
134139 self .loop = loop
135- self .nodes : List [ TestNode ] = []
140+ self .nodes : Dict [ int , TestNode ] = {}
136141 self .nodes_hub : Optional [NodesHub ] = None
137142 self .proposer_node_ids : List [int ] = []
138143 self .validator_node_ids : List [int ] = []
@@ -145,11 +150,18 @@ def run(self) -> bool:
145150 self .setup_chain ()
146151 self .setup_nodes ()
147152
148- try :
149- if self . num_validator_nodes > 0 :
153+ if self . num_validator_nodes > 0 :
154+ try :
150155 self .autofinalization_workaround ()
156+ except BaseException as e :
157+ self .logger .critical (
158+ 'Workaround execution failure' , exc_info = e
159+ )
160+ return False
161+ try :
151162 self .start_nodes ()
152- except (OSError , AssertionError ):
163+ except (OSError , AssertionError ) as e :
164+ self .logger .critical ('Unable to start nodes' , exc_info = e )
153165 return False # Early shutdown
154166
155167 self .nodes_hub = NodesHub (
@@ -163,14 +175,19 @@ def run(self) -> bool:
163175 self .nodes_hub .sync_start_proxies ()
164176 self .nodes_hub .sync_connect_nodes_graph (self .graph_edges )
165177
166- # Notice that the validators have already loaded their wallets
167178 self .logger .info ('Importing wallets' )
168- for idx , proposer_id in enumerate ( self .proposer_node_ids ):
169- if idx > 0 :
170- self . nodes [ proposer_id ]. createwallet (f'n{ proposer_id } ' )
179+ for node_id , node in self .nodes . items ( ):
180+ node . createwallet ( f'n { node_id } ' )
181+ tmp_wallet = node . get_wallet_rpc (f'n{ node_id } ' )
171182
172- tmp_wallet = self .nodes [proposer_id ].get_wallet_rpc (f'n{ proposer_id } ' )
173- tmp_wallet .importwallet (normpath (self .tmp_dir + f'/n{ proposer_id } .wallet' ))
183+ if self .num_validator_nodes > 0 :
184+ tmp_wallet .importwallet (
185+ normpath (self .tmp_dir + f'/n{ node_id } .wallet' )
186+ )
187+ else :
188+ tmp_wallet .importmasterkey (
189+ regtest_mnemonics [node_id ]['mnemonics' ]
190+ )
174191
175192 self .loop .run_until_complete (self .trigger_simulation_stop ())
176193 return True
@@ -183,10 +200,20 @@ def autofinalization_workaround(self):
183200 self .logger .info ('Running auto-finalization workaround' )
184201
185202 lucky_proposer_id = self .proposer_node_ids [0 ]
186- validators = [self . nodes [ i ] for i in self .validator_node_ids ]
203+ lucky_node_ids = [lucky_proposer_id ] + self .validator_node_ids
187204
188- self .start_node (lucky_proposer_id )
189- self .start_nodes (validators )
205+ # We'll start nodes isolated from the experiment's network, and reload
206+ # their wallets later once the experiment starts after the workaround.
207+ if not path_exists (self .tmp_dir + '/workaround' ):
208+ mkdir (self .tmp_dir + '/workaround' )
209+ for node_id in lucky_node_ids :
210+ initialize_datadir (self .tmp_dir + '/workaround' , node_id )
211+
212+ workaround_nodes = self .build_nodes_instances (
213+ base_dir = self .tmp_dir + '/workaround' ,
214+ node_ids = lucky_node_ids
215+ )
216+ self .start_nodes (workaround_nodes )
190217
191218 # Although we don't need to collect data during this initialization
192219 # phase, we'll connect the nodes through a NodesHub instance to ensure
@@ -195,47 +222,62 @@ def autofinalization_workaround(self):
195222 tmp_hub = NodesHub (
196223 loop = self .loop ,
197224 latency_policy = StaticLatencyPolicy (0 ),
198- nodes = self . nodes ,
225+ nodes = workaround_nodes ,
199226 network_stats_collector = NullNetworkStatsCollector ()
200227 )
201- lucky_node_ids = [lucky_proposer_id ] + self .validator_node_ids
202228 tmp_hub .sync_start_proxies (lucky_node_ids )
203229 dense_graph = create_simple_dense_graph (node_ids = lucky_node_ids )
204230 tmp_hub .sync_connect_nodes_graph (dense_graph )
205231
206232 # We have to load some money into the nodes
207- lucky_proposer = self . nodes [lucky_proposer_id ]
233+ lucky_proposer = workaround_nodes [lucky_proposer_id ]
208234 for proposer_id in self .proposer_node_ids :
209235 lucky_proposer .createwallet (f'n{ proposer_id } ' )
210236 tmp_wallet = lucky_proposer .get_wallet_rpc (f'n{ proposer_id } ' )
211237 tmp_wallet .importmasterkey (
212238 regtest_mnemonics [proposer_id ]['mnemonics' ]
213239 )
214240 for validator_id in self .validator_node_ids :
215- self . nodes [validator_id ].createwallet (f'n{ validator_id } ' )
216- tmp_wallet = self . nodes [validator_id ].get_wallet_rpc (f'n{ validator_id } ' )
241+ workaround_nodes [validator_id ].createwallet (f'n{ validator_id } ' )
242+ tmp_wallet = workaround_nodes [validator_id ].get_wallet_rpc (f'n{ validator_id } ' )
217243 tmp_wallet .importmasterkey (
218244 regtest_mnemonics [validator_id ]['mnemonics' ]
219245 )
220246
221- self .loop . run_until_complete ( self . ensure_autofinalization_is_off () )
247+ self .logger . info ( 'Imported mnemonics into workaround nodes' )
222248
223- # Unloading the wallets that don't belong to the lucky proposer
249+ self .loop .run_until_complete (self .ensure_autofinalization_is_off (
250+ workaround_nodes
251+ ))
252+
253+ # Dumping wallets to be loaded later
224254 for proposer_id in self .proposer_node_ids :
225255 tmp_wallet = lucky_proposer .get_wallet_rpc (f'n{ proposer_id } ' )
226256 tmp_wallet .dumpwallet (normpath (self .tmp_dir + f'/n{ proposer_id } .wallet' ))
227- if proposer_id != lucky_proposer_id :
228- lucky_proposer .unloadwallet (f'n{ proposer_id } ' )
257+ lucky_proposer .unloadwallet (f'n{ proposer_id } ' )
258+ for validator_id in self .validator_node_ids :
259+ tmp_wallet = workaround_nodes [validator_id ].get_wallet_rpc (f'n{ validator_id } ' )
260+ tmp_wallet .dumpwallet (normpath (self .tmp_dir + f'/n{ validator_id } .wallet' ))
261+
262+ self .logger .info ('Dumped workaround wallets to be reused later' )
263+
264+ # We close all temporary connections & shut down nodes
265+ tmp_hub .close ()
266+ self .stop_nodes (workaround_nodes )
229267
230- tmp_hub .close () # We close all temporary connections
268+ # Cleaning workaround stuff
269+ rmtree (self .tmp_dir + '/workaround' )
231270
232271 # We recover the original topology for the full network
233272 # self.num_nodes, self.graph_edges = tmp_num_nodes, tmp_graph_edges
234273 self .logger .info ('Finished auto-finalization workaround' )
235274
236- async def ensure_autofinalization_is_off (self ):
275+ async def ensure_autofinalization_is_off (
276+ self ,
277+ workaround_nodes : Dict [int , TestNode ]
278+ ):
237279 for validator_id in self .validator_node_ids :
238- validator = self . nodes [validator_id ]
280+ validator = workaround_nodes [validator_id ]
239281 tmp_wallet = validator .get_wallet_rpc (f'n{ validator_id } ' )
240282 tmp_wallet .deposit (
241283 tmp_wallet .getnewaddress ('' , 'legacy' ),
@@ -249,7 +291,7 @@ async def ensure_autofinalization_is_off(self):
249291 # We have to wait at least for one epoch :( .
250292 await asyncio_sleep (1 + self .block_time_seconds * 50 )
251293
252- lucky_proposer = self . nodes [self .proposer_node_ids [0 ]]
294+ lucky_proposer = workaround_nodes [self .proposer_node_ids [0 ]]
253295 is_autofinalization_off = False
254296
255297 while not is_autofinalization_off :
@@ -265,6 +307,8 @@ def safe_run(self, close_loop=True) -> bool:
265307 successful_run = False
266308 try :
267309 successful_run = self .run ()
310+ except BaseException as e :
311+ self .logger .critical ('The sub-experiment failed' , exc_info = e )
268312 finally :
269313 self .logger .info ('Releasing resources' )
270314 if self .nodes_hub is not None :
@@ -299,29 +343,13 @@ def cleanup_directories(self):
299343 if self .tmp_dir != '' and path_exists (self .tmp_dir ):
300344 self .logger .info ('Cleaning temporary directories' )
301345 rmtree (self .tmp_dir )
302- # TODO: Remove wallet.* files too
303346
304347 def setup_chain (self ):
305348 self .logger .info ('Preparing "empty" chain' )
306349 for i in range (self .num_nodes ):
307350 initialize_datadir (self .tmp_dir , i )
308351
309- def setup_nodes (self ):
310- if len (self .nodes ) > 0 :
311- self .logger .info ('Skipping nodes setup' )
312- return
313-
314- self .logger .info ('Creating node wrappers' )
315-
316- all_node_ids = set (range (self .num_nodes ))
317- self .proposer_node_ids = sample (
318- all_node_ids , self .num_proposer_nodes
319- )
320- self .validator_node_ids = sample (
321- all_node_ids .difference (self .proposer_node_ids ),
322- self .num_validator_nodes
323- )
324-
352+ def get_node_args (self , node_id : int ) -> List [str ]:
325353 # Some values are copied from test_framework.util.initialize_datadir, so
326354 # they are redundant, but it's easier to see what's going on by having
327355 # all of them together.
@@ -353,36 +381,61 @@ def setup_nodes(self):
353381 for mnemonic in regtest_mnemonics
354382 ]
355383 }
356- }, separators = ("," ,":" ))} '''
384+ }, separators = ("," , ":" ))} '''
357385 ]
358386 relay_args = ['-proposing=0' ] + node_args
359387 proposer_args = ['-proposing=1' ] + node_args
360388 validator_args = ['-proposing=0' , '-validating=1' ] + node_args
361389
390+ if node_id in self .proposer_node_ids :
391+ _node_args = proposer_args
392+ elif node_id in self .validator_node_ids :
393+ _node_args = validator_args
394+ else :
395+ _node_args = relay_args
396+ return [
397+ f'-bind=127.0.0.1:{ NodesHub .get_p2p_node_port (node_id )} ' ,
398+ f'-rpcbind=127.0.0.1:{ NodesHub .get_rpc_node_port (node_id )} ' ,
399+ f'''-stats-log-output-file={
400+ self .nodes_stats_directory .joinpath (f"stats_{ node_id } .csv" )
401+ } ''' ,
402+ f'-uacomment=simpatch{ node_id } '
403+ ] + _node_args
404+
405+ def setup_nodes (self ):
406+ if len (self .nodes ) > 0 :
407+ self .logger .info ('Skipping nodes setup' )
408+ return
409+
410+ self .logger .info ('Creating node wrappers' )
411+
412+ all_node_ids = set (range (self .num_nodes ))
413+ self .proposer_node_ids = sample (
414+ all_node_ids , self .num_proposer_nodes
415+ )
416+ self .validator_node_ids = sample (
417+ all_node_ids .difference (self .proposer_node_ids ),
418+ self .num_validator_nodes
419+ )
420+
362421 if not self .nodes_stats_directory .exists ():
363422 self .nodes_stats_directory .mkdir ()
364423
365- def get_node_args (node_id : int ) -> List [str ]:
366- if node_id in self .proposer_node_ids :
367- _node_args = proposer_args
368- elif node_id in self .validator_node_ids :
369- _node_args = validator_args
370- else :
371- _node_args = relay_args
372- return [
373- f'-bind=127.0.0.1:{ NodesHub .get_p2p_node_port (node_id )} ' ,
374- f'-rpcbind=127.0.0.1:{ NodesHub .get_rpc_node_port (node_id )} ' ,
375- f'''-stats-log-output-file={
376- self .nodes_stats_directory .joinpath (f"stats_{ node_id } .csv" )
377- } ''' ,
378- f'-uacomment=simpatch{ node_id } '
379- ] + _node_args
380-
381- self .nodes = [
382- TestNode (
424+ self .nodes = self .build_nodes_instances (
425+ base_dir = self .tmp_dir ,
426+ node_ids = range (self .num_nodes )
427+ )
428+
429+ def build_nodes_instances (
430+ self ,
431+ base_dir : str ,
432+ node_ids : Iterable [int ]
433+ ) -> Dict [int , TestNode ]:
434+ return {
435+ i : TestNode (
383436 i = i ,
384- datadir = f'{ self . tmp_dir } /node{ i } ' ,
385- extra_args = get_node_args (i ),
437+ datadir = f'{ base_dir } /node{ i } ' ,
438+ extra_args = self . get_node_args (i ),
386439 rpchost = None ,
387440 timewait = 60 ,
388441 unit_e = environ ['UNIT_E' ],
@@ -391,8 +444,8 @@ def get_node_args(node_id: int) -> List[str]:
391444 coverage_dir = None ,
392445 use_cli = False
393446 )
394- for i in range ( self . num_nodes )
395- ]
447+ for i in node_ids
448+ }
396449
397450 def start_node (self , i : int ):
398451 node = self .nodes [i ]
@@ -403,20 +456,20 @@ def start_node(self, i: int):
403456 self .stop_nodes ()
404457 raise
405458
406- def start_nodes (self , nodes : Optional [List [ TestNode ]] = None ):
459+ def start_nodes (self , nodes : Optional [Dict [ int , TestNode ]] = None ):
407460 self .logger .info ('Starting nodes' )
408461
409462 if nodes is None :
410463 nodes = self .nodes
411464
412- for node_id , node in enumerate ( nodes ):
465+ for node_id , node in nodes . items ( ):
413466 try :
414467 if not node .running :
415468 node .start ()
416469 except OSError as e :
417470 self .logger .critical (f'Node { node_id } failed to start' , e )
418471 raise
419- for node_id , node in enumerate ( nodes ):
472+ for node_id , node in nodes . items ( ):
420473 try :
421474 node .wait_for_rpc_connection ()
422475 except AssertionError as e :
@@ -428,14 +481,18 @@ def start_nodes(self, nodes: Optional[List[TestNode]] = None):
428481
429482 self .logger .info ('Started nodes' )
430483
431- def stop_nodes (self ):
484+ def stop_nodes (self , nodes : Optional [ Dict [ int , TestNode ]] = None ):
432485 self .logger .info ('Stopping nodes' )
433- for node in self .nodes :
486+
487+ if nodes is None :
488+ nodes = self .nodes
489+
490+ for node in nodes .values ():
434491 try :
435492 node .stop_node ()
436493 except AssertionError :
437494 continue
438- for node in self . nodes :
495+ for node in nodes . values () :
439496 node .wait_until_stopped ()
440497
441498 def define_network_topology (self ):
0 commit comments