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