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 }