-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathindex.html
executable file
·5408 lines (4980 loc) · 222 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Blockchain Simulator</title>
<meta name='author' content='Simeon Wuthier'>
<meta name='description' content='A proof-of-work network simulator for blockchain and cryptocurrency research.'>
<meta name='keywords'
content='blockchain, bitcoin, proof of work, pow, network, simulator, simulation, blockchain, cryptocurrency, research, peer to peer, p2p'>
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<link rel='shortcut icon' href='img/favicon.ico' type='image/x-icon'>
<link rel='icon' href='img/favicon.ico' type='image/x-icon'>
<!-- Stylesheets -->
<link rel='stylesheet' href='lib/bootstrap.min.css'>
<link href='lib/jsoneditor.min.css' rel='stylesheet' type='text/css'>
<link rel='stylesheet' href='lib/codemirror/lint.css'>
<link rel='stylesheet' href='lib/codemirror/codemirror.css'>
<link rel='stylesheet' href='lib/codemirror/material-darker.css'>
<link rel='stylesheet' href='style.min.css'>
<!-- Scripts -->
<script type='text/javascript' src='lib/vis-network.min.js'></script>
<script src='lib/jsoneditor.min.js'></script>
<script src='lib/lz-string.min.js'></script>
<script src='lib/codemirror/codemirror.js'></script>
<script src='lib/codemirror/formatting.js'></script>
<script src='lib/codemirror/javascript.js'></script>
<script src='lib/codemirror/jshint.js'></script>
<script src='lib/codemirror/lint.js'></script>
<script src='lib/codemirror/javascript-lint.js'></script>
<script src='lib/NoSleep.min.js'></script>
<!-- Dynamically Updating Inline Styles -->
<style id='miner-colors-selector'></style>
</head>
<body class='py-3'>
<div class='container bg-light rounded pb-3'>
<div class='py-2 text-center'>
<h1 class='display-4 d-inline user-select-none'>Blockchain Simulator</h1> <span
class='badge badge-primary user-select-none'>v1.2</span> <span class='badge badge-secondary'><a
class='text-white user-select-none' href='index_v0.12.html'>v0.12</a></span>
</div>
<div class='jumbotron p-3 mb-3'>
<div class='row'>
<div class='col-md-12 mb-3'>
<div id='navbar' class='btn-group fillx user-select-none'>
<div class='btn-group w-100'>
<!-- File Options Dropdown -->
<button type='button' class='btn btn-dark dropdown-toggle dropdown-toggle-split rounded-0'
data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'
onclick='DOM_clearOptionMsg()'>
<span class='sr-only'>Toggle Dropdown</span>
<span id='fileOptionMsg' class='badge badge-light'></span>
File
</button>
<div class='dropdown-menu'>
<a class='dropdown-item' onclick='loadFromFile()'>Open network file<span
class='badge badge-secondary float-right'
style='margin-top:3px'>CTRL+O</span></a>
<a class='dropdown-item' onclick='saveToFile(true)'>Save network file <span
class='badge badge-secondary float-right'
style='margin-top:3px'>CTRL+S</span></a>
<a class='dropdown-item' onclick='saveToFile(false)'>Save network file
(uncompressed)</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='saveLocalStorage()'>Save network to LocalStorage</a>
<a class='dropdown-item' onclick='loadLocalStorage()'>Load network from LocalStorage</a>
<a class='dropdown-item' onclick='deleteLocalStorage()'>Delete network from
LocalStorage</a>
<a class='dropdown-item' onclick='clearAllLocalStorage()'>Clear LocalStorage</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' data-toggle='modal' data-target='#copyToClipboardModal'
onclick='DOM_saveToClipboard()'>Copy network to clipboard</a>
<a class='dropdown-item' data-toggle='modal' data-target='#loadFromClipboardModal'
onclick='DOM_loadFromClipboard()'>Load network from clipboard</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' href='https://github.com/simewu/blockchain-simulator'
target='_blank'>View on GitHub</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' data-toggle='modal' data-target='#aboutModal'
onclick='loadAboutModal()'>About</a>
</div>
</div>
<div class='btn-group w-100' style='max-height:35px;cursor:pointer'>
<!-- Network Options Dropdown -->
<button type='button' class='btn btn-dark dropdown-toggle dropdown-toggle-split rounded-0'
data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'
onclick='DOM_clearOptionMsg()'>
<span class='sr-only'>Toggle Dropdown</span>
<span id='networkOptionMsg' class='badge badge-light'></span>
Network
</button>
<div class='dropdown-menu'>
<a class='dropdown-item' data-toggle='modal' data-target='#addMinerModal'
onclick='addMinerBtnClicked()'><b>Add/edit miner</b><span
class="badge badge-secondary float-right" style="margin-top:3px">E</span></a>
<a class='dropdown-item' onclick='DOM_duplicateMiner()'><span
id='duplicateMinerBtn'>Duplicate miner</span><span
class="badge badge-secondary float-right" style="margin-top:3px">D</span></a>
<a class='dropdown-item' onclick='DOM_removeMiner()'><span id='removeMinerBtn'>Remove
miner</span><span class="badge badge-secondary float-right"
style="margin-top:3px">Del</span></a>
<a class='dropdown-item'
onclick='if(confirm("You are about to remove all miners.")) DOM_removeAllMiners()'>Remove
all miners<span class="badge badge-secondary float-right"
style="margin-top:3px">Shift+Del</span></a>
<a class='dropdown-item' onclick='DOM_sortMiners()'>Sort miners</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='DOM_globalSetPower()'>Globally set power
(hashes/s)</a>
<a class='dropdown-item' onclick='DOM_globalSetLatency()'>Globally set latency (ms)</a>
<a class='dropdown-item' onclick='DOM_globalSetDownloadMBPS()'>Globally set downlink
bandwidth (MBps)</a>
<a class='dropdown-item' onclick='DOM_globalSetUploadMBPS()'>Globally set uplink
bandwidth (MBps)</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='DOM_toggleLoggingNetworkData()'
id='DOM_item_toggleLoggingNetworkData'>Enable logging network<br>data within each
miner</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='DOM_clearBlockchain()'>Clear all blockchains</a>
<a class='dropdown-item' onclick='DOM_setNetworkDifficulty()'>Set network difficulty</a>
<a class='dropdown-item' onclick='DOM_setBlockSizes()'>Set block sizes</a>
<a class='dropdown-item' onclick='toggleSha256()' id='useSHA256Btn'>Use secure SHA-256
(slower)</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' data-toggle='modal' data-target='#viewNetworkModal'
onclick='DOM_viewNetworkJSON()'>View full network structure</a>
</div>
</div>
<div class='btn-group w-100' style='max-height:35px;cursor:pointer'>
<!-- Topology Options Dropdown -->
<button type='button' class='btn btn-dark dropdown-toggle dropdown-toggle-split rounded-0'
data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'
onclick='DOM_clearOptionMsg()'>
<span class='sr-only'>Toggle Dropdown</span>
<span id='topologyOptionMsg' class='badge badge-light'></span>
Topology
</button>
<div class='dropdown-menu'>
<a class='dropdown-item' data-toggle='modal'
data-target='#generateTopologyModal'><b>Generate a topology</b></a>
<a class='dropdown-item' onclick='duplicateTopology()'>Duplicate current topology</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='toggleBidirectionalConnections()'>Toggle
bidirectionality <span class="badge badge-secondary float-right"
style="margin-top:3px">+</span></a>
<a class='dropdown-item' onclick='disconnectNonBidirectionalPeers()'>Remove partial
edges <span class="badge badge-secondary float-right"
style="margin-top:3px">–</span></a>
<a class='dropdown-item' onclick='fullyConnectPeers()'>Connect all nodes</a>
<a class='dropdown-item' onclick='fullyDisconnectPeers()'>Disconnect all nodes</a>
</div>
</div>
<div class='btn-group w-100' style='max-height:35px;cursor:pointer'>
<!-- Display Options Dropdown -->
<button type='button' class='btn btn-dark dropdown-toggle dropdown-toggle-split rounded-0'
data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'
onclick='DOM_clearOptionMsg()'>
<span class='sr-only'>Toggle Dropdown</span>
<span id='displayOptionMsg' class='badge badge-light'></span>
Display
</button>
<div class='dropdown-menu'>
<a class='dropdown-item' onclick='toggleVisualizations()'>Toggle visualizations</a>
<a class='dropdown-item' onclick='DOM_toggleMinerStatsTable()'>Toggle miner stats
table</a>
<a class='dropdown-item' onclick='DOM_toggleSamplerForm()'>Toggle sampler</a>
<div class='dropdown-divider' id='vis_btn_delimeter'></div>
<a class='dropdown-item' id='vis_refresh_btn' onclick='DOM_refreshVisualizer()'>Refresh
visualizer<span class="badge badge-secondary float-right"
style="margin-top:3px">R</span></a>
<a class='dropdown-item' onclick='DOM_togglePhysics()'><span
id='vis_physics_btn'>Disable visualizer physics</span><span
class="badge badge-secondary float-right" style="margin-top:3px">P</span></a>
<a class='dropdown-item' id='vis_line_length_btn' onclick='DOM_setLineLengths()'>Set
line lengths</a>
<div class='dropdown-divider'></div>
<a class='dropdown-item' onclick='formatSamplerCode()'>Format all sampler code</a>
</div>
</div>
</div>
</div>
<hr>
</div>
<div class='row'>
<div class='col-md-4'>
<div class='row px-3' style='height:calc(100% - 35px)'>
<select id='minerList' onclick='DOM_selectedMiner()' onchange='DOM_selectedMiner()' size='6'
class='form-control fillx filly'>
</select>
</div>
<div class='btn-group fillx' style='max-height:35px;cursor:pointer;padding:0 1px'>
<button id='toggleMiningBtn' onclick='DOM_toggleAllMining()' type='button'
class='btn btn-dark rounded-0' style='width:100%'>Start All Miners</button>
<!-- Miner Options -->
<button type='button' class='btn btn-dark rounded-0 dropdown-toggle dropdown-toggle-split'
data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'
onclick='DOM_clearOptionMsg()'>
<span class='sr-only'>Toggle Dropdown</span>
</button>
<div class='dropdown-menu'>
<a id='toggleMinerMiningBtn' class='dropdown-item' onclick='DOM_toggleMining()'>Toggle
mining for miner</a>
<a id='clearMinerBlockchainBtn' class='dropdown-item'
onclick='DOM_clearMinerBlockchain()'>Clear blockchain for miner</a>
<div class='dropdown-divider'></div>
<a id='stopBlocksInFlightBtn' class='dropdown-item'
onclick='DOM_toggleStoppingBlocksInFlight()'>Stop blocks in flight<br>after stopping all
miners</a>
</div>
</div>
</div>
<div class='col-md-8'>
<div class='row pr-3'>
<div id='jsoneditor'></div>
<span id='editMinerBadge' class='ml-auto badge badge-secondary'></span>
</div>
</div>
</div>
</div>
<div class='row'>
<div class='col-md-12'>
<span id='canvasVisualizerSpan'></span>
<div id='networkVisualizer'></div>
</div>
</div>
<div class='row'>
<div id='canvasVisualizerContainer' class='col-md-12'>
<div id='canvasVisualizerCodeContainer' class='col-md-12 m-0'>
<label id='canvasVisualizerCodeLabel' for='canvasVisualizerCode'>Time Plot:</label>
<input id='canvasVisualizerCode' list='canvasVisualizerCodeList' type='text'
onchange='saveSamplerSettings()' class='col-sm-6 custom-select custom-select-sm'>
<datalist id='canvasVisualizerCodeList'>
<option value='miner.lastAcceptedBlock'>Visualize most recent block</option>
<option value='miner.lastAcceptedBlock.balances[miner.ID] * 700'>Visualize account balance
</option>
<option value='miner.lastAcceptedBlock.height * 700'>Visualize block height</option>
<option value='miner.networkBuffer.buffer'>Visualize network buffers</option>
<option value='miner.blockchain_blockDelay[miner.blockchain_blockDelay.length - 1] * 10'>
Visualize block propagation time (requires toggling network logging)</option>
<option value='Math.random()'>Random noise</option>
</datalist>
</div>
<canvas id='canvasVisualizer' onclick='canvasVisualizerClicked(event)' class='w-100'
height='9'></canvas>
</div>
</div>
<div id='minerStatsDiv' class='row'>
<div class='col-md-12'>
<small>
<table id='minerStatsTable' class='table table-dark table-hover table-responsive table-sm mb-0'>
</table>
</small>
</div>
</div>
<div id='samplerDiv' class='row'>
<div class='col-md-12'>
<div class='card text-white bg-dark rounded-0 px-2'>
<div class='card-header'>
<br>
<div class='form-group row'>
<div class='input-group'>
<label for='timePerSample' class='col-sm-3 col-form-label'>Time per sample (s)</label>
<div class='col-sm-9 p-0'>
<input id='timePerSample' type='number' onchange='saveSamplerSettings()'
class='form-control border border-secondary' min='0' value='60' required>
</div>
</div>
</div>
<div class='form-group row'>
<label for='codeBeforeSampling' class='col-sm-3 col-form-label'>Run before sampling</label>
<div class='col-sm-9 p-0'>
<textarea id='codeBeforeSampling'
class='form-control text-white bg-dark border border-secondary' rows='1'></textarea>
</div>
</div>
<div class='form-group row'>
<label for='codeBetweenSamples' class='col-sm-3 col-form-label'>Run between samples</label>
<div class='col-sm-9 p-0'>
<textarea id='codeBetweenSamples'
class='form-control text-white bg-dark border border-secondary' rows='2'></textarea>
</div>
</div>
<div class='form-group row'>
<label for='codeAfterSampling' class='col-sm-3 col-form-label'>Run after sampling</label>
<div class='col-sm-9 p-0'>
<textarea id='codeAfterSampling'
class='form-control text-white bg-dark border border-secondary' rows='1'></textarea>
</div>
</div>
<div class='form-group row'>
<label class='col-sm-3 col-form-label'></label>
<div class='col-sm-9 p-0'>
<label class='btn btn-secondary'>
<input id='logSamplesCheckbox' type='checkbox' onchange='saveSamplerSettings()'> Log
samples to file
</label>
<label class='btn btn-secondary'>
<input id='resetBlockchainAfterEachSampleCheckbox' type='checkbox'
onchange='saveSamplerSettings()' checked> Reset blockchain after each sample
</label>
<label class='btn btn-secondary'>
<input id='updateTableAfterEachSampleCheckbox' type='checkbox'
onchange='saveSamplerSettings()' checked> Update table
</label>
</div>
</div>
<div id='logColumns' class='form-group row'>
<label class='col-sm-3 col-form-label'>Log columns (multiselect)</label>
<div class='col-sm-9 p-0'>
<select id='logColumnsList' multiple='multiple' onchange='saveSamplerSettings()'
class='form-control' size='8'>
</select>
<label class='btn btn-dark'>
<input id='logAveragesOnlyCheckbox' type='checkbox'
onchange='saveSamplerSettings()'> Log averages only, rather than per-node
</label>
</div>
</div>
<div class='form-group row mb-0'>
<button id='toggleSamplingBtn' class='btn btn-primary fillx border border-dark'
onclick='toggleSampling()'>Begin Sampling</button>
</div>
</div>
<div class='col-md-12 p-2'>
<small>
<table id='sampleTable' class='table table-dark table-hover table-responsive table-sm mb-2'>
</table>
</small>
</div>
</div>
</div>
</div>
<div class='row'>
<div class='col-md-12'>
<div class='card text-white bg-dark rounded-0 px-2'>
<div class='card-header'>
<h3 class='display-5 d-inline'>Blockchain Explorer</h3>
<hr>
<div class='form-group row'>
<textarea id='explorerOutput' class='bg-dark text-white' readonly></textarea>
</div>
<div class='form-group row'>
<div class='input-group'>
<label for='explorerMinerName' class='col-sm-3 w-25'>Miner Name<span
id='explorerMinerMinerColor' class='badge badge-pill'> </span></label>
<select id='explorerMinerName'
class='col-sm-3 w-25 form-select bg-secondary text-white rounded'
onchange='changeExplorerMiner()'></select>
<label for='explorerMinerBlock' class='col-sm-3 w-25'>Block Number<span
id='explorerMinerBlockColor' class='badge badge-pill'> </span></label>
<input id='explorerMinerBlock' type='number' onchange='changeExplorerMiner()'
class='border border-secondary bg-secondary text-white px-4 w-25 rounded' min='-1'
value='0' required>
</div>
</div>
</div>
<div class='col-md-12 p-2'>
<small>
<table id='sampleTable' class='table table-dark table-hover table-responsive table-sm mb-2'>
</table>
</small>
</div>
</div>
</div>
</div>
<!-- Modals -->
<div class='modal fade' id='addMinerModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 class='modal-title'>Add or Edit a Miner</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<form>
<div class='modal-body'>
<div class='form-group row'>
<label for='addMinerName' class='col-sm-4 col-form-label'>Name</label>
<div class='col-sm-8'>
<input type='text' class='form-control' id='addMinerName' value=''
onclick='this.select()' onblur='this.value = fixCase(this.value)' required>
</div>
</div>
<div class='form-group row'>
<label for='addMinerPower' class='col-sm-4 col-form-label'>Computing Power</label>
<div class='col-sm-8'>
<input type='number' class='form-control' id='addMinerPower' min='0' value='10'
required>
</div>
</div>
<div class='form-group row'>
<label for='addMinerLatency' class='col-sm-4 col-form-label'>Network Latency
(milliseconds)</label>
<div class='col-sm-8'>
<input type='number' class='form-control' id='addMinerLatency' min='0' value='10'
required>
</div>
</div>
<div class='form-group row'>
<label for='addMinerDownloadMBPS' class='col-sm-4 col-form-label'>Network Downlink (MB
per second)</label>
<div class='col-sm-8'>
<input type='number' class='form-control' id='addMinerDownloadMBPS' min='0'
value='10' required>
</div>
</div>
<div class='form-group row'>
<label for='addMinerUploadMBPS' class='col-sm-4 col-form-label'>Network Uplink (MB per
second)</label>
<div class='col-sm-8'>
<input type='number' class='form-control' id='addMinerUploadMBPS' min='0' value='10'
required>
</div>
</div>
<div class='form-group row'>
<label for='multiselectAddPeers' class='col-sm-4 col-form-label'>Peers
(multiselect)</label>
<div class='col-sm-8'>
<select id='multiselectAddPeers' multiple='multiple' class='form-control' size='6'>
</select>
</div>
</div>
</div>
<div class='modal-footer'>
<button id='addMinerSubmit' class='btn btn-default' onclick='DOM_addMiner()'
data-dismiss='modal'>Add/Edit</button>
</div>
</form>
</div>
</div>
</div>
<div class='modal fade' id='generateTopologyModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 class='modal-title'>Pick a Topology</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<div class='modal-body'>
<!-- Support clickable areas on the image for topology selection -->
<img src='img/topologies.png' style='width:100%' usemap='#topologies'>
<map id='topologyMap' name='topologies' data-dismiss='modal' data-toggle='modal'
data-target='#generateTopologyModal2'>
<area shape='rect' coords='10, 10, 450, 506' alt='Ring'
onclick='DOM_selectTopology("Ring")'>
<area shape='rect' coords='471, 10, 1005, 506' alt='Mesh'
onclick='DOM_selectTopology("Mesh")'>
<area shape='rect' coords='1026, 10, 1403, 506' alt='Star'
onclick='DOM_selectTopology("Star")'>
<area shape='rect' coords='1424, 10, 1910, 506' alt='Fully Connected'
onclick='DOM_selectTopology("Fully Connected")'>
<area shape='rect' coords='10, 527, 740, 931' alt='Line'
onclick='DOM_selectTopology("Line")'>
<area shape='rect' coords='761, 527, 1367, 931' alt='Tree'
onclick='DOM_selectTopology("Tree")'>
<area shape='rect' coords='1388, 527, 1910, 931' alt='Bus'
onclick='DOM_selectTopology("Bus")'>
<area shape='rect' coords='10, 958, 955, 1412' alt='Barabási-Albert'
onclick='DOM_selectTopology("Barabási-Albert")'>
<area shape='rect' coords='965, 958, 1910, 1412' alt='Bitcoin'
onclick='DOM_selectTopology("Bitcoin")'>
</map>
</div>
</div>
</div>
</div>
<div class='modal fade' id='generateTopologyModal2' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 id='topologyTitle' class='modal-title'>Topology</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<form>
<div class='modal-body'>
<div id='topologyjsoneditor'></div>
</div>
<div class='modal-footer'>
<button class='btn btn-default' onclick='DOM_generateTopology()'
data-dismiss='modal'>Generate</button>
</div>
</form>
</div>
</div>
</div>
<div class='modal fade' id='viewNetworkModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 id='topologyTitle' class='modal-title'>View Network</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<div class='modal-body'>
<div id='networkjsoneditor'></div>
<span id='editNetworkBadge' class='ml-auto badge badge-secondary'></span>
</div>
</div>
</div>
</div>
<div class='modal fade' id='copyToClipboardModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 id='topologyTitle' class='modal-title'>Copy To Clipboard <span id='networkToCopy'></span>
</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<div class='modal-body'>
<div class='btn-group btn-group-toggle rounded-top w-100' style='overflow-x: auto'
data-toggle='buttons'>
<label class='btn btn-secondary active'>
<input type='radio' name='compressionOptions' id='compressionNone' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()'> None
</label>
<label class='btn btn-secondary'>
<input type='radio' name='compressionOptions' id='compressionUTF16' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()' checked> UTF16
</label>
<label class='btn btn-info'>
<input type='radio' name='compressionOptions' id='compressionURL' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()'> URL
</label>
<label class='btn btn-secondary active'>
<input type='radio' name='compressionOptions' id='compressionBinary' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()'> Binary
</label>
<label class='btn btn-secondary'>
<input type='radio' name='compressionOptions' id='compressionNumeric' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()'> Numeric
</label>
<label class='btn btn-secondary'>
<input type='radio' name='compressionOptions' id='compressionHexadecimal'
autocomplete='off' onchange='DOM_saveToClipboardUpdateCompression()'> Hexadecimal
</label>
<label class='btn btn-secondary'>
<input type='radio' name='compressionOptions' id='compressionAlphanumeric'
autocomplete='off' onchange='DOM_saveToClipboardUpdateCompression()'> Alphanumeric
</label>
<label class='btn btn-secondary'>
<input type='radio' name='compressionOptions' id='compressionURI' autocomplete='off'
onchange='DOM_saveToClipboardUpdateCompression()'> URI
</label>
</div>
<br>
<textarea id='copyToClipboardTextarea' style='width:100%;height:400px' readonly></textarea>
<button class='btn btn-dark fillx' onclick='DOM_copyToClipboardButton()'
data-dismiss='modal'>Copy</button>
</div>
</div>
</div>
</div>
<div class='modal fade' id='loadFromClipboardModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 id='topologyTitle' class='modal-title'>Paste Network</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<div class='modal-body'>
<p>Compressed and uncompressed (JSON) strings are supported.</p>
<textarea id='pasteNetworkTextarea' style='width:100%;height:400px'></textarea>
<button id='loadPastedNetworkBtn' class='btn btn-dark fillx' onclick='DOM_loadPastedNetwork()'
data-dismiss='modal'>Load network</button>
</div>
</div>
</div>
</div>
<div class='modal fade' id='aboutModal' role='dialog'>
<div class='modal-dialog'>
<!-- Modal content-->
<div class='modal-content'>
<div class='modal-header'>
<h4 id='topologyTitle' class='modal-title'>Blockchain Simulator</h4>
<button type='button' class='close' data-dismiss='modal'>×</button>
</div>
<div class='modal-body' style='font-size:small'>
<p>
Blockchain and the proof-of-work (PoW) distributed consensus protocol rely on peer-to-peer
(P2P) networking. We introduce a PoW P2P simulator for the modeling and analyses of
permissionless blockchain and cryptocurrency. We publish our simulator in open source to
facilitate its use for blockchain and P2P networking research and especially recommend it
for scalability research or preliminary testing.
<hr>
Visit the GitHub repository <a
href='https://github.com/simewu/blockchain-simulator'>here</a>. Additionally, if you
have any questions, comments, or suggestions, please send them to <span
id='contactAddress'></span>.
<hr>
We appreciate you using our app. If you'd like to support us, donations can be made to the
following Bitcoin address <a
href='https://www.blockchain.com/btc/address/188R8dBjRcCyR7MPwtY6tWiFyapdBapGFh'
target='_blank'>188R8dBjRcCyR7MPwtY6tWiFyapdBapGFh</a>.
</p>
<!-- Bitcoin Address QR Code -->
<p style='text-align:center'>
<a href='https://www.blockchain.com/btc/address/188R8dBjRcCyR7MPwtY6tWiFyapdBapGFh'
target='_blank'>
<svg shape-rendering='crispEdges' height='128' width='128'
style='display:block;margin:5px auto' viewBox='0 0 29 29' class='qrcode'>
<path fill='#FFF' d='M0,0 h29v29H0z'></path>
<path fill='#000'
d='M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h1v1H12zM15 0h4v1H15zM20 0h1v1H20zM22,0 h7v1H22zM0 1h1v1H0zM6 1h1v1H6zM9 1h6v1H9zM17 1h2v1H17zM22 1h1v1H22zM28,1 h1v1H28zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h3v1H8zM13 2h1v1H13zM18 2h1v1H18zM20 2h1v1H20zM22 2h1v1H22zM24 2h3v1H24zM28,2 h1v1H28zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM8 3h3v1H8zM12 3h4v1H12zM20 3h1v1H20zM22 3h1v1H22zM24 3h3v1H24zM28,3 h1v1H28zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM8 4h1v1H8zM11 4h2v1H11zM14 4h7v1H14zM22 4h1v1H22zM24 4h3v1H24zM28,4 h1v1H28zM0 5h1v1H0zM6 5h1v1H6zM10 5h2v1H10zM13 5h1v1H13zM16 5h1v1H16zM19 5h1v1H19zM22 5h1v1H22zM28,5 h1v1H28zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22,6 h7v1H22zM10 7h1v1H10zM12 7h1v1H12zM17 7h1v1H17zM19 7h2v1H19zM0 8h4v1H0zM6 8h1v1H6zM8 8h1v1H8zM10 8h3v1H10zM14 8h1v1H14zM17 8h5v1H17zM24 8h3v1H24zM28,8 h1v1H28zM0 9h1v1H0zM5 9h1v1H5zM8 9h1v1H8zM10 9h1v1H10zM12 9h1v1H12zM14 9h1v1H14zM16 9h1v1H16zM20 9h1v1H20zM22 9h1v1H22zM24 9h1v1H24zM27,9 h2v1H27zM0 10h1v1H0zM2 10h2v1H2zM6 10h1v1H6zM9 10h4v1H9zM14 10h1v1H14zM16 10h1v1H16zM18 10h1v1H18zM22 10h4v1H22zM27 10h1v1H27zM0 11h1v1H0zM3 11h2v1H3zM9 11h2v1H9zM13 11h1v1H13zM16 11h1v1H16zM18 11h2v1H18zM22 11h1v1H22zM28,11 h1v1H28zM0 12h2v1H0zM6 12h2v1H6zM9 12h2v1H9zM12 12h1v1H12zM14 12h2v1H14zM17 12h1v1H17zM19 12h2v1H19zM23 12h2v1H23zM26 12h1v1H26zM1 13h1v1H1zM4 13h2v1H4zM7 13h1v1H7zM11 13h2v1H11zM14 13h6v1H14zM22 13h1v1H22zM25 13h1v1H25zM27,13 h2v1H27zM1 14h2v1H1zM5 14h2v1H5zM9 14h1v1H9zM11 14h1v1H11zM14 14h1v1H14zM18 14h5v1H18zM26,14 h3v1H26zM2 15h4v1H2zM7 15h2v1H7zM10 15h2v1H10zM14 15h2v1H14zM18 15h2v1H18zM21 15h1v1H21zM25 15h1v1H25zM0 16h4v1H0zM6 16h4v1H6zM12 16h1v1H12zM15 16h4v1H15zM20 16h2v1H20zM24 16h1v1H24zM28,16 h1v1H28zM2 17h1v1H2zM4 17h1v1H4zM7 17h1v1H7zM10 17h1v1H10zM13 17h1v1H13zM15 17h1v1H15zM17 17h4v1H17zM22 17h1v1H22zM25 17h2v1H25zM0 18h1v1H0zM2 18h1v1H2zM6 18h4v1H6zM13 18h3v1H13zM18 18h1v1H18zM20 18h2v1H20zM24 18h2v1H24zM2 19h1v1H2zM4 19h1v1H4zM7 19h2v1H7zM10 19h2v1H10zM14 19h7v1H14zM22 19h5v1H22zM28,19 h1v1H28zM1 20h2v1H1zM4 20h3v1H4zM8 20h2v1H8zM14 20h1v1H14zM16 20h2v1H16zM19 20h8v1H19zM28,20 h1v1H28zM8 21h1v1H8zM13 21h1v1H13zM15 21h2v1H15zM20 21h1v1H20zM24,21 h5v1H24zM0 22h7v1H0zM9 22h2v1H9zM14 22h1v1H14zM16 22h1v1H16zM18 22h3v1H18zM22 22h1v1H22zM24 22h1v1H24zM27 22h1v1H27zM0 23h1v1H0zM6 23h1v1H6zM11 23h3v1H11zM16 23h1v1H16zM20 23h1v1H20zM24 23h2v1H24zM0 24h1v1H0zM2 24h3v1H2zM6 24h1v1H6zM9 24h3v1H9zM17 24h1v1H17zM20,24 h9v1H20zM0 25h1v1H0zM2 25h3v1H2zM6 25h1v1H6zM8 25h1v1H8zM11 25h1v1H11zM13 25h6v1H13zM21 25h1v1H21zM24 25h1v1H24zM28,25 h1v1H28zM0 26h1v1H0zM2 26h3v1H2zM6 26h1v1H6zM8 26h3v1H8zM12 26h1v1H12zM14 26h1v1H14zM18 26h2v1H18zM25 26h2v1H25zM28,26 h1v1H28zM0 27h1v1H0zM6 27h1v1H6zM8 27h1v1H8zM10 27h1v1H10zM12 27h2v1H12zM16 27h1v1H16zM18 27h2v1H18zM21 27h4v1H21zM27 27h1v1H27zM0 28h7v1H0zM8 28h1v1H8zM11 28h1v1H11zM16 28h5v1H16zM22 28h2v1H22zM25 28h1v1H25zM27 28h1v1H27z'>
</path>
</svg>
</a>
</p>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery first, imageMapResizer, then Popper.js, then Bootstrap JS -->
<script src='lib/jquery.slim.min.js'></script>
<script src='lib/imageMapResizer.min.js'></script>
<script src='lib/popper.min.js'></script>
<script src='lib/bootstrap.min.js'></script>
</body>
<script type='text/javascript'>
// Optional strict mode supported but disabled by default to support the use of eval() for the code editor
//'use strict';
let network = null;
// Global variables for the code editor, network, and visualizer
const updateToolOptions = {
bitcoinLatencyData: { url: null, data: null },
canvasVisualizerCanvas: document.getElementById('canvasVisualizer'),
canvasVisualizerCtx: document.getElementById('canvasVisualizer').getContext('2d', { willReadFrequently: true }),
canvasVisualizerBarWidth: 15,
canvasVisualizerInterval: 1000 / 30,
canvasVisualizerPixelStepSize: 1,
canvasVisualizerThread: null,
codeEditorAfterSampling: null,
codeEditorBeforeSampling: null,
codeEditorBetweenSamples: null,
isCtrlPressed: false,
isShiftPressed: false,
minerStatsTable_active: true,
nodeLatencyBackup: {},
requestVisRedrawTimeout: null,
sampleEndTime: 0,
sampler_DOM_active: true,
sampler_running: false,
samplerData: [],
samplerLog: [],
sampleStartTime: 0,
saveSamplerSettingsTimeout: null,
selectedMiner: '',
selectedMinerPrev: '',
selectMinerTimeout: null,
stopBlocksInFlightWhenStoppingMining: false,
timeout: null,
updateMinerListTimeout: null,
visBorderSize: 'default', // Changes according to zoom: ['none', 'small', 'default', 'large']
visLineLength: undefined,
visNetwork: null,
visNodeIDs: [],
visPhysics: true,
visPhysicsAutoSet: false,
visSimulationActive: true,
visTimeout: null,
};
// Use the 256-bit secure hashing algorithm (SHA256) by calling sha256('data string')
const sha256 = function a(b) { function c(a, b) { return a >>> b | a << 32 - b } for (var d, e, f = Math.pow, g = f(2, 32), h = 'length', i = '', j = [], k = 8 * b[h], l = a.h = a.h || [], m = a.k = a.k || [], n = m[h], o = {}, p = 2; 64 > n; p++)if (!o[p]) { for (d = 0; 313 > d; d += p)o[d] = p; l[n] = f(p, .5) * g | 0, m[n++] = f(p, 1 / 3) * g | 0 } for (b += '\x80'; b[h] % 64 - 56;)b += '\x00'; for (d = 0; d < b[h]; d++) { if (e = b.charCodeAt(d), e >> 8) return; j[d >> 2] |= e << (3 - d) % 4 * 8 } for (j[j[h]] = k / g | 0, j[j[h]] = k, e = 0; e < j[h];) { var q = j.slice(e, e += 16), r = l; for (l = l.slice(0, 8), d = 0; 64 > d; d++) { var s = q[d - 15], t = q[d - 2], u = l[0], v = l[4], w = l[7] + (c(v, 6) ^ c(v, 11) ^ c(v, 25)) + (v & l[5] ^ ~v & l[6]) + m[d] + (q[d] = 16 > d ? q[d] : q[d - 16] + (c(s, 7) ^ c(s, 18) ^ s >>> 3) + q[d - 7] + (c(t, 17) ^ c(t, 19) ^ t >>> 10) | 0), x = (c(u, 2) ^ c(u, 13) ^ c(u, 22)) + (u & l[1] ^ u & l[2] ^ l[1] & l[2]); l = [w + x | 0].concat(l), l[4] = l[4] + w | 0 } for (d = 0; 8 > d; d++)l[d] = l[d] + r[d] | 0 } for (d = 0; 8 > d; d++)for (e = 3; e + 1; e--) { var y = l[d] >> 8 * e & 255; i += (16 > y ? 0 : '') + y.toString(16) } return i };
// This is the definition of the Network class, which represents a network of peers in a blockchain simulation. It contains properties such as the network's name, the peers in the network, the difficulty of mining blocks, the size of blocks, the genesis block, and logging information. It also contains methods for setting the difficulty, starting and stopping all miners, removing blocks in flight, clearing the blockchain, and finding a peer by name. Additionally, it has a static method for serializing a network object into JSON format.
class Network {
constructor() {
this.name = '';
this.peers = {};
this.difficulty = 0.01;
this.blockSize = 1000000;
this.genesis = new Header();
this.genesis.height = 0;
this.genesis.timestamp = Date.now();
this.useSHA256 = false; // Specify whether or not to actually use genuine hashing (real security, but slow), or to use random numbers and a difficulty threshold to determine a block (no security, but fast)
this.ID_Registry = {};
this.miningThreads = {};
this.minimumBlockchainPurgeLength = 100;
this.maximumBlockchainPurgeLength = 200; // When a miner's blockchain reaches this many blocks, it will remove the oldest ones until it's the minimumBlockchainPurgeLength
// When a block is received, keep track of number of networking data, such as the number of hops
this.logging = {
columns: {
'Sample': true,
'Sample time': true,
'Sample tag': false,
'Miner name': true,
'Balance': true,
'Balance %': false,
'Power (hash/s)': true,
'Power %': false,
'Blockchain IDs': true,
'Blockchain num hops': true,
'Blockchain travel time (ms)': true,
'Artificial travel time (ms)': false,
'Network buffer size limit': true,
'Blocks in flight': true,
'Latency (ms)': true,
'Downlink (MBps)': true,
'Uplink (MBps)': true,
'Block height': true,
'Total blocks found': true,
'Stale blocks found': true,
'Max fork length': true,
'Number of network partitions': true,
'Diameter of the network (number of hops)': false,
'Diameter of the network (latency)': false,
'Number of edges (ignoring bidirectional)': false,
'Number of edges (including bidirectional)': false,
'Estimated time for a block to reach all nodes': false,
'Estimated time for a block to reach 50% of nodes': false,
},
logNetworkData: false,
msPerSample: 20000,
codeBeforeSampling: 'console.log(\'Sampling started at \', Date.now());',
codeBetweenSamples: '',
codeAfterSampling: 'console.log(\'Sampling ended at \', Date.now());',
logSamples: false,
resetBlockchainAfterEachSample: true,
updateTableAfterEachSample: true,
logAveragesOnlyCheckbox: true,
canvasVisualizerCode: 'miner.lastAcceptedBlock',
}
}
setDifficulty(_difficulty) {
_difficulty = _difficulty.toString();
if (_difficulty.startsWith('0x')) {
_difficulty = _difficulty.substring(2); // Remove the 0x
const maxDifficulty = parseInt('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'.substring(0, 15), 16);
const d = parseInt(_difficulty.substring(0, 15), 16) / maxDifficulty;
if (!isNaN(d)) {
this.difficulty = d;
} else {
alert(`Invalid difficulty value: '${_difficulty}'.`);
}
} else {
const d = parseFloat(_difficulty);
if (!isNaN(d)) {
this.difficulty = d;
} else {
alert(`Invalid difficulty value: '${_difficulty}'.`);
}
}
}
startAllMiners() {
for (let id in network.ID_Registry) {
this.peers[id].startMining();
}
}
stopAllMiners() {
for (let id in network.ID_Registry) {
this.peers[id].stopMining();
}
if (updateToolOptions.stopBlocksInFlightWhenStoppingMining) {
this.removeBlocksInFlight();
for (let id in network.ID_Registry) {
this.peers[id].setVisualizerColor(); // Fix all the colors
}
}
}
removeBlocksInFlight() {
for (let id in network.ID_Registry) {
this.peers[id].networkBuffer.clear();
}
}
clearBlockchain() {
for (let id in this.ID_Registry) {
this.peers[id].clearBlockchain();
this.peers[id].networkBuffer.clear();
}
clearAllTimeouts();
}
findPeerByName(name) {
for (let id in this.ID_Registry) {
if (this.peers[id].name == name) return this.peers[id];
}
return null;
}
// Given a network object, return it in its serialized JSON form
// Compression can be [null, 'binary', 'numeric', 'hexadecimal', 'alphanumeric', 'utf16', 'uri']
static serialize(network, compression = 'utf16') {
network.stopAllMiners();
network.removeBlocksInFlight();
let newNetwork = null, destructive = false;
try {
const json = JSON_stringify(network);
// Now clone it, remove the temporary properties such as blockchain, then re-convert to JSON
newNetwork = JSON_parse(json);
} catch (e) {
const proceed = confirm('There was an out-of-memory error cloning the network for serialization.\n\nWould you like to proceed with successful serialization at the cost of potentially corrupting the current network instance?');
if (!proceed) return;
destructive = true;
newNetwork = network; // Don't clone it
}
delete newNetwork.genesis;
for (let ID in newNetwork.peers) {
const peer = newNetwork.peers[ID];
delete peer.lastAcceptedBlock;
delete peer.blockchain;
delete peer.blockchainOffset;
delete peer.currentHeader;
delete peer.totalBlocksFound;
delete peer.staleBlocksFound;
delete peer.largestForkLength;
delete peer.blockchain_blockID;
delete peer.blockchain_numHops;
delete peer.blockchain_blockDelay;
delete peer.blockchain_artificialBlockDelay;
delete peer.networkBuffer;
}
const restoreNetwork = (json) => {
// If we removed the network's critical data, rebuild it here
let fixedNetwork = Network.deserialize(json);
if (fixedNetwork != null) network = fixedNetwork;
else console.error('Unable to restore the network, please refresh the page.');
};
if (compression != null && compression != 'none' && compression !== undefined) {
let fixedJSON = JSON_stringify(newNetwork);
if (destructive) setTimeout(restoreNetwork, 1000, fixedJSON);
let compressed = compressString('\n' + fixedJSON, compression);
return compressed;
} else {
let fixedJSONFormatted = JSON_stringify(newNetwork, null, '\t');
if (destructive) setTimeout(restoreNetwork, 1000, fixedJSONFormatted);
return fixedJSONFormatted;
}
}
// Given a serialized network, return the network object (consisting of Network, Miner, Header classes)
static deserialize(data, quiet = false) {
clearAllTimeouts();
const decompressed = decompressString(data);
if (decompressed != null && decompressed.startsWith('\n')) data = decompressed.substring(1);
try {
const newNetworkObj = JSON_parse(data);
let network = new Network();
// Add logging if it doesn't exist
if (newNetworkObj.logging === undefined) newNetworkObj.logging = network.logging;
if (newNetworkObj.logging.columns === undefined) newNetworkObj.logging.columns = network.logging.columns;
// Merge the columns together
for (let column in network.logging.columns) {
if (newNetworkObj.logging.columns[column] !== undefined) {
network.logging.columns[column] = newNetworkObj.logging.columns[column];
}
}
newNetworkObj.logging.columns = network.logging.columns;
network = Object.assign(network, newNetworkObj);
network.name = network.name.toString();
if (network.name == '0') network.name = '';
for (let peer in network.peers) { // Make all miners Miner objects
let miner = new Miner(network.peers[peer].name, network.peers[peer].power, network.peers[peer].latency, network.peers[peer].bandwidth?.downlink || network.peers[peer].downloadMBPS, network.peers[peer].bandwidth?.uplink || network.peers[peer].uploadMBPS);
miner = Object.assign(miner, network.peers[peer]);
network.peers[peer] = miner;
// Make the currentHeader an actual Header object
let currentHeader = new Header();
currentHeader = Object.assign(currentHeader, network.peers[peer].currentHeader);
network.peers[peer].currentHeader = currentHeader;
const lastAcceptedBlock = currentHeader.clone();
// And so that it doesn't affect the color:
delete lastAcceptedBlock.numHops;
delete lastAcceptedBlock.propagationTime;
// Backward compatibility to support outgoingPeers arrays; convert them to latency objects
miner.latency = miner.latency || 0;
if (Array.isArray(miner.outgoingPeers)) {
let updatedOutgoingPeers = {};
for (let id of miner.outgoingPeers) {
updatedOutgoingPeers[id] = miner.latency;
}
miner.outgoingPeers = updatedOutgoingPeers;
}
// Copy over the bandwidth
let bandwidth = { uplink: Infinity, downlink: Infinity };
bandwidth = Object.assign(bandwidth, network.peers[peer].bandwidth);
network.peers[peer].bandwidth = bandwidth;
if (network.peers[peer].downloadMBPS !== undefined && network.peers[peer].uploadMBPS !== undefined) {
delete network.peers[peer].downloadMBPS;
delete network.peers[peer].uploadMBPS; // Backward compatibility
}
delete network.peers[peer].networkBufferManager; // Clean up old version data
// Make the blocks in the blockchain an actual Header object
for (let block in network.peers[peer].blockchain) {
let header = new Header();
header = Object.assign(header, network.peers[peer].blockchain[block]);
network.peers[peer].blockchain[block] = header;
}
// Recreate the networkBuffer
const networkBuffer = new NetworkBuffer(miner.ID);
network.peers[peer].networkBuffer = networkBuffer;
}
return network;
} catch (e) {
if (quiet) return null;
console.error(e);
alert('Error deserializing the network. See console for more information.');
return null;
}
}
}
// This class represents a header of a block in a blockchain. It contains information such as the solver, height, timestamp, nonce, hash, previous hash, and balances. It also has methods for cloning the header, generating a nonce, and checking if the header is a valid block.
class Header {
constructor() {
this.solver = null;
this.height = null;
this.timestamp = null; // Time block was solved in seconds
if (network != null) this.numBytes = network.blockSize;
else this.numBytes = 1000000;
this.nonce = null;
this.hash = null;
this.prevHash = null;
this.balances = {};
if (network && network.logging.logNetworkData) {
this.numHops = 0;
this.propagationTime = 0;
}
}
clone() {
const newHeader = new Header();
newHeader.solver = this.solver;
newHeader.height = this.height;
newHeader.timestamp = this.timestamp;
newHeader.hash = this.hash;
newHeader.prevHash = this.prevHash;
newHeader.balances = Object.assign({}, this.balances);
if (network.logging.logNetworkData) {
newHeader.numHops = this.numHops;
newHeader.propagationTime = this.propagationTime;
}
return newHeader;
}
static fromObj(obj) {
const block = new Header();
if (obj.solver !== undefined) block.solver = obj.solver;
if (obj.height !== undefined) block.height = obj.height;
if (obj.timestamp !== undefined) block.timestamp = obj.timestamp;
if (obj.numBytes !== undefined) block.numBytes = obj.numBytes;
if (obj.nonce !== undefined) block.nonce = obj.nonce;
if (obj.hash !== undefined) block.hash = obj.hash;
if (obj.prevHash !== undefined) block.prevHash = obj.prevHash;
if (obj.balances !== undefined) block.balances = obj.balances;
if (obj.numHops !== undefined) block.numHops = obj.numHops;
if (obj.propagationTime !== undefined) block.propagationTime = obj.propagationTime;
return block;
}