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)));