1 module tagion.testbench.services.trt_service;
2 // Default import list for bdd
3 import tagion.behaviour;
4 import tagion.hibon.Document;
5 import std.typecons : Tuple;
6 import tagion.testbench.tools.Environment;
7 import std.file;
8 import std.path : buildPath, setExtension;
9 import tagion.GlobalSignals;
10 import tagion.basic.Types : FileExtension;
11 import std.stdio;
12 import tagion.behaviour.Behaviour;
13 import tagion.services.options;
14 import tagion.testbench.services;
15 import tagion.tools.Basic;
16 import neuewelle = tagion.tools.neuewelle;
17 import tagion.utils.pretend_safe_concurrency;
18 import core.thread;
19 import core.time;
20 import tagion.logger.Logger;
21 import tagion.script.TagionCurrency;
22 import tagion.script.common;
23 import tagion.communication.HiRPC;
24 import tagion.testbench.services.sendcontract;
25 import tagion.wallet.SecureWallet;
26 import tagion.testbench.services.helper_functions;
27 import tagion.behaviour.BehaviourException : check;
28 import tagion.tools.wallet.WalletInterface;
29 
30 enum CONTRACT_TIMEOUT = 40;
31 mixin Main!(_main);
32 
33 void wrap_neuewelle(immutable(string)[] args) {
34     neuewelle._main(cast(string[]) args);
35 }
36 int _main(string[] args) {
37     auto module_path = env.bdd_log.buildPath(__MODULE__);
38     if (module_path.exists) {
39         rmdirRecurse(module_path);
40     }
41     mkdirRecurse(module_path);
42     string config_file = buildPath(module_path, "tagionwave.json");
43 
44     scope Options local_options = Options.defaultOptions;
45     local_options.dart.folder_path = buildPath(module_path);
46     local_options.trt.folder_path = buildPath(module_path);
47     local_options.trt.enable = true;
48     local_options.replicator.folder_path = buildPath(module_path, "recorders");
49     local_options.epoch_creator.timeout = 500;
50     local_options.wave.prefix_format = "TRT_TEST_Node_%s_";
51     local_options.subscription.address = contract_sock_addr("TRT_TEST_SUBSCRIPTION");
52     local_options.save(config_file);
53 
54     import std.algorithm;
55     import std.array;
56     import std.format;
57     import std.range;
58     import std.stdio;
59     import tagion.crypto.SecureInterfaceNet;
60     import tagion.crypto.SecureNet : StdSecureNet;
61     import tagion.dart.DART;
62     import tagion.dart.DARTFile;
63     import tagion.dart.Recorder;
64     StdSecureWallet[] wallets;
65     // create the wallets
66     foreach (i; 0 .. 5) {
67         StdSecureWallet secure_wallet;
68         secure_wallet = StdSecureWallet(
69                 iota(0, 5).map!(n => format("%dquestion%d", i, n)).array,
70                 iota(0, 5).map!(n => format("%danswer%d", i, n)).array,
71                 4,
72                 format("%04d", i),
73         );
74         wallets ~= secure_wallet;
75     }
76 
77     TagionBill requestAndForce(ref StdSecureWallet w, TagionCurrency amount) {
78         auto b = w.requestBill(amount);
79         w.addBill(b);
80         return b;
81     }
82 
83     TagionBill[] bills;
84     foreach (ref wallet; wallets) {
85         foreach (i; 0 .. 3) {
86             bills ~= requestAndForce(wallet, 1000.TGN);
87         }
88     }
89 
90     SecureNet net = new StdSecureNet();
91     net.generateKeyPair("very_secret");
92 
93     auto factory = RecordFactory(net);
94     auto recorder = factory.recorder;
95     recorder.insert(bills, Archive.Type.ADD);
96     import tagion.trt.TRT;
97     auto trt_recorder = factory.recorder;
98     genesisTRT(bills, trt_recorder, net);
99     
100 
101     foreach (i; 0 .. local_options.wave.number_of_nodes) {
102         immutable prefix = format(local_options.wave.prefix_format, i);
103         const path = buildPath(local_options.dart.folder_path, prefix ~ local_options.dart.dart_filename);
104         const trt_path = buildPath(local_options.trt.folder_path, prefix ~ local_options.trt.trt_filename);
105         // writeln(path);
106         // writeln(trt_path);
107         DARTFile.create(path, net);
108         DARTFile.create(trt_path, net);
109         auto db = new DART(net, path);
110         auto trt_db = new DART(net, trt_path);
111         db.modify(recorder);
112         trt_db.modify(trt_recorder);
113 
114         writefln("%s TRT bullseye: %(%02x%)", trt_path, trt_db.bullseye);
115         writefln("%s DART bullseye: %(%02x%)", path, db.bullseye);
116 
117         db.close;
118         trt_db.close;
119     }
120 
121 
122     immutable neuewelle_args = ["trt_test", config_file, "--nodeopts", module_path]; // ~ args;
123     auto tid = spawn(&wrap_neuewelle, neuewelle_args);
124     import tagion.utils.JSONCommon : load;
125 
126     Options[] node_opts;
127 
128     Thread.sleep(15.seconds);
129     foreach (i; 0 .. local_options.wave.number_of_nodes) {
130         const filename = buildPath(module_path, format(local_options.wave.prefix_format ~ "opts", i).setExtension(FileExtension
131                 .json));
132         writeln(filename);
133         Options node_opt = load!(Options)(filename);
134         node_opts ~= node_opt;
135     }
136     auto name = "trt_testing";
137     register(name, thisTid);
138     log.registerSubscriptionTask(name);
139 
140     Thread.sleep(10.seconds);
141 
142     
143     auto feature = automation!(trt_service);
144     feature.SendAInoiceUsingTheTRT(node_opts[0], wallets[0], wallets[1]);
145 
146     feature.run;
147 
148     stopsignal.set;
149     Thread.sleep(6.seconds);
150 
151     return 0;
152 
153 }
154 
155 
156 
157 
158 enum feature = Feature(
159             "TRT Service test",
160             []);
161 
162 alias FeatureContext = Tuple!(
163         SendAInoiceUsingTheTRT, "SendAInoiceUsingTheTRT",
164         FeatureGroup*, "result"
165 );
166 
167 @safe @Scenario("send a inoice using the TRT",
168         [])
169 class SendAInoiceUsingTheTRT {
170     Options opts1;
171     StdSecureWallet wallet1;
172     StdSecureWallet wallet2;
173     //
174     SignedContract signed_contract1;
175     SignedContract signed_contract2;
176     TagionCurrency amount;
177     TagionCurrency fee1;
178     TagionCurrency fee2;
179 
180     HiRPC wallet1_hirpc;
181     HiRPC wallet2_hirpc;
182     TagionCurrency start_amount1;
183     TagionCurrency start_amount2;
184     this(Options opts1, ref StdSecureWallet wallet1, ref StdSecureWallet wallet2) {
185         this.wallet1 = wallet1;
186         this.wallet2 = wallet2;
187         this.opts1 = opts1;
188 
189         wallet1_hirpc = HiRPC(wallet1.net);
190         wallet2_hirpc = HiRPC(wallet2.net);
191         start_amount1 = wallet1.calcTotal(wallet1.account.bills);
192         start_amount2 = wallet2.calcTotal(wallet2.account.bills);
193         
194     }
195 
196     @Given("i have a running network with a trt")
197     Document trt() {
198         writefln("address to dial %s", opts1.dart_interface.sock_addr);
199         auto wallet1_amount = getWalletInvoiceUpdateAmount(wallet1, opts1.dart_interface.sock_addr, wallet1_hirpc);
200         check(wallet1_amount == start_amount1, "balance should not have changed");
201         auto wallet2_amount = getWalletInvoiceUpdateAmount(wallet2, opts1.dart_interface.sock_addr, wallet2_hirpc);
202         check(wallet2_amount == start_amount2, "balance should not have changed");
203         // create a update request
204         return result_ok;
205     }
206 
207     @When("i create and send a invoice")
208     Document invoice() {
209         amount = 100.TGN;
210         auto invoice_to_pay = wallet2.createInvoice("wowo", amount);
211         wallet2.registerInvoice(invoice_to_pay);
212         wallet1.payment([invoice_to_pay], signed_contract1, fee1);
213         (() @trusted => Thread.sleep(1.seconds))();
214         wallet1.payment([invoice_to_pay], signed_contract2, fee2);
215 
216         sendSubmitHiRPC(opts1.inputvalidator.sock_addr, wallet1_hirpc.submit(signed_contract1), wallet1.net);
217         sendSubmitHiRPC(opts1.inputvalidator.sock_addr, wallet1_hirpc.submit(signed_contract2), wallet1.net);
218         (() @trusted => Thread.sleep(CONTRACT_TIMEOUT.seconds))();
219         return result_ok;
220     }
221 
222     @When("i update my wallet using the pubkey lookup")
223     Document lookup() {
224         import std.format;
225         auto wallet1_amount = getWalletInvoiceUpdateAmount(wallet1, opts1.dart_interface.sock_addr, wallet1_hirpc);
226         auto wallet2_amount = getWalletInvoiceUpdateAmount(wallet2, opts1.dart_interface.sock_addr, wallet2_hirpc);
227 
228         auto wallet1_expected = start_amount1 - fee1 - fee2 - 2*amount;
229         check(wallet1_amount == wallet1_expected, format("should have %s had %s", wallet1_expected, wallet1_amount));
230         auto wallet2_expected = start_amount2 + 2*amount;
231         check(wallet2_amount == wallet2_expected, format("should have %s had %s", wallet2_expected, wallet2_amount));
232 
233         return result_ok;
234     }
235 
236     @Then("the transaction should go through")
237     Document through() {
238         return Document();
239     }
240 
241 }