1 /// Service for the tagion virtual machine 2 /// [Documentation](https://docs.tagion.org/#/documents/architecture/TVM) 3 module tagion.services.TVM; 4 5 @safe: 6 7 import core.time; 8 import std.conv : to; 9 import std.stdio; 10 import tagion.actor.actor; 11 import tagion.basic.Debug : __write; 12 import tagion.hibon.Document; 13 import tagion.hibon.HiBONJSON; 14 import tagion.hibon.HiBONRecord; 15 import tagion.logger.Logger; 16 import tagion.logger.Logger; 17 import tagion.script.common; 18 import tagion.script.execute; 19 import tagion.services.messages; 20 import tagion.services.options; 21 22 enum ResponseError { 23 UnsupportedScript, 24 ExecutionError, 25 } 26 27 /** 28 * TVMService actor 29 * Receives: 30 * (signedContract, immutable(CollectedSignedContract)*) 31 * (consensusContract, immutable(CollectedSignedContract)*) 32 * 33 * Sends: 34 * (Payload, const(Document)) to TaskNames.epoch_creator 35 * (producedContract, immutable(ContractProduct)*) to TaskNames.transcript 36 **/ 37 struct TVMService { 38 ActorHandle transcript_handle; 39 ActorHandle epoch_handle; 40 41 this(immutable(TaskNames) tn) nothrow { 42 transcript_handle = ActorHandle(tn.transcript); 43 epoch_handle = ActorHandle(tn.epoch_creator); 44 } 45 46 static ContractExecution execute; 47 static Topic tvm_error = Topic("error/tvm"); 48 49 void task() { 50 run(&contract, &consensus_contract); 51 } 52 53 void contract(signedContract, immutable(CollectedSignedContract)* collected) { 54 55 if (!engine(collected)) { 56 log(tvm_error, ResponseError.UnsupportedScript.to!string, Document()); 57 return; 58 } 59 log("sending pload to epoch creator"); 60 epoch_handle.send(Payload(), collected.sign_contract.toDoc); 61 } 62 63 void consensus_contract(consensusContract, immutable(CollectedSignedContract)* collected) { 64 engine(collected); 65 } 66 67 bool engine(immutable(CollectedSignedContract)* collected) { 68 log("received signed contract"); 69 if (!collected.sign_contract.contract.script.isRecord!PayScript) { 70 log(tvm_error, ResponseError.UnsupportedScript.to!string); 71 return false; 72 } 73 // import std.algorithm; 74 // collected.inputs.each!(d => writefln("%s", d.toPretty)); 75 76 log("before sending to tvm"); 77 auto result = execute(collected); 78 if (result.error) { 79 log(tvm_error, ResponseError.ExecutionError.to!string, Document()); 80 log.trace("Execution error - %s", result.e.message); 81 return false; 82 } 83 log("sending produced contract to transcript"); 84 transcript_handle.send(producedContract(), result.get); 85 return true; 86 } 87 88 } 89 90 unittest { 91 import core.time; 92 import tagion.utils.pretend_safe_concurrency; 93 94 enum task_names = TaskNames(); 95 scope (exit) { 96 unregister(task_names.transcript); 97 unregister(task_names.epoch_creator); 98 } 99 register(task_names.transcript, thisTid); 100 register(task_names.epoch_creator, thisTid); 101 auto tvm_service = TVMService(task_names); 102 103 import std.algorithm.iteration : map; 104 import std.array; 105 import std.range : iota; 106 import tagion.basic.Types : Buffer; 107 import tagion.crypto.Types; 108 import tagion.script.TagionCurrency; 109 import tagion.utils.StdTime; 110 111 auto createCollected(uint input, uint output) { 112 immutable(Document)[] in_bills; 113 in_bills ~= iota(0, 10).map!(_ => TagionBill(TGN(input), sdt_t.init, Pubkey.init, Buffer.init).toDoc).array; 114 immutable(TagionBill)[] out_bills; 115 out_bills ~= iota(0, 10).map!(_ => TagionBill(TGN(output), sdt_t.init, Pubkey.init, Buffer.init)).array; 116 117 auto contract = immutable(Contract)(null, null, PayScript(out_bills).toDoc); 118 auto s_contract = new immutable(SignedContract)(null, contract); 119 return new immutable(CollectedSignedContract)( 120 s_contract, 121 in_bills, 122 null, 123 ); 124 } 125 126 { /// Positive test 127 auto collected = createCollected(100, 50); 128 tvm_service.contract(signedContract(), collected); 129 130 foreach (_; 0 .. 2) { 131 const received = receiveTimeout( 132 Duration.zero, 133 (Payload _, const(Document) __) {}, 134 (producedContract _, immutable(ContractProduct)* __) {}, 135 ); 136 assert(received, "TVM did not send the contract or payload"); 137 } 138 } 139 140 { // False test 141 auto collected = createCollected(50, 100); 142 tvm_service.contract(signedContract(), collected); 143 144 const received = receiveTimeout( 145 Duration.zero, 146 (Payload _, const(Document) __) {}, 147 (producedContract _, immutable(ContractProduct)* __) {}, 148 ); 149 assert(!received, "The tvm should not send a contract where the output bills are greater than the input"); 150 } 151 }