1 /// Module for handling callbacks from the hashgraph
2 module tagion.hashgraph.Refinement;
3 
4 import tagion.basic.Debug;
5 import tagion.basic.Types : Buffer;
6 import tagion.crypto.Types : Pubkey;
7 import tagion.hashgraph.Event;
8 import tagion.hashgraph.HashGraph;
9 import tagion.hashgraph.HashGraphBasic;
10 import tagion.hashgraph.RefinementInterface;
11 import tagion.hashgraph.Round;
12 import tagion.hibon.Document;
13 import tagion.hibon.HiBON;
14 import tagion.hibon.HiBONRecord;
15 import tagion.logger.Logger;
16 import tagion.services.messages : consensusEpoch;
17 import tagion.utils.BitMask;
18 import tagion.utils.StdTime;
19 
20 // std
21 import std.algorithm : filter, map, reduce, sort, until;
22 import std.array;
23 import std.stdio;
24 import tagion.hibon.HiBONRecord;
25 import tagion.script.standardnames;
26 import tagion.services.options : TaskNames;
27 import tagion.utils.pretend_safe_concurrency;
28 
29 @safe
30 @recordType("finishedEpoch")
31 struct FinishedEpoch {
32     @label("events") const(EventPackage)[] events;
33     @label(StdNames.time) sdt_t time;
34     @label("epoch") long epoch;
35     mixin HiBONRecord!(q{
36         this(const(Event)[] events, sdt_t time, long epoch) pure {
37             this.events = events
38                 .map!((e) => *(e.event_package))
39                 .array;
40 
41             this.time = time;
42             this.epoch = epoch;
43         }
44     });
45 }
46 
47 @safe
48 class StdRefinement : Refinement {
49 
50     static Topic epoch_created = Topic("epoch_creator/epoch_created");
51 
52     enum MAX_ORDER_COUNT = 10; /// Max recursion count for order_less function
53     protected {
54         HashGraph hashgraph;
55         TaskNames task_names;
56     }
57 
58     void setOwner(HashGraph hashgraph)
59     in (this.hashgraph is null)
60     do {
61         this.hashgraph = hashgraph;
62     }
63 
64     void setTasknames(TaskNames task_names) {
65         this.task_names = task_names;
66     }
67 
68     Tid collector_service;
69     void payload(immutable(EventPackage*) epack) {
70         if (!epack.event_body.payload.empty) {
71             // send to collector payload.
72 
73         }
74     }
75 
76     void finishedEpoch(
77             const(Event[]) events,
78     const sdt_t epoch_time,
79     const Round decided_round) {
80         auto event_payload = FinishedEpoch(events, epoch_time, decided_round.number);
81 
82 
83         log(epoch_created, "epoch_succesful", event_payload);
84 
85         if (task_names is TaskNames.init) {
86             return;
87         }
88 
89         immutable(EventPackage*)[] epacks = events
90             .map!((e) => e.event_package)
91             .array;
92 
93         auto transcript_tid = locate(task_names.transcript);
94         if (transcript_tid !is Tid.init) {
95             transcript_tid.send(consensusEpoch(), epacks, cast(immutable(long)) decided_round.number, epoch_time);
96         }
97     }
98 
99     void excludedNodes(ref BitMask excluded_mask) {
100         // should be implemented
101     }
102 
103     void epack(immutable(EventPackage*) epack) {
104         // log.trace("epack.event_body.payload.empty %s", epack.event_body.payload.empty);
105     }
106 
107     void epoch(Event[] event_collection, const(Round) decided_round) {
108 
109         import std.bigint;
110         import std.numeric : gcd;
111         import std.range : back, retro, tee;
112 
113         pragma(msg, "fixme(bbh): move pseudotime out and add function labels");
114         struct PseudoTime {
115             BigInt num; //fraction representing the avg received round
116             BigInt denom;
117             BigInt order; //sum of received orders
118             sdt_t time; //avg received time
119 
120             this(BigInt num, BigInt denom, BigInt order, long time) {
121                 this.num = num;
122                 this.denom = denom;
123                 this.order = order;
124                 this.time = time;
125             }
126 
127             this(int num, int denom, int order, sdt_t time, long round_number, ulong witness_count) {
128                 this.num = BigInt(num + denom * round_number);
129                 this.denom = BigInt(denom * witness_count);
130                 this.order = BigInt(order);
131                 this.time = time / witness_count;
132             }
133 
134             PseudoTime opBinary(string op)(PseudoTime other) if (op == "+") {
135                 BigInt d = gcd(denom, other.denom);
136                 return PseudoTime(other.denom / d * num + denom / d * other.num,
137                         denom / d * other.denom,
138                         order + other.order,
139                         time + other.time);
140             }
141         }
142 
143         const famous_witnesses = decided_round
144             ._events
145             .filter!(e => e !is null)
146             .filter!(e => decided_round.famous_mask[e.node_id])
147             .array;
148 
149         PseudoTime calc_pseudo_time(Event event) {
150             auto receivers = famous_witnesses
151                 .map!(e => e[].until!(e => !e.sees(event))
152                 .array.back);
153 
154             return receivers.map!(e => PseudoTime(e.pseudo_time_counter,
155                     (e[].retro.filter!(e => e._witness)
156                 .front._mother.pseudo_time_counter + 1),
157             e.order,
158             e.event_body.time,
159             e.round.number,
160             decided_round.famous_mask.count))
161                 .array
162                 .reduce!((a, b) => a + b);
163         }
164 
165         bool order_less(Event a, Event b) {
166             PseudoTime at = calc_pseudo_time(a);
167             PseudoTime bt = calc_pseudo_time(b);
168 
169             if (at.num * bt.denom == at.denom * bt.num) {
170                 if (at.order == bt.order) {
171                     if (a.order == b.order) {
172                         return a.fingerprint < b.fingerprint;
173                     }
174                     return a.order < b.order;
175                 }
176                 return at.order < bt.order;
177             }
178             return at.num * bt.denom < at.denom * bt.num;
179         }
180 
181         sdt_t[] times;
182         auto events = event_collection
183             .tee!((e) => times ~= e.event_body.time)
184             .filter!((e) => !e.event_body.payload.empty)
185             .array
186             .sort!((a, b) => order_less(a, b))
187             .release;
188 
189         times.sort;
190         const epoch_time = times[times.length / 2];
191 
192         version(EPOCH_LOG) {
193         log.trace("%s Epoch round %d event.count=%d witness.count=%d event in epoch=%d time=%s",
194                 hashgraph.name, decided_round.number,
195                 Event.count, Event.Witness.count, events.length, epoch_time);
196         }
197         log.trace("event.count=%d witness.count=%d event in epoch=%d", Event.count, Event.Witness.count, events.length);
198 
199         finishedEpoch(events, epoch_time, decided_round);
200 
201         excludedNodes(hashgraph._excluded_nodes_mask);
202     }
203 
204     version (none) //SHOULD NOT BE DELETED SO WE CAN REVERT TO OLD ORDERING IF NEEDED
205     void epoch(Event[] event_collection, const(Round) decided_round) {
206         import std.algorithm;
207         import std.range;
208 
209         bool order_less(const Event a, const Event b, const(int) order_count) @safe {
210             bool rare_less(Buffer a_print, Buffer b_print) {
211                 // rare_order_compare_count++;
212                 pragma(msg, "review(cbr): Concensus order changed");
213                 return a_print < b_print;
214             }
215 
216             if (order_count < 0) {
217                 return rare_less(a.fingerprint, b.fingerprint);
218             }
219             if (a.order is b.order) {
220                 if (a.father && b.father) {
221                     return order_less(a.father, b.father, order_count - 1);
222                 }
223                 if (a.father) {
224                     return true;
225                 }
226                 if (b.father) {
227                     return false;
228                 }
229 
230                 if (!a.isFatherLess && !b.isFatherLess) {
231                     return order_less(a.mother, b.mother, order_count - 1);
232                 }
233 
234                 return rare_less(a.fingerprint, b.fingerprint);
235             }
236             return a.order < b.order;
237         }
238 
239         sdt_t[] times;
240         auto events = event_collection
241             .filter!((e) => e !is null)
242             .tee!((e) => times ~= e.event_body.time)
243             .filter!((e) => !e.event_body.payload.empty)
244             .array
245             .sort!((a, b) => order_less(a, b, MAX_ORDER_COUNT))
246             .release;
247         times.sort;
248         const mid = times.length / 2 + (times.length % 1);
249         const epoch_time = times[mid];
250 
251         log.trace("%s Epoch round %d event.count=%d witness.count=%d event in epoch=%d time=%s",
252                 hashgraph.name, decided_round.number,
253                 Event.count, Event.Witness.count, events.length, epoch_time);
254 
255         finishedEpoch(events, epoch_time, decided_round);
256 
257         excludedNodes(hashgraph._excluded_nodes_mask);
258     }
259 
260 }
261 
262 @safe
263 struct RoundFingerprint {
264     Buffer[] fingerprints;
265     mixin HiBONRecord;
266 }
267 
268 @safe
269 const(RoundFingerprint) hashLastDecidedRound(const Round last_decided_round) pure nothrow {
270     import std.algorithm : filter;
271 
272     RoundFingerprint round_fingerprint;
273     round_fingerprint.fingerprints = last_decided_round.events
274         .filter!(e => e !is null)
275         .map!(e => cast(Buffer) e.event_package.fingerprint)
276         .array
277         .sort
278         .array;
279     return round_fingerprint;
280 }