1 module tagion.testbench.hashgraph.synchron_network;
2 // Default import list for bdd
3 import core.sys.posix.sys.resource;
4 import std.algorithm;
5 import std.array;
6 import std.array;
7 import std.conv;
8 import std.datetime;
9 import std.exception;
10 import std.format;
11 import std.functional : toDelegate;
12 import std.path : buildPath;
13 import std.path : extension, setExtension;
14 import std.range;
15 import std.stdio;
16 import std.typecons : Tuple;
17 import tagion.basic.Types : FileExtension;
18 import tagion.basic.Types;
19 import tagion.basic.basic;
20 import tagion.behaviour;
21 import tagion.crypto.Types : Pubkey;
22 import tagion.hashgraph.Event;
23 import tagion.hashgraph.HashGraph;
24 import tagion.hashgraph.HashGraphBasic;
25 import tagion.hashgraph.Refinement;
26 import tagion.hashgraphview.Compare;
27 import tagion.hibon.Document;
28 import tagion.hibon.HiBONJSON;
29 import tagion.testbench.hashgraph.hashgraph_test_network;
30 import tagion.testbench.tools.Environment;
31 import tagion.utils.Miscellaneous : cutHex;
32 
33 enum feature = Feature(
34             "Bootstrap of hashgraph",
35             []);
36 
37 alias FeatureContext = Tuple!(
38         StartNetworkWithNAmountOfNodes, "StartNetworkWithNAmountOfNodes",
39         FeatureGroup*, "result"
40 );
41 
42 @safe @Scenario("Start network with n amount of nodes",
43         [])
44 class StartNetworkWithNAmountOfNodes {
45     string[] node_names;
46     TestNetwork network;
47     string module_path;
48     uint MAX_CALLS;
49     this(string[] node_names, const uint calls, const(string) module_path) {
50         this.node_names = node_names;
51         this.module_path = module_path;
52         MAX_CALLS = cast(uint) node_names.length * calls;
53     }
54 
55     bool coherent;
56 
57     @Given("i have a HashGraph TestNetwork with n number of nodes")
58     Document nodes() {
59         rlimit limit;
60         (() @trusted { getrlimit(RLIMIT_STACK, &limit); })();
61         writefln("RESOURCE LIMIT = %s", limit);
62 
63         network = new TestNetwork(node_names);
64         network.networks.byValue.each!((ref _net) => _net._hashgraph.scrap_depth = 0);
65         network.random.seed(123456789);
66         writeln(network.random);
67 
68         network.global_time = SysTime.fromUnixTime(1_614_355_286);
69 
70         return result_ok;
71     }
72 
73     @When("the network has started")
74     Document started() {
75 
76         foreach (channel; network.channels) {
77             auto current = network.networks[channel];
78             (() @trusted { current.call; })();
79         }
80         return result_ok;
81 
82     }
83 
84     @When("all nodes are sending ripples")
85     Document ripples() {
86         foreach (i; 0 .. MAX_CALLS) {
87             const channel_number = network.random.value(0, network.channels.length);
88             const channel = network.channels[channel_number];
89             auto current = network.networks[channel];
90             (() @trusted { current.call; })();
91 
92             // printStates(network);
93             if (network.allInGraph) {
94                 coherent = true;
95                 break;
96             }
97         }
98 
99         return result_ok;
100     }
101 
102     @When("all nodes are coherent")
103     Document _coherent() {
104         check(coherent, "Nodes not coherent");
105         return result_ok;
106     }
107 
108     @Then("wait until the first epoch")
109     Document epoch() @trusted {
110         {
111             uint i = 0;
112             while (i < MAX_CALLS) {
113 
114                 const channel_number = network.random.value(0, network.channels.length);
115                 network.current = Pubkey(network.channels[channel_number]);
116                 auto current = network.networks[network.current];
117                 (() @trusted { current.call; })();
118 
119                 // if (network.epoch_events.length == node_names.length) {
120                 //     // all nodes have created at least one epoch
121                 //     break;
122                 // }
123                 // printStates(network);
124                 i++;
125             }
126             check(TestRefinement.epoch_events.length == node_names.length,
127                     format("Max calls %d reached, not all nodes have created epochs only %d",
128                     MAX_CALLS, TestRefinement.epoch_events.length));
129         }
130 
131         // compare ordering
132         auto names = network.networks.byValue
133             .map!((net) => net._hashgraph.name)
134             .array.dup
135             .sort
136             .array;
137 
138         HashGraph[string] hashgraphs;
139         foreach (net; network.networks) {
140             hashgraphs[net._hashgraph.name] = net._hashgraph;
141         }
142         foreach (i, name_h1; names[0 .. $ - 1]) {
143             const h1 = hashgraphs[name_h1];
144             foreach (name_h2; names[i + 1 .. $]) {
145                 const h2 = hashgraphs[name_h2];
146                 auto comp = Compare(h1, h2, toDelegate(&event_error));
147                 // writefln("%s %s round_offset=%d order_offset=%d",
148                 //     h1.name, h2.name, comp.round_offset, comp.order_offset);
149                 const result = comp.compare;
150                 check(result, format("HashGraph %s and %s is not the same", h1.name, h2.name));
151             }
152         }
153 
154         // compare epochs
155         foreach (i, compare_epoch; TestRefinement.epoch_events.byKeyValue.front.value) {
156             auto compare_events = compare_epoch
157                 .events
158                 .map!(e => cast(Buffer) e.event_package.fingerprint)
159                 .array;
160             auto compare_round_fingerprint = hashLastDecidedRound(compare_epoch.decided_round);
161 
162             // compare_events.sort!((a,b) => a < b);
163             // compare_events.each!writeln;
164             // writefln("%s", compare_events.map!(f => f.cutHex));
165             foreach (channel_epoch; TestRefinement.epoch_events.byKeyValue) {
166                 // writefln("epoch: %s", i);
167                 if (channel_epoch.value.length - 1 < i) {
168                     break;
169                 }
170                 auto events = channel_epoch.value[i]
171                     .events
172                     .map!(e => cast(Buffer) e.event_package.fingerprint)
173                     .array;
174                 auto channel_round_fingerprint = hashLastDecidedRound(compare_epoch.decided_round);
175                 // events.sort!((a,b) => a < b);
176 
177                 // writefln("%s", events.map!(f => f.cutHex));
178                 // events.each!writeln;
179                 // writefln("channel %s time: %s", channel_epoch.key.cutHex, channel_epoch.value[i].epoch_time);
180 
181                 check(compare_events.length == events.length, "event_packages not the same length");
182 
183                 const sameEvents = equal(compare_events.sort, events.sort);
184                 check(sameEvents, "events lists does not contain same events");
185 
186                 const isSame = equal(compare_events, events);
187                 // writefln("isSame: %s", isSame);
188                 check(isSame, "event_packages not the same");
189 
190                 const sameRoundFingerprints = equal(compare_round_fingerprint.fingerprints, channel_round_fingerprint
191                         .fingerprints);
192                 check(sameRoundFingerprints, "round fingerprints not the same");
193             }
194         }
195 
196         return result_ok;
197     }
198 
199     @Then("stop the network")
200     Document _network() {
201         Pubkey[string] node_labels;
202         foreach (channel, _net; network.networks) {
203             node_labels[_net._hashgraph.name] = channel;
204         }
205         foreach (_net; network.networks) {
206             const filename = buildPath(module_path, "ripple-" ~ _net._hashgraph.name.setExtension(FileExtension.hibon));
207             writeln(filename);
208             _net._hashgraph.fwrite(filename, node_labels);
209         }
210         return result_ok;
211     }
212 
213 }