1 module tagion.testbench.services.epoch_creator;
2 // Default import list for bdd
3 import core.thread;
4 import core.time;
5 import std.algorithm;
6 import std.array;
7 import std.format;
8 import std.range : empty;
9 import std.stdio;
10 import std.typecons : Tuple;
11 import tagion.actor;
12 import tagion.actor.exceptions;
13 import tagion.behaviour;
14 import tagion.crypto.SecureInterfaceNet : SecureNet;
15 import tagion.crypto.SecureNet : StdSecureNet;
16 import tagion.crypto.Types : Pubkey;
17 import tagion.gossip.AddressBook : NodeAddress, addressbook;
18 import tagion.hashgraph.HashGraphBasic;
19 import tagion.hibon.Document;
20 import tagion.hibon.HiBON;
21 import tagion.hibon.HiBONJSON;
22 import tagion.logger.LogRecords : LogInfo;
23 import tagion.logger.Logger;
24 import tagion.services.epoch_creator;
25 import tagion.services.messages;
26 import tagion.services.monitor;
27 import tagion.services.options;
28 import tagion.services.options : NetworkMode;
29 import tagion.testbench.actor.util;
30 import tagion.testbench.tools.Environment;
31 import tagion.utils.Miscellaneous : cutHex;
32 import tagion.utils.pretend_safe_concurrency;
33 
34 enum feature = Feature(
35             "EpochCreator service",
36             [
37         "This service is responsbile for resolving the Hashgraph and producing a consensus ordered list of events, an Epoch."
38 ]);
39 
40 alias FeatureContext = Tuple!(
41         SendPayloadAndCreateEpoch, "SendPayloadAndCreateEpoch",
42         FeatureGroup*, "result"
43 );
44 
45 @safe @Scenario("Send payload and create epoch",
46         [])
47 class SendPayloadAndCreateEpoch {
48     struct Node {
49         shared(StdSecureNet) node_net;
50         string name;
51         EpochCreatorOptions opts;
52         MonitorOptions monitor_opts;
53     }
54 
55     immutable(size_t) number_of_nodes;
56 
57     Node[] nodes;
58     ActorHandle[] handles;
59     Document send_payload;
60 
61     this(EpochCreatorOptions epoch_creator_options, MonitorOptions monitor_opts, immutable size_t number_of_nodes) {
62         import tagion.services.options;
63 
64         this.number_of_nodes = number_of_nodes;
65 
66         addressbook.number_of_active_nodes = number_of_nodes;
67         foreach (i; 0 .. number_of_nodes) {
68             immutable prefix = format("Node_%s", i);
69             immutable task_names = TaskNames(prefix);
70             auto net = new StdSecureNet();
71             net.generateKeyPair(task_names.epoch_creator);
72             shared shared_net = (() @trusted => cast(shared) net)();
73             scope (exit) {
74                 net = null;
75             }
76             writefln("node task name %s", task_names.epoch_creator);
77             auto monitor_local_options = monitor_opts;
78             nodes ~= Node(shared_net, task_names.epoch_creator, epoch_creator_options, monitor_local_options);
79             addressbook[net.pubkey] = NodeAddress(task_names.epoch_creator);
80         }
81 
82     }
83 
84     @Given("I have 5 nodes and start them in mode0")
85     Document mode0() @trusted {
86         register("epoch_creator_tester", thisTid);
87 
88         foreach (n; nodes) {
89             handles ~= spawn!EpochCreatorService(
90                     cast(immutable) n.name,
91                     cast(immutable) n.opts,
92                     NetworkMode.INTERNAL,
93                     number_of_nodes,
94                     n.node_net,
95                     cast(immutable) n.monitor_opts,
96                     TaskNames(),
97             );
98         }
99 
100         waitforChildren(Ctrl.ALIVE, 15.seconds);
101 
102         return result_ok;
103     }
104 
105     @When("i sent a payload to node0")
106     Document node0() @trusted {
107         log.registerSubscriptionTask("epoch_creator_tester");
108 
109         submask.subscribe("epoch_creator/epoch_created");
110 
111         import tagion.hibon.Document;
112         import tagion.hibon.HiBON;
113 
114         auto h = new HiBON;
115         h["node0"] = "TEST PAYLOAD";
116         send_payload = Document(h);
117         writefln("SENDING TEST DOC");
118         handles[1].send(Payload(), const Document(h));
119 
120         return result_ok;
121     }
122 
123     @Then("all the nodes should create an epoch containing the payload")
124     Document payload() {
125         writefln("BEFORE TIMEOUT");
126 
127         bool stop;
128         const max_attempts = 30;
129         uint counter;
130         do {
131             const received = receiveOnlyTimeout!(LogInfo, const(Document))(27.seconds);
132             check(received[0].symbol_name.canFind("epoch_succesful"), "Event should have been epoch_succesful");
133             const epoch = received[1];
134 
135             import tagion.hashgraph.Refinement : FinishedEpoch;
136             import tagion.hibon.HiBONRecord;
137 
138             check(epoch.isRecord!FinishedEpoch, "received event should be an FinishedEpoch record");
139             const events = FinishedEpoch(epoch).events;
140             writefln("Received epoch %s \n event_length %s", epoch.toPretty, events.length);
141 
142             if (events.length == 1) {
143                 const received_payload = events[0].event_body.payload;
144                 check(received_payload == send_payload, "Payloads not the same");
145                 stop = true;
146             }
147             counter++;
148         }
149         while (!stop && counter < max_attempts);
150         check(stop, "no epoch found");
151 
152         foreach (handle; handles) {
153             handle.send(Sig.STOP);
154         }
155 
156         waitforChildren(Ctrl.END);
157         return result_ok;
158     }
159 }