1 module tagion.testbench.services.genesis_test; 2 // Default import list for bdd 3 import std.typecons : Tuple; 4 import tagion.behaviour; 5 import tagion.hibon.Document; 6 import tagion.testbench.tools.Environment; 7 import std.stdio; 8 import tagion.logger.LogRecords : LogInfo; 9 import tagion.logger.Logger; 10 import tagion.hashgraph.Refinement; 11 import tagion.utils.pretend_safe_concurrency; 12 import tagion.testbench.actor.util; 13 import core.time; 14 import std.range; 15 import std.array; 16 import tagion.dart.Recorder; 17 import tagion.services.replicator : modify_log; 18 import tagion.actor; 19 import tagion.services.options; 20 import tagion.crypto.SecureInterfaceNet; 21 import std.format; 22 import tagion.hibon.HiBONRecord : isRecord; 23 import tagion.script.common; 24 import tagion.crypto.SecureNet : StdHashNet; 25 import std.algorithm; 26 import core.thread; 27 import std.exception; 28 import tagion.crypto.Types; 29 import tagion.script.TagionCurrency; 30 import tagion.tools.wallet.WalletInterface; 31 import tagion.crypto.SecureNet : StdSecureNet; 32 import tagion.wallet.SecureWallet : SecureWallet; 33 import tagion.basic.Types : Buffer; 34 import tagion.utils.StdTime; 35 import tagion.communication.HiRPC; 36 import tagion.hibon.HiBONtoText; 37 38 enum EPOCH_TIMEOUT = 15; 39 alias StdSecureWallet = SecureWallet!StdSecureNet; 40 41 enum feature = Feature( 42 "Boot system with genesis block.", 43 []); 44 45 alias FeatureContext = Tuple!( 46 NetworkRunningWithGenesisBlockAndEpochChain, "NetworkRunningWithGenesisBlockAndEpochChain", 47 FeatureGroup*, "result" 48 ); 49 50 51 52 53 54 55 @safe @Scenario("network running with genesis block and epoch chain.", 56 []) 57 class NetworkRunningWithGenesisBlockAndEpochChain { 58 bool epoch_on_startup; 59 long start_epoch; 60 RecordFactory record_factory; 61 Options[] opts; 62 ActorHandle[] handles; 63 HashNet net = new StdHashNet; 64 StdSecureWallet wallet1; 65 TagionCurrency amount; 66 TagionCurrency fee; 67 68 const(GenesisEpoch) genesis_epoch; 69 SignedContract signed_contract; 70 71 72 struct History { 73 TagionHead[] heads; 74 Epoch[] epochs; 75 } 76 History[string] histories; 77 78 this(Options[] opts, ref StdSecureWallet wallet1, const(GenesisEpoch) genesis_epoch) { 79 this.opts = opts; 80 record_factory = RecordFactory(net); 81 this.wallet1 = wallet1; 82 this.genesis_epoch = genesis_epoch; 83 } 84 85 @Given("i have a network booted with a genesis block") 86 Document block() { 87 88 amount = 500.TGN; 89 auto bill_to_pay = TagionBill(amount, currentTime, Pubkey([0]), Buffer.init); 90 check(wallet1.createPayment([bill_to_pay], signed_contract, fee).value, "Error creating payment"); 91 check(signed_contract.contract.inputs.length == 1, format("should only contain one bill had %s", signed_contract.contract.inputs)); 92 93 auto wallet1_hirpc = HiRPC(wallet1.net); 94 auto hirpc_submit = wallet1_hirpc.submit(signed_contract); 95 96 foreach(opt; opts) { 97 histories[opt.task_names.replicator] = History.init; 98 } 99 100 writefln("signed_contract: %s", signed_contract.toPretty); 101 102 103 submask.subscribe(modify_log); 104 105 int max = 100; 106 int start = 0; 107 while(start < max) { 108 if (start == 20) { 109 sendSubmitHiRPC(opts[0].inputvalidator.sock_addr, hirpc_submit, wallet1.net); 110 } 111 auto modify_log_result = receiveOnlyTimeout!(LogInfo, const(Document))(EPOCH_TIMEOUT.seconds); 112 log("received something"); 113 114 check(modify_log_result[1].isRecord!(RecordFactory.Recorder), "Did not receive recorder"); 115 // writefln("received recorder %s", modify_log_result[1].toPretty); 116 117 auto recorder = record_factory.recorder(modify_log_result[1]); 118 auto head = recorder[].filter!(a => a.filed.isRecord!TagionHead).array; 119 check(head.length == 1, format("Should contain only one head per modify. had %s", head.length)); 120 121 122 const task_name = modify_log_result[0].task_name; 123 124 histories[task_name].heads ~= TagionHead(head.front.filed); 125 126 auto epochs = recorder[] 127 .filter!(a => a.filed.isRecord!Epoch) 128 .map!(a => Epoch(a.filed)) 129 .filter!(e => !e.previous.empty) 130 .array; 131 132 histories[task_name].epochs ~= epochs; 133 writefln("EPOCH NUMBERS %s", epochs.map!(e => format("%(%02x%)", e.previous)).array); 134 135 136 start++; 137 } 138 // (() @trusted => writefln("%s", histories))(); 139 140 return result_ok; 141 } 142 143 @When("the network continues to run.") 144 Document run() @trusted { 145 // start by sorting the histories 146 147 History[string] sorted_histories; 148 149 foreach(hist; histories.byKeyValue) { 150 auto sorted_epochs = hist.value.epochs.sort!((a,b) => a.epoch_number < b.epoch_number).array; 151 auto sorted_heads = hist.value.heads.sort!((a,b) => a.current_epoch < b.current_epoch).array; 152 check(sorted_epochs.length <= sorted_heads.length, format("there should be equal or more heads than the total amount of epochs, heads %s, epochs %s", sorted_heads.length, sorted_epochs.length)); 153 History sorted_hist; 154 sorted_hist.epochs = sorted_epochs; 155 sorted_hist.heads = sorted_heads; 156 sorted_histories[hist.key] = sorted_hist; 157 } 158 159 // since we need to ref to the previous element we use a for loop over the epochs since we know that there must be fewer epochs than heads. 160 161 Epoch[] ref_epochs = sorted_histories.byValue.front.epochs.array; 162 check(ref_epochs.length > 0, "did not create any finished epochs"); 163 164 // we first check that the epoch for the first is node created correctly. Then we compare the different epochs afterwards 165 for (int i = 1; i < ref_epochs.length; i++) { 166 auto ref_epoch = ref_epochs[i]; 167 auto prev_epoch = ref_epochs[i-1]; 168 169 writefln("comparing %s", i); 170 171 172 173 174 writefln("prev epoch: %s \n hash_of_prev: %s\n new epoch: %s", prev_epoch.toPretty, net.calcHash(prev_epoch).encodeBase64, ref_epoch.toPretty); 175 check(ref_epoch.epoch_number == prev_epoch.epoch_number +1, "The epoch number was not correctly incremented"); 176 auto previous = net.calcHash(prev_epoch); 177 check(previous == ref_epoch.previous, format("The fingerprint was not correct. should be %s was %s", previous.encodeBase64, ref_epoch.previous.encodeBase64)); 178 179 if (ref_epoch.globals != prev_epoch.globals) { 180 writefln("NEW DIF EPOCH %s\n PREV EPOCH %s", ref_epoch.globals.toPretty, prev_epoch.globals.toPretty); 181 182 183 // this was the epoch where our tx should have gone through 184 check(ref_epoch.globals.total < prev_epoch.globals.total, format("the total was not decreased previous %s current %s", prev_epoch.globals.total, ref_epoch.globals.total)); 185 186 187 const(TagionBill)[] outputs = PayScript(signed_contract.contract.script).outputs; 188 const(TagionBill)[] inputs = [wallet1.account.bills.front]; 189 auto delta_bills = outputs.length - inputs.length; 190 check(ref_epoch.globals.burnt_bills - 1 == prev_epoch.globals.burnt_bills, "the contract should have burned a bill"); 191 192 TagionCurrency burned = wallet1.calcTotal(inputs) - wallet1.calcTotal(outputs); 193 check(burned > 0, "should have burned more for the contract"); 194 195 check(prev_epoch.globals.total_burned + wallet1.calcTotal(inputs).units == ref_epoch.globals.total_burned, format("the burned amount was not correct. prev_epoch burned: %s, new_epoch burned %s burned units %s", prev_epoch.globals.total_burned, ref_epoch.globals.total_burned, burned.units)); 196 check(ref_epoch.globals.number_of_bills - delta_bills == prev_epoch.globals.number_of_bills, "We should have updated the number of bills"); 197 } 198 199 foreach(hist; sorted_histories.byValue) { 200 if (hist.epochs.length-1 < i) { 201 continue; 202 } 203 check(hist.epochs[i] == ref_epoch, "The epoch was different across the nodes"); 204 } 205 } 206 return result_ok; 207 } 208 209 210 211 @Then("it should continue adding blocks to the _epochchain") 212 Document epochchain() { 213 return result_ok; 214 } 215 216 @Then("check the chains validity.") 217 Document validity() { 218 return result_ok; 219 } 220 221 } 222