11
11
import time
12
12
from io import BytesIO
13
13
from pathlib import Path
14
+ from typing import Dict , List , Optional
15
+
16
+ import networkx as nx
14
17
15
18
from test_framework .p2p import MESSAGEMAP
16
19
from test_framework .messages import ser_uint256
17
20
18
21
19
22
logger = logging .getLogger ("utils" )
20
23
24
+
21
25
SUPPORTED_TAGS = [
22
26
"25.1" ,
23
27
"24.2" ,
24
28
"23.2" ,
25
29
"22.2" ,
26
30
]
31
+ DEFAULT_TAG = SUPPORTED_TAGS [0 ]
32
+ WEIGHTED_TAGS = [tag for index , tag in enumerate (reversed (SUPPORTED_TAGS )) for _ in range (index + 1 )]
27
33
28
34
29
35
def exponential_backoff (max_retries = 5 , base_delay = 1 , max_delay = 32 ):
@@ -187,7 +193,7 @@ def parse_bitcoin_conf(file_content):
187
193
return result
188
194
189
195
190
- def dump_bitcoin_conf (conf_dict ):
196
+ def dump_bitcoin_conf (conf_dict , for_graph = False ):
191
197
"""
192
198
Converts a dictionary representation of bitcoin.conf content back to INI-style string.
193
199
@@ -213,6 +219,9 @@ def dump_bitcoin_conf(conf_dict):
213
219
for sub_key , sub_value in values :
214
220
result .append (f"{ sub_key } ={ sub_value } " )
215
221
222
+ if for_graph :
223
+ return "," .join (result )
224
+
216
225
# Terminate file with newline
217
226
return "\n " .join (result ) + "\n "
218
227
@@ -408,3 +417,94 @@ def default_bitcoin_conf_args() -> str:
408
417
conf_args .append (f"-{ key } ={ value } " )
409
418
410
419
return " " .join (conf_args )
420
+
421
+
422
+ def create_graph_with_probability (graph_func , params : List , version : str , bitcoin_conf : Optional [str ], random_version : bool ):
423
+ kwargs = {}
424
+ for param in params :
425
+ try :
426
+ key , value = param .split ("=" )
427
+ kwargs [key ] = value
428
+ except ValueError :
429
+ msg = f"Invalid parameter format: { param } "
430
+ logger .error (msg )
431
+ return msg
432
+
433
+ # Attempt to convert numerical values from string to their respective numerical types
434
+ for key in kwargs :
435
+ try :
436
+ kwargs [key ] = int (kwargs [key ])
437
+ except ValueError :
438
+ try :
439
+ kwargs [key ] = float (kwargs [key ])
440
+ except ValueError :
441
+ pass
442
+
443
+ logger .debug (f"Parsed params: { kwargs } " )
444
+
445
+ try :
446
+ graph = graph_func (** kwargs )
447
+ except TypeError as e :
448
+ msg = f"Failed to create graph: { e } "
449
+ logger .error (msg )
450
+ return msg
451
+
452
+ # calculate degree
453
+ degree_dict = dict (graph .degree (graph .nodes ()))
454
+ nx .set_node_attributes (graph , degree_dict , 'degree' )
455
+
456
+ # add a default layout
457
+ pos = nx .spring_layout (graph )
458
+ for node in graph .nodes ():
459
+ graph .nodes [node ]['x' ] = float (pos [node ][0 ])
460
+ graph .nodes [node ]['y' ] = float (pos [node ][1 ])
461
+
462
+ # parse and process conf file
463
+ conf_contents = ""
464
+ if bitcoin_conf is not None :
465
+ conf = Path (bitcoin_conf )
466
+ if conf .is_file ():
467
+ with open (conf , 'r' ) as f :
468
+ # parse INI style conf then dump using for_graph
469
+ conf_dict = parse_bitcoin_conf (f .read ())
470
+ conf_contents = dump_bitcoin_conf (conf_dict , for_graph = True )
471
+
472
+ # populate our custom fields
473
+ for node in graph .nodes ():
474
+ if random_version :
475
+ graph .nodes [node ]['version' ] = random .choice (WEIGHTED_TAGS )
476
+ else :
477
+ graph .nodes [node ]['version' ] = version
478
+ graph .nodes [node ]['bitcoin_config' ] = conf_contents
479
+ graph .nodes [node ]['tc_netem' ] = ""
480
+
481
+ # remove type and customer fields from edges as we don't need 'em!
482
+ for edge in graph .edges ():
483
+ del graph .edges [edge ]["customer" ]
484
+ del graph .edges [edge ]["type" ]
485
+
486
+ convert_unsupported_attributes (graph )
487
+ return graph
488
+
489
+
490
+ def convert_unsupported_attributes (graph ):
491
+ # Sometimes networkx complains about invalid types when writing the graph
492
+ # (it just generated itself!). Try to convert them here just in case.
493
+ for _ , node_data in graph .nodes (data = True ):
494
+ for key , value in node_data .items ():
495
+ if isinstance (value , set ):
496
+ node_data [key ] = list (value )
497
+ elif isinstance (value , (int , float , str )):
498
+ continue
499
+ else :
500
+ node_data [key ] = str (value )
501
+
502
+ for _ , _ , edge_data in graph .edges (data = True ):
503
+ for key , value in edge_data .items ():
504
+ if isinstance (value , set ):
505
+ edge_data [key ] = list (value )
506
+ elif isinstance (value , (int , float , str )):
507
+ continue
508
+ else :
509
+ edge_data [key ] = str (value )
510
+
0 commit comments