1 /// HashGraph basic support functions 2 module tagion.hashgraph.HashGraphBasic; 3 4 import std.exception : assumeWontThrow; 5 import std.format; 6 import std.stdio; 7 import std.traits : isSigned; 8 import std.typecons : TypedefType; 9 import tagion.basic.ConsensusExceptions : ConsensusException, GossipConsensusException, convertEnum; 10 import tagion.basic.Types : Buffer; 11 import tagion.basic.basic : EnumText; 12 import tagion.communication.HiRPC : HiRPC; 13 import tagion.crypto.SecureInterfaceNet : SecureNet; 14 import tagion.crypto.Types : Fingerprint, Pubkey, Signature; 15 import tagion.crypto.Types; 16 import tagion.hashgraph.Event; 17 import tagion.hashgraph.HashGraph : HashGraph; 18 import tagion.hibon.Document : Document; 19 import tagion.hibon.HiBON : HiBON; 20 import tagion.hibon.HiBONJSON : JSONString; 21 import tagion.hibon.HiBONRecord; 22 import tagion.utils.BitMask; 23 import tagion.utils.StdTime; 24 25 enum minimum_nodes = 3; 26 import tagion.utils.Miscellaneous : cutHex; 27 28 @safe @nogc 29 T highest(T)(T a, T b) pure nothrow if (isSigned!(T)) { 30 return higher(a, b) ? a : b; 31 } 32 33 @safe @nogc 34 bool higher(T)(T a, T b) pure nothrow if (isSigned!(T)) { 35 return a - b > 0; 36 } 37 38 @safe 39 unittest { // Test of the altitude measure function 40 int x = int.max - 10; 41 int y = x + 20; 42 assert(x > 0); 43 assert(y < 0); 44 assert(highest(x, y) == y); 45 assert(higher(y, x)); 46 assert(!higher(x, x)); 47 } 48 49 /** 50 * Calculates the majority votes 51 * Params: 52 * voting = Number of votes 53 * node_size = Total number of votes 54 * Returns: 55 * Returns `true` if the votes are more than 2/3 56 */ 57 @safe @nogc 58 bool isMajority(const size_t voting, const size_t node_size) pure nothrow { 59 return (node_size >= minimum_nodes) && (3 * voting > 2 * node_size); 60 } 61 62 @safe 63 unittest { 64 65 int[] some_array; 66 assert(!isMajority(some_array.length, ulong(5))); 67 68 69 } 70 71 bool isMajority(T, S)(T voting, S node_size) pure nothrow { 72 return (node_size >= minimum_nodes) && (3 * voting > 2 * node_size); 73 } 74 75 76 @safe @nogc 77 bool isMajority(const(BitMask) mask, const HashGraph hashgraph) pure nothrow { 78 return isMajority(mask.count, hashgraph.node_size); 79 } 80 81 @safe @nogc 82 bool isAllVotes(const(BitMask) mask, const HashGraph hashgraph) pure nothrow { 83 return mask.count is hashgraph.node_size; 84 } 85 86 enum int eva_altitude = -77; 87 @safe @nogc 88 int nextAltitide(const Event event) pure nothrow { 89 return (event) ? event.altitude + 1 : eva_altitude; 90 } 91 92 protected enum _params = [ 93 "events", 94 "size", 95 ]; 96 97 mixin(EnumText!("Params", _params)); 98 99 enum ExchangeState : uint { 100 NONE, 101 INIT_TIDE, 102 TIDAL_WAVE, 103 FIRST_WAVE, 104 SECOND_WAVE, 105 BREAKING_WAVE, 106 SHARP, 107 RIPPLE, /// Ripple is used the first time a node connects to the network 108 COHERENT, /** Coherent state is when an the least epoch wavefront has been received or 109 if all the nodes isEva notes (This only occurs at genesis). 110 */ 111 112 113 114 } 115 116 alias convertState = convertEnum!(ExchangeState, GossipConsensusException); 117 118 @safe 119 struct EventBody { 120 enum int eva_altitude = -77; 121 import tagion.basic.ConsensusExceptions; 122 123 protected alias check = Check!HashGraphConsensusException; 124 import std.traits : OriginalType, Unqual, getSymbolsByUDA, hasMember; 125 126 @label("$p") @optional @filter(q{!a.empty}) Document payload; // Transaction 127 @label("$m") @optional @(filter.Initialized) Buffer mother; // Hash of the self-parent 128 @label("$f") @optional @(filter.Initialized) Buffer father; // Hash of the other-parent 129 @label("$a") int altitude; 130 @label("$t") sdt_t time; 131 bool verify() { 132 return (father is null) ? true : (mother !is null); 133 } 134 135 mixin HiBONRecord!( 136 q{ 137 this( 138 Document payload, 139 const Event mother, 140 const Event father, 141 lazy const sdt_t time) inout { 142 this.time = time; 143 this.mother = (mother is null)?null:mother.fingerprint; 144 this.father = (father is null)?null:father.fingerprint; 145 this.payload = payload; 146 this.altitude = mother.nextAltitide; 147 consensus(); 148 } 149 150 package this( 151 Document payload, 152 const Buffer mother_fingerprint, 153 const Buffer father_fingerprint, 154 const int altitude, 155 lazy const sdt_t time) inout { 156 this.time = time; 157 this.mother = mother_fingerprint; 158 this.father = father_fingerprint; 159 this.payload = payload; 160 this.altitude = altitude; 161 consensus(); 162 } 163 164 165 }); 166 167 invariant { 168 if ((mother.length != 0) && (father.length != 0)) { 169 assert(mother.length == father.length); 170 } 171 } 172 173 @nogc 174 bool isEva() pure const nothrow { 175 return (mother.length == 0); 176 } 177 178 immutable(EventBody) eva(); 179 180 void consensus() inout { 181 if (mother.length == 0) { 182 // Seed event first event in the chain 183 check(father.length == 0, ConsensusFailCode.NO_MOTHER); 184 } 185 else { 186 if (father.length != 0) { 187 // If the Event has a father 188 check(mother.length == father.length, ConsensusFailCode.MOTHER_AND_FATHER_SAME_SIZE); 189 } 190 check(mother != father, ConsensusFailCode.MOTHER_AND_FATHER_CAN_NOT_BE_THE_SAME); 191 } 192 } 193 } 194 195 @safe 196 struct EventPackage { 197 @exclude Buffer fingerprint; 198 @label("$sign") Signature signature; 199 @label("$pkey") Pubkey pubkey; 200 @label("$body") EventBody event_body; 201 202 mixin HiBONRecord!( 203 q{ 204 import tagion.basic.ConsensusExceptions : ConsensusCheck = Check, ConsensusFailCode, EventConsensusException; 205 protected alias consensus_check=ConsensusCheck!EventConsensusException; 206 import std.stdio; 207 /++ 208 Used when a Event is receved from another node 209 +/ 210 this(const SecureNet net, const(Document) doc_epack) immutable { 211 immutable _this=EventPackage(doc_epack); 212 //this(doc_epack); 213 this.signature=_this.signature; 214 this.pubkey=_this.pubkey; 215 this.event_body=_this.event_body; 216 fingerprint=cast(Buffer)net.calcHash(_this.event_body); 217 consensus_check(pubkey.length !is 0, ConsensusFailCode.EVENT_MISSING_PUBKEY); 218 consensus_check(signature.length !is 0, ConsensusFailCode.EVENT_MISSING_SIGNATURE); 219 consensus_check(net.verify(Fingerprint(fingerprint), signature, pubkey), ConsensusFailCode.EVENT_BAD_SIGNATURE); 220 } 221 222 /++ 223 Create a EventPackage from a body 224 +/ 225 this(const SecureNet net, immutable(EventBody) ebody) immutable { 226 pubkey=net.pubkey; 227 event_body=ebody; 228 auto sig = net.sign(event_body); 229 signature = sig.signature; 230 fingerprint = cast(Buffer) sig.message; 231 } 232 233 this(const SecureNet net, const Pubkey pkey, const Signature signature, immutable(EventBody) ebody) { 234 pubkey=pkey; 235 event_body=ebody; 236 auto _fingerprint=net.calcHash(event_body); 237 fingerprint = cast(Buffer) _fingerprint; 238 this.signature=signature; 239 consensus_check(net.verify(_fingerprint, signature, pubkey), 240 ConsensusFailCode.EVENT_BAD_SIGNATURE); 241 } 242 }); 243 } 244 245 alias Tides = int[Pubkey]; 246 247 @recordType("Wavefront") @safe 248 struct Wavefront { 249 @label("$tides") @optional @filter(q{a.length is 0}) private Tides _tides; 250 @label("$events") @optional @filter(q{a.length is 0}) const(immutable(EventPackage)*[]) epacks; 251 @label("$state") ExchangeState state; 252 enum tidesName = GetLabel!(_tides).name; 253 enum epacksName = GetLabel!(epacks).name; 254 enum stateName = GetLabel!(state).name; 255 256 mixin HiBONRecordType; 257 mixin JSONString; 258 259 this(Tides tides) pure nothrow { 260 _tides = tides; 261 epacks = null; 262 state = ExchangeState.TIDAL_WAVE; 263 } 264 265 this(immutable(EventPackage)*[] epacks, Tides tides, const ExchangeState state) pure nothrow { 266 this.epacks = epacks; 267 this._tides = tides; 268 this.state = state; 269 } 270 271 private struct LoadTides { 272 @label(tidesName) Tides tides; 273 mixin HiBONRecord!( 274 q{ 275 this(const(Tides) _tides) const { 276 tides=_tides; 277 } 278 }); 279 } 280 281 this(const SecureNet net, const Document doc) { 282 state = doc[stateName].get!ExchangeState; 283 immutable(EventPackage)*[] event_packages; 284 if (doc.hasMember(epacksName)) { 285 const sub_doc = doc[epacksName].get!Document; 286 foreach (e; sub_doc[]) { 287 (() @trusted { immutable epack = new immutable(EventPackage)(net, e.get!Document); event_packages ~= epack; })(); 288 } 289 } 290 epacks = event_packages; 291 if (doc.hasMember(tidesName)) { 292 auto load_tides = LoadTides(doc); 293 _tides = load_tides.tides; 294 } 295 update_tides; 296 } 297 298 const(Document) toDoc() const { 299 auto h = new HiBON; 300 h[stateName] = state; 301 if (epacks.length) { 302 auto epacks_hibon = new HiBON; 303 foreach (i, epack; epacks) { 304 epacks_hibon[i] = epack.toDoc; 305 } 306 h[epacksName] = epacks_hibon; 307 } 308 if (_tides.length) { 309 const load_tides = const(LoadTides)(_tides); 310 h[tidesName] = load_tides.toDoc[tidesName].get!Document; 311 } 312 return Document(h); 313 } 314 315 private void update_tides() pure nothrow { 316 foreach (e; epacks) { 317 _tides.update(e.pubkey, 318 { return e.event_body.altitude; }, 319 (int altitude) { return highest(altitude, e.event_body.altitude); }); 320 } 321 } 322 323 @nogc 324 const(Tides) tides() const pure nothrow { 325 return _tides; 326 } 327 } 328 329 @safe 330 struct EvaPayload { 331 @label("$channel") Pubkey channel; 332 @label("$nonce") Buffer nonce; 333 mixin HiBONRecord!( 334 q{ 335 this(const Pubkey channel, const Buffer nonce) pure { 336 this.channel=channel; 337 this.nonce=nonce; 338 } 339 } 340 ); 341 } 342 343 static assert(isHiBONRecord!Wavefront); 344 static assert(isHiBONRecord!(EventPackage)); 345 static assert(isHiBONRecord!(immutable(EventPackage)));