1 /// Consensus HashGraph main object 
2 module tagion.testbench.hashgraph.hashgraph_test_network;
3 
4 import core.memory : pageSize;
5 import std.algorithm;
6 import std.conv;
7 import std.exception : assumeWontThrow;
8 import std.format;
9 import std.range;
10 import std.stdio;
11 import std.typecons;
12 import tagion.basic.Types : Buffer;
13 import tagion.communication.HiRPC;
14 import tagion.crypto.Types;
15 import tagion.hashgraph.Event;
16 import tagion.hashgraph.HashGraph;
17 import tagion.hashgraph.HashGraphBasic;
18 import tagion.hashgraph.Refinement;
19 import tagion.hashgraph.Round;
20 import tagion.hibon.Document;
21 import tagion.hibon.HiBON;
22 import tagion.hibon.HiBONRecord : isHiBONRecord;
23 import tagion.logger.Logger : log;
24 import tagion.services.options;
25 import tagion.utils.BitMask;
26 import tagion.utils.Miscellaneous : cutHex;
27 import tagion.utils.StdTime;
28 
29 class TestRefinement : StdRefinement {
30 
31     struct ExcludedNodesHistory {
32         Pubkey pubkey;
33         bool state;
34         int round;
35         bool stop_communication;
36     }
37 
38     static ExcludedNodesHistory[] excluded_nodes_history;
39 
40     struct Swap {
41         Pubkey swap_out;
42         Pubkey swap_in;
43         int round;
44     }
45 
46     static Swap swap;
47 
48     struct Epoch {
49         Event[] events;
50         sdt_t epoch_time;
51         Round decided_round;
52     }
53 
54     static Epoch[][Pubkey] epoch_events;
55     override void finishedEpoch(const(Event[]) events, const sdt_t epoch_time, const Round decided_round) {
56 
57         auto epoch = (() @trusted => Epoch(cast(Event[]) events, epoch_time, cast(Round) decided_round))();
58         epoch_events[hashgraph.owner_node.channel] ~= epoch;
59     }
60 
61     // override void excludedNodes(ref BitMask excluded_mask) {
62     //     import tagion.basic.Debug;
63     //     import std.algorithm : filter;
64 
65     //     if (excluded_nodes_history is null) {
66     //         return;
67     //     }
68 
69     //     const last_decided_round = hashgraph.rounds.last_decided_round.number;
70 
71     //     auto histories = excluded_nodes_history.filter!(h => h.round == last_decided_round);
72     //     foreach (history; histories) {
73     //         const node = hashgraph.nodes.get(history.pubkey, HashGraph.Node.init);
74     //         if (node !is HashGraph.Node.init) {
75     //             excluded_mask[node.node_id] = history.state;
76     //             __write("setting exclude mask");
77     //         }
78     //     }
79     //     __write("callback<%s>", excluded_mask);
80 
81     // }
82 
83 }
84 
85 /++
86     This function makes sure that the HashGraph has all the events connected to this event
87 +/
88 @safe
89 static class TestNetwork { //(NodeList) if (is(NodeList == enum)) {
90     import core.thread.fiber : Fiber;
91     import core.time;
92     import std.datetime.systime : SysTime;
93     import tagion.crypto.SecureInterfaceNet : SecureNet;
94     import tagion.crypto.SecureNet : StdSecureNet;
95     import tagion.gossip.InterfaceNet : GossipNet;
96     import tagion.hibon.HiBONJSON;
97     import tagion.utils.Queue;
98     import tagion.utils.Random;
99 
100     TestGossipNet authorising;
101     Random!size_t random;
102     SysTime global_time;
103     enum timestep {
104         MIN = 50,
105         MAX = 150
106     }
107 
108     static const(SecureNet) verify_net;
109     static this() {
110         verify_net = new StdSecureNet();
111     }
112 
113     Pubkey current;
114 
115     alias ChannelQueue = Queue!Document;
116     class TestGossipNet : GossipNet {
117         import tagion.hashgraph.HashGraphBasic;
118 
119         ChannelQueue[Pubkey] channel_queues;
120         sdt_t _current_time;
121 
122         static bool[Pubkey] online_states;
123 
124         void start_listening() {
125             // empty
126         }
127 
128         @property
129         void time(const(sdt_t) t) {
130             _current_time = sdt_t(t);
131         }
132 
133         @property
134         const(sdt_t) time() pure const {
135             return _current_time;
136         }
137 
138         bool isValidChannel(const(Pubkey) channel) const pure nothrow {
139             return (channel in channel_queues) !is null;
140         }
141 
142         void send(const(Pubkey) channel, const(HiRPC.Sender) sender) {
143             if (online_states !is null && !online_states[channel]) {
144                 return;
145             }
146 
147             const doc = sender.toDoc;
148             channel_queues[channel].write(doc);
149         }
150 
151         void send(const(Pubkey) channel, const(Document) doc) nothrow {
152             if (online_states !is null && !online_states[channel]) {
153                 return;
154             }
155 
156             channel_queues[channel].write(doc);
157         }
158 
159         final void send(T)(const(Pubkey) channel, T pack) if (isHiBONRecord!T) {
160             if (online_states !is null && !online_states[channel]) {
161                 return;
162             }
163 
164             send(channel, pack.toDoc);
165         }
166 
167         const(Document) receive(const Pubkey channel) nothrow {
168             return channel_queues[channel].read;
169         }
170 
171         void close() {
172             // Dummy empty
173         }
174 
175         const(Pubkey) select_channel(ChannelFilter channel_filter) {
176             foreach (count; 0 .. channel_queues.length / 2) {
177 
178                 const node_index = random.value(0, channel_queues.length);
179 
180                 auto send_channels = channel_queues
181                     .byKey
182                     .dropExactly(node_index)
183                     .filter!((k) => online_states[k]);
184 
185                 if (!send_channels.empty && channel_filter(send_channels.front)) {
186                     return send_channels.front;
187                 }
188             }
189             return Pubkey();
190         }
191 
192         const(Pubkey) gossip(
193                 ChannelFilter channel_filter,
194                 SenderCallBack sender) {
195             const send_channel = select_channel(channel_filter);
196             if (send_channel.length) {
197                 send(send_channel, sender());
198             }
199             return send_channel;
200         }
201 
202         bool empty(const Pubkey channel) const pure nothrow {
203             return channel_queues[channel].empty;
204         }
205 
206         void add_channel(const Pubkey channel) {
207             channel_queues[channel] = new ChannelQueue;
208         }
209 
210         void remove_channel(const Pubkey channel) {
211             channel_queues.remove(channel);
212         }
213     }
214 
215     class FiberNetwork : Fiber {
216         HashGraph _hashgraph;
217         //immutable(string) name;
218         @trusted
219         this(HashGraph h, const(ulong) stacksize = pageSize * Fiber.defaultStackPages) nothrow
220         in {
221             assert(_hashgraph is null);
222         }
223         do {
224             super(&run, stacksize);
225             _hashgraph = h;
226         }
227 
228         const(HashGraph) hashgraph() const pure nothrow {
229             return _hashgraph;
230         }
231 
232         sdt_t time() {
233             const systime = global_time + random.value(timestep.MIN, timestep.MAX).msecs;
234             const sdt_time = sdt_t(systime.stdTime);
235             return sdt_time;
236         }
237 
238         private void run() {
239             { // Eva Event
240                 immutable buf = cast(Buffer) _hashgraph.channel;
241                 const nonce = cast(Buffer) _hashgraph.hirpc.net.calcHash(buf);
242                 writefln("NODE SIZE OF TEST HASHGRAPH %s", _hashgraph.node_size);
243                 auto eva_event = _hashgraph.createEvaEvent(time, nonce);
244 
245             }
246             uint count;
247             bool stop;
248 
249             const(Document) payload() @safe {
250                 auto h = new HiBON;
251                 h["node"] = format("%s-%d", _hashgraph.name, count);
252                 return Document(h);
253             }
254 
255             while (!stop) {
256                 while (!authorising.empty(_hashgraph.channel)) {
257                     if (current !is Pubkey.init && TestGossipNet.online_states !is null && !TestGossipNet.online_states[current]) {
258                         (() @trusted { yield; })();
259                     }
260 
261                     const received = _hashgraph.hirpc.receive(
262                             authorising.receive(_hashgraph.channel));
263                     _hashgraph.wavefront(
264                             received,
265                             time,
266                             (const(HiRPC.Sender) return_wavefront) @safe {
267                         authorising.send(received.pubkey, return_wavefront);
268                     },
269                             &payload
270                     );
271                     count++;
272                 }
273                 (() @trusted { yield; })();
274                 //const onLine=_hashgraph.areWeOnline;
275                 const init_tide = random.value(0, 2) is 1;
276                 if (init_tide) {
277                     _hashgraph.init_tide(
278                             &authorising.gossip,
279                             &payload,
280                             time);
281                     count++;
282                 }
283             }
284         }
285     }
286 
287     @trusted
288     const(Pubkey[]) channels() const pure nothrow {
289         return networks.keys;
290     }
291 
292     bool allInGraph() {
293         return networks
294             .byValue
295             .map!(n => n._hashgraph.areWeInGraph)
296             .all!(s => s);
297     }
298 
299     static int testing;
300     void addNode(immutable(ulong) N, const(string) name, const Flag!"joining" joining = No.joining) {
301         immutable passphrase = format("very secret %s", name);
302         auto net = new StdSecureNet();
303         net.generateKeyPair(passphrase);
304         auto refinement = new TestRefinement;
305 
306         auto h = new HashGraph(N, net, refinement, &authorising.isValidChannel, joining, name);
307         if (testing < 4) {
308             testing++;
309             if (testing == 1) {
310                 h.__debug_print = true;
311             }
312         }
313         h.scrap_depth = 0;
314         writefln("Adding Node: %s with %s", name, net.pubkey.cutHex);
315         networks[net.pubkey] = new FiberNetwork(h, pageSize * 1024);
316 
317         authorising.add_channel(net.pubkey);
318         TestGossipNet.online_states[net.pubkey] = true;
319     }
320 
321     FiberNetwork[Pubkey] networks;
322     this(const(string[]) node_names) {
323         authorising = new TestGossipNet;
324         immutable N = node_names.length; //EnumMembers!NodeList.length;
325         node_names.each!(name => addNode(N, name));
326     }
327 }
328 
329 import tagion.hashgraphview.Compare;
330 
331 bool event_error(const Event e1, const Event e2, const Compare.ErrorCode code) @safe nothrow {
332     static string print(const Event e) nothrow {
333         if (e) {
334             const round_received = (e.round_received) ? e.round_received.number.to!string : "#";
335             return assumeWontThrow(format("(%d:%d:%d:r=%d:rr=%s:%s)",
336                     e.id, e.node_id, e.altitude, e.round.number, round_received,
337                     e.fingerprint.cutHex));
338         }
339         return assumeWontThrow(format("(%d:%d:%s:%s)", 0, -1, 0, "nil"));
340     }
341 
342     assumeWontThrow(writefln("Event %s and %s %s", print(e1), print(e2), code));
343     return false;
344 }
345 
346 @safe
347 void printStates(TestNetwork network) {
348     foreach (channel; network.networks) {
349         writeln("----------------------");
350         foreach (channel_key; network.channels) {
351             const current_hashgraph = network.networks[channel_key]._hashgraph;
352             // writef("%16s %10s ingraph:%5s|", channel_key.cutHex, current_hashgraph.owner_node.sticky_state, current_hashgraph.areWeInGraph);
353             foreach (receiver_key; network.channels) {
354                 const node = current_hashgraph.nodes.get(receiver_key, null);
355                 const state = (node is null) ? ExchangeState.NONE : node.state;
356                 writef("%15s %s", state, node is null ? "X" : " ");
357             }
358             writeln;
359         }
360     }
361 
362 }