diff --git a/onchain/permissionless-arbitration/Dockerfile b/onchain/permissionless-arbitration/Dockerfile index a0974c1f..18487c52 100644 --- a/onchain/permissionless-arbitration/Dockerfile +++ b/onchain/permissionless-arbitration/Dockerfile @@ -1,7 +1,7 @@ FROM cartesi/machine-emulator:main USER 0 -RUN apt-get -y update; apt-get -y install curl git +RUN apt-get -y update; apt-get -y install curl git; apt-get install -y procps RUN curl -sSL https://github.com/foundry-rs/foundry/releases/download/nightly/foundry_nightly_linux_$(dpkg --print-architecture).tar.gz | \ tar -zx -C /usr/local/bin diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua deleted file mode 100644 index 2db65552..00000000 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ /dev/null @@ -1,528 +0,0 @@ -local Hash = require "cryptography.hash" -local MerkleTree = require "cryptography.merkle_tree" -local eth_ebi = require "utils.eth_ebi" - -local function parse_topics(json) - local _, _, topics = json:find( - [==["topics":%[([^%]]*)%]]==] - ) - - local t = {} - for k, _ in string.gmatch(topics, [["(0x%x+)"]]) do - table.insert(t, k) - end - - return t -end - -local function parse_data(json, sig) - local _, _, data = json:find( - [==["data":"(0x%x+)"]==] - ) - - local decoded_data = eth_ebi.decode_event_data(sig, data) - return decoded_data -end - -local function parse_meta(json) - local _, _, block_hash = json:find( - [==["blockHash":"(0x%x+)"]==] - ) - - local _, _, block_number = json:find( - [==["blockNumber":"(0x%x+)"]==] - ) - - local _, _, log_index = json:find( - [==["logIndex":"(0x%x+)"]==] - ) - - local t = { - block_hash = block_hash, - block_number = tonumber(block_number), - log_index = tonumber(log_index), - } - - return t -end - - -local function parse_logs(logs, data_sig) - local ret = {} - for k, _ in string.gmatch(logs, [[{[^}]*}]]) do - local emited_topics = parse_topics(k) - local decoded_data = parse_data(k, data_sig) - local meta = parse_meta(k) - table.insert(ret, { emited_topics = emited_topics, decoded_data = decoded_data, meta = meta }) - end - - return ret -end - -local function join_tables(...) - local function join(ret, t, ...) - if not t then return ret end - - for k, v in ipairs(t) do - ret[k] = v - end - - return join(ret, ...) - end - - local ret = join({}, ...) - return ret -end - -local function sort_and_dedup(t) - table.sort(t, function(a, b) - local m1, m2 = a.meta, b.meta - - if m1.block_number < m2.block_number then - return true - elseif m1.block_number > m2.block_number then - return false - else - if m1.log_index <= m2.log_index then - return true - else - return false - end - end - end) - - local ret = {} - for k, v in ipairs(t) do - local v2 = t[k + 1] - if not v2 then - table.insert(ret, v) - else - local m1, m2 = v.meta, v2.meta - if not (m1.block_number == m2.block_number and m1.log_index == m2.log_index) then - table.insert(ret, v) - end - end - end - - return ret -end - -local function quote_args(args, not_quote) - local quoted_args = {} - for _, v in ipairs(args) do - if type(v) == "table" and (getmetatable(v) == Hash or getmetatable(v) == MerkleTree) then - if not_quote then - table.insert(quoted_args, v:hex_string()) - else - table.insert(quoted_args, '"' .. v:hex_string() .. '"') - end - elseif type(v) == "table" then - if v._tag == "tuple" then - local qa = quote_args(v, true) - local ca = table.concat(qa, ",") - local sb = "'(" .. ca .. ")'" - table.insert(quoted_args, sb) - else - local qa = quote_args(v, true) - local ca = table.concat(qa, ",") - local sb = "'[" .. ca .. "]'" - table.insert(quoted_args, sb) - end - elseif not_quote then - table.insert(quoted_args, tostring(v)) - else - table.insert(quoted_args, '"' .. v .. '"') - end - end - - return quoted_args -end - - -local Client = {} -Client.__index = Client - -function Client:new(blockchain) - local client = { - endpoint = blockchain.endpoint, - account = blockchain:new_account(), - blockchain = blockchain, - } - - setmetatable(client, self) - return client -end - -local cast_logs_template = [==[ -cast rpc -r "%s" eth_getLogs \ - '[{"fromBlock": "earliest", "toBlock": "latest", "address": "%s", "topics": [%s]}]' -w 2>&1 -]==] - -function Client:_read_logs(tournament_address, sig, topics, data_sig) - topics = topics or { false, false, false } - local encoded_sig = eth_ebi.encode_sig(sig) - table.insert(topics, 1, encoded_sig) - assert(#topics == 4, "topics doesn't have four elements") - - local topics_strs = {} - for _, v in ipairs(topics) do - local s - if v then - s = '"' .. v .. '"' - else - s = "null" - end - table.insert(topics_strs, s) - end - local topic_str = table.concat(topics_strs, ", ") - - local cmd = string.format( - cast_logs_template, - self.endpoint, - tournament_address, - topic_str - ) - - local handle = io.popen(cmd) - assert(handle) - local logs = handle:read "*a" - handle:close() - - if logs:find "Error" then - error(string.format("Read logs `%s` failed:\n%s", sig, logs)) - end - - local ret = parse_logs(logs, data_sig) - - self.blockchain:read_to("eth_getLogs") - return ret -end - -local cast_call_template = [==[ -cast call --rpc-url "%s" "%s" "%s" %s 2>&1 -]==] - -function Client:_call(address, sig, args) - local quoted_args = {} - for _, v in ipairs(args) do - table.insert(quoted_args, '"' .. v .. '"') - end - local args_str = table.concat(quoted_args, " ") - - local cmd = string.format( - cast_call_template, - self.endpoint, - address, - sig, - args_str - ) - - local handle = io.popen(cmd) - assert(handle) - - local ret = {} - local str = handle:read() - while str do - if str:find "Error" or str:find "error" then - local err_str = handle:read "*a" - handle:close() - error(string.format("Call `%s` failed:\n%s%s", sig, str, err_str)) - end - - table.insert(ret, str) - str = handle:read() - end - handle:close() - - self.blockchain:read_to("eth_call") - return ret -end - -function Client:read_match_created(tournament_address, commitment_hash) - local sig = "matchCreated(bytes32,bytes32,bytes32)" - local data_sig = "(bytes32)" - - local logs1 = self:_read_logs(tournament_address, sig, { commitment_hash:hex_string(), false, false }, data_sig) - local logs2 = self:_read_logs(tournament_address, sig, { false, commitment_hash:hex_string(), false }, data_sig) - - local logs = sort_and_dedup(join_tables(logs1, logs2)) - - local ret = {} - for k, v in ipairs(logs) do - local log = {} - log.tournament_address = tournament_address - log.meta = v.meta - - log.commitment_one = Hash:from_digest_hex(v.emited_topics[2]) - log.commitment_two = Hash:from_digest_hex(v.emited_topics[3]) - log.left_hash = Hash:from_digest_hex(v.decoded_data[1]) - log.match_id_hash = log.commitment_one:join(log.commitment_two) - - ret[k] = log - end - - return ret -end - -function Client:read_commitment(tournament_address, commitment_hash) - local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" - - local call_ret = self:_call(tournament_address, sig, { commitment_hash:hex_string() }) - assert(#call_ret == 2) - - local allowance, last_resume = call_ret[1]:match "%((%d+),(%d+)%)" - assert(allowance) - assert(last_resume) - local clock = { - allowance = tonumber(allowance), - last_resume = tonumber(last_resume) - } - - local ret = { - clock = clock, - final_state = Hash:from_digest_hex(call_ret[2]) - } - - return ret -end - -function Client:read_tournament_created(tournament_address, match_id_hash) - local sig = "newInnerTournament(bytes32,address)" - local data_sig = "(address)" - - local logs = self:_read_logs(tournament_address, sig, { match_id_hash:hex_string(), false, false }, data_sig) - assert(#logs <= 1) - - if #logs == 0 then return false end - local log = logs[1] - - local ret = { - parent_match = match_id_hash, - new_tournament = log.decoded_data[1], - } - - return ret -end - -function Client:match(address, match_id_hash) - local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint256,uint64,uint64)" - local ret = self:_call(address, sig, { match_id_hash:hex_string() }) - ret[1] = Hash:from_digest_hex(ret[1]) - ret[2] = Hash:from_digest_hex(ret[2]) - ret[3] = Hash:from_digest_hex(ret[3]) - - return ret -end - -function Client:inner_tournament_winner(address) - local sig = "innerTournamentWinner()(bool,bytes32)" - local ret = self:_call(address, sig, {}) - ret[2] = Hash:from_digest_hex(ret[2]) - - return ret -end - -function Client:root_tournament_winner(address) - local sig = "arbitrationResult()(bool,bytes32,bytes32)" - local ret = self:_call(address, sig, {}) - ret[2] = Hash:from_digest_hex(ret[2]) - ret[3] = Hash:from_digest_hex(ret[3]) - - return ret -end - -function Client:maximum_delay(address) - local sig = "maximumEnforceableDelay()(uint64)" - local ret = self:_call(address, sig, {}) - - return ret -end - -local cast_send_template = [[ -cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 -]] - -function Client:_send_tx(tournament_address, sig, args) - local quoted_args = quote_args(args) - local args_str = table.concat(quoted_args, " ") - - local cmd = string.format( - cast_send_template, - self.account.pk, - self.endpoint, - tournament_address, - sig, - args_str - ) - - local handle = io.popen(cmd) - assert(handle) - - local ret = handle:read "*a" - if ret:find "Error" then - handle:close() - error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) - end - handle:close() - self.blockchain:read_to("eth_sendRawTransaction") -end - -function Client:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) - local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] - self:_send_tx(tournament_address, sig, { final_state, proof, left_child, right_child }) -end - -function Client:tx_advance_match( - tournament_address, commitment_one, commitment_two, left, right, new_left, new_right -) - local sig = [[advanceMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32)]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, new_left, new_right } - ) -end - -function Client:tx_seal_inner_match( - tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof -) - local sig = - [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash:hex_string(), proof } - ) -end - -function Client:tx_win_inner_match(tournament_address, child_tournament_address, left, right) - local sig = - [[winInnerMatch(address,bytes32,bytes32)]] - self:_send_tx( - tournament_address, - sig, - { child_tournament_address, left, right } - ) -end - -function Client:tx_seal_leaf_match( - tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof -) - local sig = - [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } - ) -end - -function Client:tx_win_leaf_match( - tournament_address, commitment_one, commitment_two, left, right, proof -) - local sig = - [[winLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes)]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, proof } - ) -end - -return Client - - ---[[ -local blockchain = require "blockchain":new() -local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -local contract = blockchain:deploy_contract(initial_hash) - -local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) -local p1_l1 = utils.keccak "0" -local p1_l2 = utils.keccak "1" -local p1_cmt = utils.join_hashes(utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) -client1:tx_join_tournament(contract, p1_l2, {p1_l1}, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) -local commitment1 = client1:read_commitment(contract, p1_cmt) -print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) - -local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) -local p2_l1 = utils.keccak "0" -local p2_l2 = utils.keccak "2" -local p2_cmt = utils.join_hashes(utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) -client2:tx_join_tournament(contract, p2_l2, {p2_l1}, utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) -local commitment2 = client1:read_commitment(contract, p2_cmt) -print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) - -local a = client1:read_match_created(contract, false, false) -for k,log in ipairs(a) do - print("LOG ", k) - print("tournament_address", log.tournament_address) - print("commitment_one", log.commitment_one) - print("commitment_two", log.commitment_two) - print("match_id_hash", log.match_id_hash) - print("left_hash", log.left_hash) - print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) - print("END ", k) - print("") - - local x = client1:match(contract, log.match_id_hash) - for _,v in ipairs(x) do print("A", v) end -end - -client1:tx_advance_match(contract, p1_cmt, p2_cmt, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2), p1_l1, p1_l2) - -client2:tx_seal_inner_match(contract, p1_cmt, p2_cmt, p2_l1, p2_l2, p2_l1, {}) -client1:read_tournament_created(contract, utils.join_hashes(p1_cmt, p2_cmt)) ---]] - --- local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) --- local p1_l1 = utils.keccak "0" --- local p1_l2 = utils.keccak "1" --- local p1_cmt = utils.join_hashes(p1_l1, p1_l2) --- client1:tx_join_tournament(contract, p1_l2, {p1_l1}, p1_l1, p1_l2) --- local commitment1 = client1:read_commitment(contract, p1_cmt) --- print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) - --- local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) --- local p2_l1 = utils.keccak "0" --- local p2_l2 = utils.keccak "2" --- local p2_cmt = utils.join_hashes(p2_l1, p2_l2) --- client2:tx_join_tournament(contract, p2_l2, {p2_l1}, p2_l1, p2_l2) --- local commitment2 = client1:read_commitment(contract, p2_cmt) --- print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) - --- local a = client1:read_match_created(contract, false, false) --- for k,log in ipairs(a) do --- print("LOG ", k) --- print("tournament_address", log.tournament_address) --- print("commitment_one", log.commitment_one) --- print("commitment_two", log.commitment_two) --- print("match_id_hash", log.match_id_hash) --- print("left_hash", log.left_hash) --- print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) --- print("END ", k) --- print("") - --- local x = client1:match(contract, log.match_id_hash) --- for _,v in ipairs(x) do print("A", v) end --- end - - - --- client.joinRootTournament(commitmentId, leftChild, rightChild) --- client.winByTimeout(id) --- client.advanceMatch(id, leftChild, rightChild, leftChildChild, rightChildChild) --- client.sealMatch() // seals match, creates nested tournament, updates current commitment --- client.enterNestedTournament(parentMatch, parentCommitmentIdHash, childCommitment) --- client.proveLeafMatch() - - --- local LEVEL_LOG2_STEP_SIZES = { 24, 14, 7, 0 } --- local LOG2_MAX_MCYCLE = 63 --- local last_log2_num_steps = LOG2_MAX_MCYCLE - --- for i, log2_step_size in ipairs(LEVEL_LOG2_STEP_SIZES) do --- local log2_num_steps = last_log2_num_steps - log2_step_size --- print(i, log2_num_steps) --- last_log2_num_steps = log2_step_size --- end diff --git a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua new file mode 100644 index 00000000..3949135c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua @@ -0,0 +1,90 @@ +-- contains default 40 accounts of anvil test node +local constants = { + endpoint = "http://127.0.0.1:8545", + addresses = { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + "0xBcd4042DE499D14e55001CcbB24a551F3b954096", + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788", + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a", + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec", + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097", + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71", + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30", + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E", + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0", + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199", + "0x09DB0a93B389bEF724429898f539AEB7ac2Dd55f", + "0x02484cb50AAC86Eae85610D6f4Bf026f30f6627D", + "0x08135Da0A343E492FA2d4282F2AE34c6c5CC1BbE", + "0x5E661B79FE2D3F6cE70F5AAC07d8Cd9abb2743F1", + "0x61097BA76cD906d2ba4FD106E757f7Eb455fc295", + "0xDf37F81dAAD2b0327A0A50003740e1C935C70913", + "0x553BC17A05702530097c3677091C5BB47a3a7931", + "0x87BdCE72c06C21cd96219BD8521bDF1F42C78b5e", + "0x40Fc963A729c542424cD800349a7E4Ecc4896624", + "0x9DCCe783B6464611f38631e6C851bf441907c710", + "0x1BcB8e569EedAb4668e55145Cfeaf190902d3CF2", + "0x8263Fce86B1b78F95Ab4dae11907d8AF88f841e7", + "0xcF2d5b3cBb4D7bF04e3F7bFa8e27081B52191f91", + "0x86c53Eb85D0B7548fea5C4B4F82b4205C8f6Ac18", + "0x1aac82773CB722166D7dA0d5b0FA35B0307dD99D", + "0x2f4f06d218E426344CFE1A83D53dAd806994D325", + "0x1003ff39d25F2Ab16dBCc18EcE05a9B6154f65F4", + "0x9eAF5590f2c84912A08de97FA28d0529361Deb9E", + "0x11e8F3eA3C6FcF12EcfF2722d75CEFC539c51a1C", + "0x7D86687F980A56b832e9378952B738b614A99dc6", + }, + pks = { + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897", + "0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82", + "0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1", + "0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd", + "0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa", + "0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61", + "0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0", + "0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd", + "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", + "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e", + "0xeaa861a9a01391ed3d587d8a5a84ca56ee277629a8b02c22093a419bf240e65d", + "0xc511b2aa70776d4ff1d376e8537903dae36896132c90b91d52c1dfbae267cd8b", + "0x224b7eb7449992aac96d631d9677f7bf5888245eef6d6eeda31e62d2f29a83e4", + "0x4624e0802698b9769f5bdb260a3777fbd4941ad2901f5966b854f953497eec1b", + "0x375ad145df13ed97f8ca8e27bb21ebf2a3819e9e0a06509a812db377e533def7", + "0x18743e59419b01d1d846d97ea070b5a3368a3e7f6f0242cf497e1baac6972427", + "0xe383b226df7c8282489889170b0f68f66af6459261f4833a781acd0804fafe7a", + "0xf3a6b71b94f5cd909fb2dbb287da47badaa6d8bcdc45d595e2884835d8749001", + "0x4e249d317253b9641e477aba8dd5d8f1f7cf5250a5acadd1229693e262720a19", + "0x233c86e887ac435d7f7dc64979d7758d69320906a0d340d2b6518b0fd20aa998", + "0x85a74ca11529e215137ccffd9c95b2c72c5fb0295c973eb21032e823329b3d2d", + "0xac8698a440d33b866b6ffe8775621ce1a4e6ebd04ab7980deb97b3d997fc64fb", + "0xf076539fbce50f0513c488f32bf81524d30ca7a29f400d68378cc5b1b17bc8f2", + "0x5544b8b2010dbdbef382d254802d856629156aba578f453a76af01b81a80104e", + "0x47003709a0a9a4431899d4e014c1fd01c5aad19e873172538a02370a119bae11", + "0x9644b39377553a920edc79a275f45fa5399cbcf030972f771d0bca8097f9aad3", + "0xcaa7b4a2d30d1d565716199f068f69ba5df586cf32ce396744858924fdf827f0", + "0xfc5a028670e1b6381ea876dd444d3faaee96cffae6db8d93ca6141130259247c", + "0x5b92c5fe82d4fabee0bc6d95b4b8a3f9680a0ed7801f631035528f32c9eb2ad5", + "0xb68ac4aa2137dd31fd0732436d8e59e959bb62b4db2e6107b15f594caf0f405f", + }, +} + +return constants diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index 3ca56e13..7dee3044 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -1,4 +1,4 @@ -local default_account_number = 10 +local default_account_number = 40 local function stop_blockchain(handle, pid) print(string.format("Stopping blockchain with pid %d...", pid)) @@ -7,11 +7,10 @@ local function stop_blockchain(handle, pid) print "Blockchain stopped" end -local function start_blockchain(account_num) - account_num = account_num or default_account_number - print(string.format("Starting blockchain with %d accounts...", account_num)) +local function start_blockchain() + print(string.format("Starting blockchain with %d accounts...", default_account_number)) - local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d"]], account_num) + local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d > anvil.log 2>&1"]], default_account_number) local reader = io.popen(cmd) assert(reader, "`popen` returned nil reader") @@ -29,37 +28,9 @@ local function start_blockchain(account_num) return handle end -local function capture_blockchain_data(reader, account_num) - account_num = account_num or default_account_number - local str - - local addresses = {} - repeat - str = reader:read(); - local _, _, address = str:find [[%(%d+%) ("0x%x+")]] - if address then - table.insert(addresses, address) - end - until str:find("Private Keys") - assert(#addresses == account_num) - - local pks = {} - repeat - str = reader:read(); - local _, _, pk = str:find("%(%d+%) (0x%x+)") - if pk then - table.insert(pks, pk) - end - until str:find("Wallet") - assert(#pks == account_num) - - local endpoint - repeat - str = reader:read(); - _, _, endpoint = str:find("Listening on ([%w%p]+)") - until endpoint - - return { address = addresses, pk = pks }, endpoint +local function capture_blockchain_data() + local blockchain_data = require "blockchain.constants" + return { address = blockchain_data.addresses, pk = blockchain_data.pks }, blockchain_data.endpoint end @@ -78,7 +49,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, sl_factory_address = handle_sl:read("*a"):find("Deployed to: (0x%x+)") assert(sl_factory_address, "deployment failed, factory_address is nil") - print("Single Level factory deployed at", sl_factory_address) + print("Single Level factory deployed at: " .. sl_factory_address) handle_sl:close() -- @@ -95,7 +66,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, top_factory_address = handle_top:read("*a"):find("Deployed to: (0x%x+)") assert(top_factory_address, "deployment failed, factory_address is nil") - print("Top factory deployed at", top_factory_address) + print("Top factory deployed at: " .. top_factory_address) handle_top:close() -- @@ -112,7 +83,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, mid_factory_address = handle_mid:read("*a"):find("Deployed to: (0x%x+)") assert(mid_factory_address, "deployment failed, factory_address is nil") - print("Middle factory deployed at", mid_factory_address) + print("Middle factory deployed at: " .. mid_factory_address) handle_mid:close() -- @@ -129,7 +100,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, bot_factory_address = handle_bot:read("*a"):find("Deployed to: (0x%x+)") assert(bot_factory_address, "deployment failed, factory_address is nil") - print("Bottom factory deployed at", bot_factory_address) + print("Bottom factory deployed at: " .. bot_factory_address) handle_bot:close() @@ -147,7 +118,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, tournament_factory_address = handle_tournament:read("*a"):find("Deployed to: (0x%x+)") assert(tournament_factory_address, "deployment failed, factory_address is nil") - print("tournament factory deployed at", tournament_factory_address) + print("tournament factory deployed at: " .. tournament_factory_address) handle_tournament:close() @@ -166,7 +137,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, a = handle_root:read("*a"):find [["data":"0x000000000000000000000000(%x+)"]] local address = "0x" .. a assert(address, "deployment failed, address is nil") - print("Contract deployed at", address) + print("Contract deployed at: " .. address) handle_root:close() return address @@ -175,24 +146,22 @@ end local Blockchain = {} Blockchain.__index = Blockchain -function Blockchain:new(account_num) +function Blockchain:new() local blockchain = {} - local handle = start_blockchain(account_num) - local accounts, endpoint = capture_blockchain_data(handle.reader, account_num) + local handle = start_blockchain() + local accounts, endpoint = capture_blockchain_data() blockchain._handle = handle blockchain._accounts = accounts - blockchain._current_account = 1 - blockchain.endpoint = "http://" .. endpoint + blockchain.endpoint = endpoint setmetatable(blockchain, self) return blockchain end -function Blockchain:new_account() - local current_account = self._current_account - self._current_account = current_account + 1 +function Blockchain:default_account() + local current_account = 1 local accounts = self._accounts assert(current_account <= #accounts.address, "no more accounts") @@ -206,17 +175,9 @@ end function Blockchain:deploy_contract(initial_hash, deployer) assert(initial_hash) - deployer = deployer or self:new_account() + deployer = deployer or self:default_account() local address = deploy_contracts(self.endpoint, deployer.pk, initial_hash) return address, deployer end -function Blockchain:read_to(p) - repeat until self._handle.reader:read():find(p) -end - --- local bc = Blockchain:new(100) --- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" --- bc:deploy_contract(initial_hash) - return Blockchain diff --git a/onchain/permissionless-arbitration/offchain/blockchain/reader.lua b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua new file mode 100644 index 00000000..3a257683 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua @@ -0,0 +1,342 @@ +local Hash = require "cryptography.hash" +local eth_ebi = require "utils.eth_ebi" + +local function parse_topics(json) + local _, _, topics = json:find( + [==["topics":%[([^%]]*)%]]==] + ) + + local t = {} + for k, _ in string.gmatch(topics, [["(0x%x+)"]]) do + table.insert(t, k) + end + + return t +end + +local function parse_data(json, sig) + local _, _, data = json:find( + [==["data":"(0x%x+)"]==] + ) + + local decoded_data = eth_ebi.decode_event_data(sig, data) + return decoded_data +end + +local function parse_meta(json) + local _, _, block_hash = json:find( + [==["blockHash":"(0x%x+)"]==] + ) + + local _, _, block_number = json:find( + [==["blockNumber":"(0x%x+)"]==] + ) + + local _, _, log_index = json:find( + [==["logIndex":"(0x%x+)"]==] + ) + + local t = { + block_hash = block_hash, + block_number = tonumber(block_number), + log_index = tonumber(log_index), + } + + return t +end + + +local function parse_logs(logs, data_sig) + local ret = {} + for k, _ in string.gmatch(logs, [[{[^}]*}]]) do + local emited_topics = parse_topics(k) + local decoded_data = parse_data(k, data_sig) + local meta = parse_meta(k) + table.insert(ret, { emited_topics = emited_topics, decoded_data = decoded_data, meta = meta }) + end + + return ret +end + +local function join_tables(...) + local function join(ret, t, ...) + if not t then return ret end + + for k, v in ipairs(t) do + ret[k] = v + end + + return join(ret, ...) + end + + local ret = join({}, ...) + return ret +end + +local function sort_and_dedup(t) + table.sort(t, function(a, b) + local m1, m2 = a.meta, b.meta + + if m1.block_number < m2.block_number then + return true + elseif m1.block_number > m2.block_number then + return false + else + if m1.log_index <= m2.log_index then + return true + else + return false + end + end + end) + + local ret = {} + for k, v in ipairs(t) do + local v2 = t[k + 1] + if not v2 then + table.insert(ret, v) + else + local m1, m2 = v.meta, v2.meta + if not (m1.block_number == m2.block_number and m1.log_index == m2.log_index) then + table.insert(ret, v) + end + end + end + + return ret +end + +local Reader = {} +Reader.__index = Reader + +function Reader:new() + local blockchain_data = require "blockchain.constants" + + local reader = { + endpoint = blockchain_data.endpoint + } + + setmetatable(reader, self) + return reader +end + +local cast_logs_template = [==[ +cast rpc -r "%s" eth_getLogs \ + '[{"fromBlock": "earliest", "toBlock": "latest", "address": "%s", "topics": [%s]}]' -w 2>&1 +]==] + +function Reader:_read_logs(tournament_address, sig, topics, data_sig) + topics = topics or { false, false, false } + local encoded_sig = eth_ebi.encode_sig(sig) + table.insert(topics, 1, encoded_sig) + assert(#topics == 4, "topics doesn't have four elements") + + local topics_strs = {} + for _, v in ipairs(topics) do + local s + if v then + s = '"' .. v .. '"' + else + s = "null" + end + table.insert(topics_strs, s) + end + local topic_str = table.concat(topics_strs, ", ") + + local cmd = string.format( + cast_logs_template, + self.endpoint, + tournament_address, + topic_str + ) + + local handle = io.popen(cmd) + assert(handle) + local logs = handle:read "*a" + handle:close() + + if logs:find "Error" then + error(string.format("Read logs `%s` failed:\n%s", sig, logs)) + end + + local ret = parse_logs(logs, data_sig) + return ret +end + +local cast_call_template = [==[ +cast call --rpc-url "%s" "%s" "%s" %s 2>&1 +]==] + +function Reader:_call(address, sig, args) + local quoted_args = {} + for _, v in ipairs(args) do + table.insert(quoted_args, '"' .. v .. '"') + end + local args_str = table.concat(quoted_args, " ") + + local cmd = string.format( + cast_call_template, + self.endpoint, + address, + sig, + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local ret = {} + local str = handle:read() + while str do + if str:find "Error" or str:find "error" then + local err_str = handle:read "*a" + handle:close() + error(string.format("Call `%s` failed:\n%s%s", sig, str, err_str)) + end + + table.insert(ret, str) + str = handle:read() + end + handle:close() + + return ret +end + +function Reader:read_match_created(tournament_address, commitment_hash) + local sig = "matchCreated(bytes32,bytes32,bytes32)" + local data_sig = "(bytes32)" + + local logs = self:_read_logs(tournament_address, sig, { false, false, false }, data_sig) + + local ret = {} + for k, v in ipairs(logs) do + local log = {} + log.tournament_address = tournament_address + log.meta = v.meta + + log.commitment_one = Hash:from_digest_hex(v.emited_topics[2]) + log.commitment_two = Hash:from_digest_hex(v.emited_topics[3]) + log.left_hash = Hash:from_digest_hex(v.decoded_data[1]) + log.match_id_hash = log.commitment_one:join(log.commitment_two) + + ret[k] = log + end + + return ret +end + +function Reader:read_commitment_joined(tournament_address) + local sig = "commitmentJoined(bytes32)" + local data_sig = "(bytes32)" + + local logs = self:_read_logs(tournament_address, sig, { false, false, false }, data_sig) + + local ret = {} + for k, v in ipairs(logs) do + local log = {} + log.tournament_address = tournament_address + log.meta = v.meta + log.root = Hash:from_digest_hex(v.decoded_data[1]) + + ret[k] = log + end + + return ret +end + +function Reader:read_commitment(tournament_address, commitment_hash) + local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" + + local call_ret = self:_call(tournament_address, sig, { commitment_hash:hex_string() }) + assert(#call_ret == 2) + + local allowance, last_resume = call_ret[1]:match "%((%d+),(%d+)%)" + assert(allowance) + assert(last_resume) + local clock = { + allowance = tonumber(allowance), + last_resume = tonumber(last_resume) + } + + local ret = { + clock = clock, + final_state = Hash:from_digest_hex(call_ret[2]) + } + + return ret +end + +function Reader:read_tournament_created(tournament_address, match_id_hash) + local sig = "newInnerTournament(bytes32,address)" + local data_sig = "(address)" + + local logs = self:_read_logs(tournament_address, sig, { match_id_hash:hex_string(), false, false }, data_sig) + assert(#logs <= 1) + + if #logs == 0 then return false end + local log = logs[1] + + local ret = { + parent_match = match_id_hash, + new_tournament = log.decoded_data[1], + } + + return ret +end + +function Reader:match(address, match_id_hash) + local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint256,uint64,uint64)" + local ret = self:_call(address, sig, { match_id_hash:hex_string() }) + ret[1] = Hash:from_digest_hex(ret[1]) + ret[2] = Hash:from_digest_hex(ret[2]) + ret[3] = Hash:from_digest_hex(ret[3]) + + return ret +end + +function Reader:inner_tournament_winner(address) + local sig = "innerTournamentWinner()(bool,bytes32)" + local ret = self:_call(address, sig, {}) + ret[2] = Hash:from_digest_hex(ret[2]) + + return ret +end + +function Reader:root_tournament_winner(address) + local sig = "arbitrationResult()(bool,bytes32,bytes32)" + local ret = self:_call(address, sig, {}) + ret[2] = Hash:from_digest_hex(ret[2]) + ret[3] = Hash:from_digest_hex(ret[3]) + + return ret +end + +function Reader:maximum_delay(address) + local sig = "maximumEnforceableDelay()(uint64)" + local ret = self:_call(address, sig, {}) + + return ret +end + +local cast_advance_template = [[ +cast rpc -r "%s" evm_increaseTime %d +]] + +function Reader:advance_time(seconds) + local cmd = string.format( + cast_advance_template, + self.endpoint, + seconds + ) + + local handle = io.popen(cmd) + assert(handle) + local ret = handle:read "*a" + handle:close() + + if ret:find "Error" then + error(string.format("Advance time `%d`s failed:\n%s", seconds, ret)) + end +end + +return Reader diff --git a/onchain/permissionless-arbitration/offchain/blockchain/sender.lua b/onchain/permissionless-arbitration/offchain/blockchain/sender.lua new file mode 100644 index 00000000..305459ff --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/sender.lua @@ -0,0 +1,161 @@ +local Hash = require "cryptography.hash" +local MerkleTree = require "cryptography.merkle_tree" + +local function quote_args(args, not_quote) + local quoted_args = {} + for _, v in ipairs(args) do + if type(v) == "table" and (getmetatable(v) == Hash or getmetatable(v) == MerkleTree) then + if not_quote then + table.insert(quoted_args, v:hex_string()) + else + table.insert(quoted_args, '"' .. v:hex_string() .. '"') + end + elseif type(v) == "table" then + if v._tag == "tuple" then + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'(" .. ca .. ")'" + table.insert(quoted_args, sb) + else + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'[" .. ca .. "]'" + table.insert(quoted_args, sb) + end + elseif not_quote then + table.insert(quoted_args, tostring(v)) + else + table.insert(quoted_args, '"' .. v .. '"') + end + end + + return quoted_args +end + + +local Sender = {} +Sender.__index = Sender + +function Sender:new(account_index) + local blockchain_data = require "blockchain.constants" + + local sender = { + endpoint = blockchain_data.endpoint, + pk = blockchain_data.pks[account_index], + index = account_index, + tx_count = 0 + } + + setmetatable(sender, self) + return sender +end + +local cast_send_template = [[ +cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 +]] + +function Sender:_send_tx(tournament_address, sig, args) + local quoted_args = quote_args(args) + local args_str = table.concat(quoted_args, " ") + + local cmd = string.format( + cast_send_template, + self.pk, + self.endpoint, + tournament_address, + sig, + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local ret = handle:read "*a" + if ret:find "Error" then + handle:close() + error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) + end + + self.tx_count = self.tx_count + 1 + handle:close() +end + +function Sender:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) + local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { final_state, proof, left_child, right_child } + ) +end + +function Sender:tx_advance_match( + tournament_address, commitment_one, commitment_two, left, right, new_left, new_right +) + local sig = [[advanceMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, new_left, new_right } + ) +end + +function Sender:tx_seal_inner_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash:hex_string(), proof } + ) +end + +function Sender:tx_win_inner_match(tournament_address, child_tournament_address, left, right) + local sig = + [[winInnerMatch(address,bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { child_tournament_address, left, right } + ) +end + +function Sender:tx_seal_leaf_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } + ) +end + +function Sender:tx_win_leaf_match( + tournament_address, commitment_one, commitment_two, left, right, proof +) + local sig = + [[winLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, proof } + ) +end + +return Sender diff --git a/onchain/permissionless-arbitration/offchain/blockchain/utils.lua b/onchain/permissionless-arbitration/offchain/blockchain/utils.lua new file mode 100644 index 00000000..0d6fa0ae --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/utils.lua @@ -0,0 +1,22 @@ +local cast_advance_template = [[ +cast rpc -r "%s" evm_increaseTime %d +]] + +function advance_time(seconds, endpoint) + local cmd = string.format( + cast_advance_template, + endpoint, + seconds + ) + + local handle = io.popen(cmd) + assert(handle) + local ret = handle:read "*a" + handle:close() + + if ret:find "Error" then + error(string.format("Advance time `%d`s failed:\n%s", seconds, ret)) + end +end + +return { advance_time = advance_time } diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 800cf3fc..68e88b21 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -3,120 +3,97 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -print "Hello, world!" -os.execute "cd offchain/program && ./gen_machine_simple.sh" local machine_path = "offchain/program/simple-program" +local FF_TIME = 30 +local IDLE_LIMIT = 5 +local INACTIVE_LIMIT = 10 + +local helper = require 'utils.helper' +local blockchain_utils = require "blockchain.utils" +local time = require "utils.time" +local blockchain_constants = require "blockchain.constants" +local Blockchain = require "blockchain.node" +local Machine = require "computation.machine" --- local Machine = require "computation.machine" --- Machine:get_logs(machine_path, 0, 0) - --- os.exit() - - -local Player = require "player" -local Client = require "blockchain.client" +print "Hello, world!" +os.execute "cd offchain/program && ./gen_machine_simple.sh" -local Machine = require "computation.machine" local m = Machine:new_from_path(machine_path) local initial_hash = m:state().root_hash - -local Blockchain = require "blockchain.node" local blockchain = Blockchain:new() +time.sleep(2) local contract = blockchain:deploy_contract(initial_hash) - -local p1 -do - local CommitmentBuilder = require "computation.commitment" - local builder = CommitmentBuilder:new(machine_path) - local client = Client:new(blockchain) - p1 = Player:new(contract, client, builder, machine_path) +-- add more player instances here +local cmds = { + string.format([[sh -c "echo $$ ; exec ./offchain/player/honest_player.lua %d %s %s | tee honest.log"]], 1, contract, machine_path), + string.format([[sh -c "echo $$ ; exec ./offchain/player/dishonest_player.lua %d %s %s %s | tee dishonest.log"]], 2, contract, machine_path, initial_hash) +} +local pid_reader = {} +local pid_player = {} + +for i, cmd in ipairs(cmds) do + local reader = io.popen(cmd) + local pid = reader:read() + pid_reader[pid] = reader + pid_player[pid] = i end -local p2 -do - local FakeCommitmentBuilder = require "computation.fake_commitment" - - -- m:run(m.start_cycle + 1) - -- local second_hash = m:state().root_hash - -- local builder = FakeCommitmentBuilder:new(initial_hash, second_hash) - - local builder = FakeCommitmentBuilder:new(initial_hash) - local client = Client:new(blockchain) - p2 = Player:new(contract, client, builder, machine_path) -end +-- gracefully end children processes +setmetatable(pid_reader, { + __gc = function(t) + helper.stop_players(t) + end +}) -local i = 0 +local no_active_players = 0 +local all_idle = 0 +local last_ts = [[01/01/2000 00:00:00]] while true do - print(string.format("\n\n### ROUND %d ###\n", i)) - - print "Player 1 react" - if p1:react() then break end - - print "" - - print "Player 2 react" - if p2:react() then break end - - i = i + 1 + local players = 0 + + for pid, reader in pairs(pid_reader) do + local msg_out = 0 + players = players + 1 + last_ts, msg_out = helper.log_to_ts(reader, last_ts) + + -- close the reader and delete the reader entry when there's no more msg in the buffer + -- and the process has already ended + if msg_out == 0 and helper.is_zombie(pid) then + helper.log(pid_player[pid], string.format("player process %s is dead", pid)) + reader:close() + pid_reader[pid] = nil + pid_player[pid] = nil + end + end + + if players > 0 then + if helper.all_players_idle(pid_player) then + all_idle = all_idle + 1 + helper.rm_all_players_idle(pid_player) + else + all_idle = 0 + end + + -- if all players are idle for `IDLE_LIMIT` consecutive iterations, advance blockchain + if all_idle == IDLE_LIMIT then + print(string.format("all players idle, fastforward blockchain for %d seconds...", FF_TIME)) + blockchain_utils.advance_time(FF_TIME, blockchain_constants.endpoint) + all_idle = 0 + end + end + + if players == 0 then + no_active_players = no_active_players + 1 + else + no_active_players = 0 + end + + -- if no active player processes for `INACTIVE_LIMIT` consecutive iterations, break loop + if no_active_players == INACTIVE_LIMIT then + print("no active players, end program...") + break + end end - - - - - - - - - - --- os.execute "jsonrpc-remote-cartesi-machine --server-address=localhost:8080 &" --- os.execute "sleep 2" - --- require "cryptography.merkle_builder" --- require "computation.commitment" --- require "computation.machine_test" - --- local Blockchain = require "blockchain.node" - --- local bc = Blockchain:new(100) --- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" --- bc:deploy_contract(initial_hash) - - --- local utils = require "utils" --- local cartesi = {} --- cartesi.rpc = require"cartesi.grpc" - --- local remote = cartesi.rpc.stub("localhost:8080", "localhost:8081") --- local v = assert(remote.get_version()) --- print(string.format("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch)) - --- local machine = remote.machine("program/simple-program") --- print("cycles", machine:read_mcycle(), machine:read_uarch_cycle()) --- machine:snapshot() --- machine:snapshot() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) --- machine:rollback() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) --- machine:rollback() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) - - - - --- machine:read_mcycle() - - - print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua deleted file mode 100644 index 9963dd97..00000000 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ /dev/null @@ -1,293 +0,0 @@ -local constants = require "constants" -local bint = require 'utils.bint' (256) -- use 256 bits integers -local Machine = require "computation.machine" - -local Player = {} -Player.__index = Player - -function Player:new(root_tournament_address, client, commitment_builder, machine_path) - local player = { - machine_path = machine_path, - root_tournament = { - base_big_cycle = 0, - address = root_tournament_address, - level = constants.levels, - parent = false, - }, - client = client, - commitment_builder = commitment_builder, - commitments = {}, - called_win = {} - } - - setmetatable(player, self) - return player -end - -function Player:react() - if self.has_lost then - return - end - return self:_react_tournament(self.root_tournament) -end - -function Player:_react_tournament(tournament) - local commitment = self.commitments[tournament.address] - if not commitment then - commitment = self.commitment_builder:build( - tournament.base_big_cycle, - tournament.level - ) - self.commitments[tournament.address] = commitment - end - - if not tournament.parent then - local winner_final_state = self.client:root_tournament_winner(tournament.address) - if winner_final_state[1] == "true" then - print "TOURNAMENT FINISHED, HURRAYYY" - print("Winner commitment: " .. winner_final_state[2]:hex_string()) - print("Final state: " .. winner_final_state[3]:hex_string()) - return true - end - else - local tournament_winner = self.client:inner_tournament_winner(tournament.address) - if tournament_winner[1] == "true" then - local old_commitment = self.commitments[tournament.parent.address] - if tournament_winner[2] ~= old_commitment.root_hash then - print "player lost tournament" - self.has_lost = true - return - end - - if self.called_win[tournament.address] then - print "player already called winInnerMatch" - return - else - self.called_win[tournament.address] = true - end - - print(string.format( - "win tournament %s of level %d for commitment %s", - tournament.address, - tournament.level, - commitment.root_hash - )) - local _, left, right = old_commitment:children(old_commitment.root_hash) - self.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) - return - end - end - - local latest_match = self:_latest_match(tournament, commitment) - - if not latest_match then - self:_join_tournament_if_needed(tournament, commitment) - else - self:_react_match(latest_match, commitment) - end -end - -function Player:_react_match(match, commitment) - -- TODO call timeout if needed - - print("HEIGHT", match.current_height) - if match.current_height == 0 then - -- match sealed - if match.tournament.level == 1 then - local f, left, right = commitment.root_hash:children() - assert(f) - - local finished = - self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() - - if finished then - local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - print("DELAY", delay - os.time()) - return - end - - print(string.format( - "Calculating access logs for step %s", - match.running_leaf - )) - - local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() - local ucycle = (match.running_leaf & constants.uarch_span):touinteger() - local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - - print(string.format( - "win leaf match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - local ok, e = pcall(self.client.tx_win_leaf_match, - self.client, - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - logs - ) - if not ok then - print(string.format( - "win leaf match reverted: %s", - e - )) - end - else - local address = self.client:read_tournament_created( - match.tournament.address, - match.match_id_hash - ).new_tournament - - local new_tournament = {} - new_tournament.address = address - new_tournament.level = match.tournament.level - 1 - new_tournament.parent = match.tournament - new_tournament.base_big_cycle = match.base_big_cycle - - return self:_react_tournament(new_tournament) - end - elseif match.current_height == 1 then - -- match to be sealed - local found, left, right = match.current_other_parent:children() - if not found then return end - - local initial_hash, proof - if match.running_leaf:iszero() then - initial_hash, proof = commitment.implicit_hash, {} - else - initial_hash, proof = commitment:prove_leaf(match.running_leaf) - end - - if match.tournament.level == 1 then - print(string.format( - "seal leaf match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_seal_leaf_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - initial_hash, - proof - ) - else - print(string.format( - "seal inner match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_seal_inner_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - initial_hash, - proof - ) - - local address = self.client:read_tournament_created( - match.tournament.address, - match.match_id_hash - ).new_tournament - - local new_tournament = {} - new_tournament.address = address - new_tournament.level = match.tournament.level - 1 - new_tournament.parent = match.tournament - new_tournament.base_big_cycle = match.base_big_cycle - - return self:_react_tournament(new_tournament) - end - else - -- match running - local found, left, right = match.current_other_parent:children() - if not found then - return - end - - local new_left, new_right - if left ~= match.current_left then - local f - f, new_left, new_right = left:children() - assert(f) - else - local f - f, new_left, new_right = right:children() - assert(f) - end - - print(string.format( - "advance match with current height %d in tournament %s of level %d for commitment %s", - match.current_height, - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_advance_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - new_left, - new_right - ) - end -end - -function Player:_latest_match(tournament, commitment) - local matches = self.client:read_match_created(tournament.address, commitment.root_hash) - local last_match = matches[#matches] - - if not last_match then return false end - - local m = self.client:match(tournament.address, last_match.match_id_hash) - if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then - return false - end - last_match.current_other_parent = m[1] - last_match.current_left = m[2] - last_match.current_right = m[3] - last_match.running_leaf = bint(m[4]) - last_match.current_height = tonumber(m[5]) - last_match.level = tonumber(m[6]) - last_match.tournament = tournament - - local level = tournament.level - local base = bint(tournament.base_big_cycle) - local step = bint(1) << constants.log2step[level] - last_match.leaf_cycle = base + (step * last_match.running_leaf) - last_match.base_big_cycle = (last_match.leaf_cycle >> constants.log2_uarch_span):touinteger() - - return last_match -end - -function Player:_join_tournament_if_needed(tournament, commitment) - local c = self.client:read_commitment(tournament.address, commitment.root_hash) - - if c.clock.allowance == 0 then - local f, left, right = commitment:children(commitment.root_hash) - assert(f) - local last, proof = commitment:last() - - print(string.format( - "join tournament %s of level %d with commitment %s", - tournament.address, - tournament.level, - commitment.root_hash - )) - self.client:tx_join_tournament(tournament.address, last, proof, left, right) - end -end - -return Player diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua new file mode 100755 index 00000000..219fb6b5 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -0,0 +1,40 @@ +#!/usr/bin/lua +package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.path = package.path .. ";./offchain/?.lua" +package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" + +local State = require "player.state" +local Hash = require "cryptography.hash" +local Sender = require "blockchain.sender" +local HonestStrategy = require "player.honest_strategy" + +local time = require "utils.time" +local helper = require 'utils.helper' + +local player_index = tonumber(arg[1]) +local tournament = arg[2] +local machine_path = arg[3] +local initial_hash = Hash:from_digest_hex(arg[4]) + +local state = State:new(tournament) +local sender = Sender:new(player_index) +local honest_strategy +do + local FakeCommitmentBuilder = require "computation.fake_commitment" + local builder = FakeCommitmentBuilder:new(initial_hash) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) +end + +while true do + state:fetch() + local tx_count = sender.tx_count + if honest_strategy:react(state) then break end + -- player is considered idle if no tx sent in current iteration + if tx_count == sender.tx_count then + helper.log(player_index, "player idling") + helper.touch_player_idle(player_index) + else + helper.rm_player_idle(player_index) + end + time.sleep(1) +end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_player.lua b/onchain/permissionless-arbitration/offchain/player/honest_player.lua new file mode 100755 index 00000000..36673b8c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -0,0 +1,39 @@ +#!/usr/bin/lua +package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.path = package.path .. ";./offchain/?.lua" +package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" + +local State = require "player.state" +local Hash = require "cryptography.hash" +local HonestStrategy = require "player.honest_strategy" +local Sender = require "blockchain.sender" + +local time = require "utils.time" +local helper = require 'utils.helper' + +local player_index = tonumber(arg[1]) +local tournament = arg[2] +local machine_path = arg[3] + +local state = State:new(tournament) +local sender = Sender:new(player_index) +local honest_strategy +do + local CommitmentBuilder = require "computation.commitment" + local builder = CommitmentBuilder:new(machine_path) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) +end + +while true do + state:fetch() + local tx_count = sender.tx_count + if honest_strategy:react(state) then break end + -- player is considered idle if no tx sent in current iteration + if tx_count == sender.tx_count then + helper.log(player_index, "player idling") + helper.touch_player_idle(player_index) + else + helper.rm_player_idle(player_index) + end + time.sleep(1) +end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua new file mode 100644 index 00000000..73c984d1 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -0,0 +1,258 @@ +local constants = require "constants" +local helper = require 'utils.helper' + +local Machine = require "computation.machine" + +local HonestStrategy = {} +HonestStrategy.__index = HonestStrategy + +function HonestStrategy:new(commitment_builder, machine_path, sender) + local honest_strategy = { + commitment_builder = commitment_builder, + machine_path = machine_path, + sender = sender + } + + setmetatable(honest_strategy, self) + return honest_strategy +end + +function HonestStrategy:_join_tournament(state, tournament, commitment) + local f, left, right = commitment:children(commitment.root_hash) + assert(f) + local last, proof = commitment:last() + + helper.log(self.sender.index, string.format( + "join tournament %s of level %d with commitment %s", + tournament.address, + tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_join_tournament( + tournament.address, + last, + proof, + left, + right + ) + if not ok then + helper.log(self.sender.index, string.format( + "join tournament reverted: %s", + e + )) + end +end + +function HonestStrategy:_react_match(state, match, commitment) + -- TODO call timeout if needed + + helper.log(self.sender.index, "Enter match at HEIGHT: " .. match.current_height) + if match.current_height == 0 then + -- match sealed + if match.tournament.level == 1 then + local f, left, right = commitment.root_hash:children() + assert(f) + + helper.log(self.sender.index, string.format( + "Calculating access logs for step %s", + match.running_leaf + )) + + local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() + local ucycle = (match.running_leaf & constants.uarch_span):touinteger() + local logs = Machine:get_logs(self.machine_path, cycle, ucycle) + + helper.log(self.sender.index, string.format( + "win leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_win_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + logs + ) + if not ok then + helper.log(self.sender.index, string.format( + "win leaf match reverted: %s", + e + )) + end + elseif match.inner_tournament then + return self:_react_tournament(state, match.inner_tournament) + end + elseif match.current_height == 1 then + -- match to be sealed + local found, left, right = match.current_other_parent:children() + if not found then + return + end + + local initial_hash, proof + if match.running_leaf:iszero() then + initial_hash, proof = commitment.implicit_hash, {} + else + initial_hash, proof = commitment:prove_leaf(match.running_leaf) + end + + if match.tournament.level == 1 then + helper.log(self.sender.index, string.format( + "seal leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_seal_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + if not ok then + helper.log(self.sender.index, string.format( + "seal leaf match reverted: %s", + e + )) + end + else + helper.log(self.sender.index, string.format( + "seal inner match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_seal_inner_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + if not ok then + helper.log(self.sender.index, string.format( + "seal inner match reverted: %s", + e + )) + end + end + else + -- match running + local found, left, right = match.current_other_parent:children() + if not found then + return + end + + local new_left, new_right + if left ~= match.current_left then + local f + f, new_left, new_right = left:children() + assert(f) + else + local f + f, new_left, new_right = right:children() + assert(f) + end + + helper.log(self.sender.index, string.format( + "advance match with current height %d in tournament %s of level %d for commitment %s", + match.current_height, + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_advance_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + new_left, + new_right + ) + if not ok then + helper.log(self.sender.index, string.format( + "advance match reverted: %s", + e + )) + end + end +end + +function HonestStrategy:_react_tournament(state, tournament) + helper.log(self.sender.index, "Enter tournament at address: " .. tournament.address) + local commitment = self.commitment_builder:build( + tournament.base_big_cycle, + tournament.level + ) + + local tournament_winner = tournament.tournament_winner + if tournament_winner[1] == "true" then + if not tournament.parent then + helper.log(self.sender.index, "TOURNAMENT FINISHED, HURRAYYY") + helper.log(self.sender.index, "Winner commitment: " .. tournament_winner[2]:hex_string()) + helper.log(self.sender.index, "Final state: " .. tournament_winner[3]:hex_string()) + return true + else + local old_commitment = self.commitment_builder:build( + tournament.parent.base_big_cycle, + tournament.parent.level + ) + if tournament_winner[2] ~= old_commitment.root_hash then + helper.log(self.sender.index, "player lost tournament") + return true + end + + if tournament.commitments[commitment.root_hash].called_win then + helper.log(self.sender.index, "player already called winInnerMatch") + return + else + tournament.commitments[commitment.root_hash].called_win = true + end + + helper.log(self.sender.index, string.format( + "win tournament %s of level %d for commitment %s", + tournament.address, + tournament.level, + commitment.root_hash + )) + local _, left, right = old_commitment:children(old_commitment.root_hash) + local ok, e = self.sender:tx_win_inner_match( + tournament.parent.address, + tournament.address, + left, + right + ) + if not ok then + helper.log(self.sender.index, string.format( + "win inner match reverted: %s", + e + )) + end + return + end + end + + if not tournament.commitments[commitment.root_hash] then + self:_join_tournament(state, tournament, commitment) + else + local latest_match = tournament.commitments[commitment.root_hash].latest_match + if latest_match then + return self:_react_match(state, latest_match, commitment) + end + end +end + +function HonestStrategy:react(state) + return self:_react_tournament(state, state.root_tournament) +end + +return HonestStrategy diff --git a/onchain/permissionless-arbitration/offchain/player/state.lua b/onchain/permissionless-arbitration/offchain/player/state.lua new file mode 100644 index 00000000..7f009538 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/state.lua @@ -0,0 +1,114 @@ +local constants = require "constants" +local bint = require 'utils.bint' (256) -- use 256 bits integers + +local Reader = require "blockchain.reader" + +local State = {} +State.__index = State + +function State:new(root_tournament_address) + local state = { + root_tournament = { + base_big_cycle = 0, + address = root_tournament_address, + level = constants.levels, + parent = false, + commitments = {}, + matches = {}, + tournament_winner = {} + }, + reader = Reader:new() + } + + setmetatable(state, self) + return state +end + +function State:fetch() + return self:_fetch_tournament(self.root_tournament) +end + +function State:_fetch_tournament(tournament) + local matches = self:_matches(tournament) + local commitments = self.reader:read_commitment_joined(tournament.address) + + for _, log in ipairs(commitments) do + local root = log.root + local status = self.reader:read_commitment(tournament.address, root) + tournament.commitments[root] = { status = status, latest_match = false } + end + + for _, match in ipairs(matches) do + if match then + self:_fetch_match(match) + tournament.commitments[match.commitment_one].latest_match = match + tournament.commitments[match.commitment_two].latest_match = match + end + end + tournament.matches = matches + + if not tournament.parent then + tournament.tournament_winner = self.reader:root_tournament_winner(tournament.address) + else + tournament.tournament_winner = self.reader:inner_tournament_winner(tournament.address) + end +end + +function State:_fetch_match(match) + if match.current_height == 0 then + -- match sealed + if match.tournament.level == 1 then + + match.finished = + self.reader:match(match.tournament.address, match.match_id_hash)[1]:is_zero() + + if match.finished then + match.delay = tonumber(self.reader:maximum_delay(match.tournament.address)[1]) + end + else + local address = self.reader:read_tournament_created( + match.tournament.address, + match.match_id_hash + ).new_tournament + + local new_tournament = {} + new_tournament.address = address + new_tournament.level = match.tournament.level - 1 + new_tournament.parent = match.tournament + new_tournament.base_big_cycle = match.base_big_cycle + new_tournament.commitments = {} + match.inner_tournament = new_tournament + + return self:_fetch_tournament(new_tournament) + end + end +end + +function State:_matches(tournament) + local matches = self.reader:read_match_created(tournament.address) + + for k, match in ipairs(matches) do + local m = self.reader:match(tournament.address, match.match_id_hash) + if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then + matches[k] = false + else + match.current_other_parent = m[1] + match.current_left = m[2] + match.current_right = m[3] + match.running_leaf = bint(m[4]) + match.current_height = tonumber(m[5]) + match.level = tonumber(m[6]) + match.tournament = tournament + + local level = tournament.level + local base = bint(tournament.base_big_cycle) + local step = bint(1) << constants.log2step[level] + match.leaf_cycle = base + (step * match.running_leaf) + match.base_big_cycle = (match.leaf_cycle >> constants.log2_uarch_span):touinteger() + end + end + + return matches +end + +return State diff --git a/onchain/permissionless-arbitration/offchain/utils/color.lua b/onchain/permissionless-arbitration/offchain/utils/color.lua new file mode 100644 index 00000000..c188228c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/color.lua @@ -0,0 +1,133 @@ +-- color.lua + +-------------------------------------------------------------------------------- + +-- A super-simple way to make colored text output in Lua. +-- To use, simply print out things from this module, then print out some text. +-- +-- Example: +-- print(color.bg.green .. color.fg.RED .. "This is bright red on green") +-- print(color.invert .. "This is inverted..." .. color.reset .. " And this isn't.") +-- print(color.fg(0xDE) .. color.bg(0xEE) .. "You can use xterm-256 colors too!" .. color.reset) +-- print("And also " .. color.bold .. "BOLD" .. color.normal .. " if you want.") +-- print(color.bold .. color.fg.BLUE .. color.bg.blue .. "Miss your " .. color.fg.RED .. "C-64" .. color.fg.BLUE .. "?" .. color.reset) +-- +-- You can see all these examples in action by calling color.test() +-- +-- Can't pick a good color scheme? Look at a handy chart: +-- print(color.chart()) +-- +-- If you want to add anything to this, check out the Wikipedia page on ANSI control codes: +-- http://en.wikipedia.org/wiki/ANSI_escape_code + +-------------------------------------------------------------------------------- + +-- Copyright (C) 2012 Ross Andrews +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Lesser General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with this program. If not, see . + +-------------------------------------------------------------------------------- + +-- A note about licensing: +-- +-- The LGPL isn't really intended to be used with non-compiled libraries. The way +-- I interpret derivative works of this library is this: if you don't modify this +-- file, and the program it's embedded in doesn't modify the Lua table it defines, +-- then you can distribute it with a program under any license. If you do either +-- of those things, then you've created a derivative work of this library and you +-- have to release the modifications you made under this same license. + +local color = { _NAME = "color" } +local _M = color + +local esc = string.char(27, 91) + +local names = {'black', 'red', 'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} +local hi_names = {'BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'PINK', 'CYAN', 'WHITE'} + +color.fg, color.bg = {}, {} + +for i, name in ipairs(names) do + color.fg[name] = esc .. tostring(30+i-1) .. 'm' + _M[name] = color.fg[name] + color.bg[name] = esc .. tostring(40+i-1) .. 'm' +end + +for i, name in ipairs(hi_names) do + color.fg[name] = esc .. tostring(90+i-1) .. 'm' + _M[name] = color.fg[name] + color.bg[name] = esc .. tostring(100+i-1) .. 'm' +end + +local function fg256(_,n) + return esc .. "38;5;" .. n .. 'm' +end + +local function bg256(_,n) + return esc .. "48;5;" .. n .. 'm' +end + +setmetatable(color.fg, {__call = fg256}) +setmetatable(color.bg, {__call = bg256}) + +color.reset = esc .. '0m' +color.clear = esc .. '2J' + +color.bold = esc .. '1m' +color.faint = esc .. '2m' +color.normal = esc .. '22m' +color.invert = esc .. '7m' +color.underline = esc .. '4m' + +color.hide = esc .. '?25l' +color.show = esc .. '?25h' + +function color.move(x, y) + return esc .. y .. ';' .. x .. 'H' +end + +color.home = color.move(1, 1) + +-------------------------------------------------- + +function color.chart(ch,col) + local cols = '0123456789abcdef' + + ch = ch or ' ' + col = col or color.fg.black + local str = color.reset .. color.bg.WHITE .. col + + for y = 0, 15 do + for x = 0, 15 do + local lbl = cols:sub(x+1, x+1) + if x == 0 then lbl = cols:sub(y+1, y+1) end + + str = str .. color.bg.black .. color.fg.WHITE .. lbl + str = str .. color.bg(x+y*16) .. col .. ch + end + str = str .. color.reset .. "\n" + end + return str .. color.reset +end + +function color.test() + print(color.reset .. color.bg.green .. color.fg.RED .. "This is bright red on green" .. color.reset) + print(color.invert .. "This is inverted..." .. color.reset .. " And this isn't.") + print(color.fg(0xDE) .. color.bg(0xEE) .. "You can use xterm-256 colors too!" .. color.reset) + print("And also " .. color.bold .. "BOLD" .. color.normal .. " if you want.") + print(color.bold .. color.fg.BLUE .. color.bg.blue .. "Miss your " .. color.fg.RED .. "C-64" .. color.fg.BLUE .. "?" .. color.reset) + print("Try printing " .. color.underline .. _M._NAME .. ".chart()" .. color.reset) +end + +return color diff --git a/onchain/permissionless-arbitration/offchain/utils/helper.lua b/onchain/permissionless-arbitration/offchain/utils/helper.lua new file mode 100644 index 00000000..8410296b --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/helper.lua @@ -0,0 +1,94 @@ +local color = require "utils.color" + +local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} +local idle_template = [[ls player%d_idle 2>/dev/null | grep player%d_idle | wc -l]] +local ps_template = [[ps %s | grep defunct | wc -l]] + +local function log(player_index, msg) + local color_index = (player_index - 1) % #names + 1 + local timestamp = os.date("%m/%d/%Y %X") + print(color.reset .. color.fg[names[color_index]] .. string.format("[#%d][%s] %s", player_index, timestamp, msg) .. color.reset) +end + +local function log_to_ts(reader, last_ts) + -- print everything hold in the buffer which has smaller timestamp + -- this is to synchronise when there're gaps in between the logs + local msg_output = 0 + while true do + local msg = reader:read() + if msg then + msg_output = msg_output + 1 + print(msg) + + local i, j = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") + if i and j then + local timestamp = msg:sub(i, j) + if timestamp > last_ts then + last_ts = timestamp + break + end + end + else + break + end + end + return last_ts, msg_output +end + +local function is_zombie(pid) + local reader = io.popen(string.format(ps_template, pid)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end + +local function stop_players(pid_reader) + for pid, reader in pairs(pid_reader) do + print(string.format("Stopping player with pid %s...", pid)) + os.execute(string.format("kill -15 %s", pid)) + reader:close() + print "Player stopped" + end +end + +local function touch_player_idle(player_index) + os.execute(string.format("touch player%d_idle", player_index)) +end + +local function is_player_idle(player_index) + local reader = io.popen(string.format(idle_template, player_index, player_index)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end + +local function rm_player_idle(player_index) + os.execute(string.format("rm player%d_idle 2>/dev/null", player_index)) +end + +local function all_players_idle(pid_player) + for pid, player in pairs(pid_player) do + if not is_player_idle(player) then + return false + end + end + return true +end + +local function rm_all_players_idle(pid_player) + for pid, player in pairs(pid_player) do + rm_player_idle(player) + end + return true +end + +return { + log = log, + log_to_ts = log_to_ts, + is_zombie = is_zombie, + stop_players = stop_players, + touch_player_idle = touch_player_idle, + all_players_idle = all_players_idle, + rm_all_players_idle = rm_all_players_idle, + rm_player_idle = rm_player_idle +} diff --git a/onchain/permissionless-arbitration/offchain/utils/time.lua b/onchain/permissionless-arbitration/offchain/utils/time.lua new file mode 100644 index 00000000..85e8b293 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/time.lua @@ -0,0 +1,8 @@ +local clock = os.clock + +function sleep(number_of_seconds) + local t0 = clock() + while clock() - t0 <= number_of_seconds do end +end + +return {sleep = sleep} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index bd75fcff..3874fe0e 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -67,6 +67,7 @@ abstract contract Tournament { Tree.Node indexed two, Tree.Node leftOfTwo ); + event commitmentJoined(Tree.Node root); // @@ -148,6 +149,7 @@ abstract contract Tournament { _clock.setNewPaused(startInstant, allowance); pairCommitment(_commitmentRoot, _clock, _leftNode, _rightNode); + emit commitmentJoined(_commitmentRoot); } /// @notice Advance the match until the smallest divergence is found at current level