diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua similarity index 59% rename from onchain/permissionless-arbitration/offchain/blockchain/client.lua rename to onchain/permissionless-arbitration/offchain/blockchain/reader.lua index 26ded24d..3a257683 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua @@ -1,5 +1,4 @@ local Hash = require "cryptography.hash" -local MerkleTree = require "cryptography.merkle_tree" local eth_ebi = require "utils.eth_ebi" local function parse_topics(json) @@ -107,51 +106,18 @@ local function sort_and_dedup(t) 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 +local Reader = {} +Reader.__index = Reader - return quoted_args -end - - -local Client = {} -Client.__index = Client - -function Client:new(account_index) +function Reader:new() local blockchain_data = require "blockchain.constants" - local client = { - endpoint = blockchain_data.endpoint, - pk = blockchain_data.pks[account_index], + local reader = { + endpoint = blockchain_data.endpoint } - setmetatable(client, self) - return client + setmetatable(reader, self) + return reader end local cast_logs_template = [==[ @@ -159,7 +125,7 @@ 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) +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) @@ -201,7 +167,7 @@ local cast_call_template = [==[ cast call --rpc-url "%s" "%s" "%s" %s 2>&1 ]==] -function Client:_call(address, sig, args) +function Reader:_call(address, sig, args) local quoted_args = {} for _, v in ipairs(args) do table.insert(quoted_args, '"' .. v .. '"') @@ -236,14 +202,11 @@ function Client:_call(address, sig, args) return ret end -function Client:read_match_created(tournament_address, commitment_hash) +function Reader: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 logs = self:_read_logs(tournament_address, sig, { false, false, false }, data_sig) local ret = {} for k, v in ipairs(logs) do @@ -262,7 +225,26 @@ function Client:read_match_created(tournament_address, commitment_hash) return ret end -function Client:read_commitment(tournament_address, commitment_hash) +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() }) @@ -284,7 +266,7 @@ function Client:read_commitment(tournament_address, commitment_hash) return ret end -function Client:read_tournament_created(tournament_address, match_id_hash) +function Reader:read_tournament_created(tournament_address, match_id_hash) local sig = "newInnerTournament(bytes32,address)" local data_sig = "(address)" @@ -302,7 +284,7 @@ function Client:read_tournament_created(tournament_address, match_id_hash) return ret end -function Client:match(address, match_id_hash) +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]) @@ -312,7 +294,7 @@ function Client:match(address, match_id_hash) return ret end -function Client:inner_tournament_winner(address) +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]) @@ -320,7 +302,7 @@ function Client:inner_tournament_winner(address) return ret end -function Client:root_tournament_winner(address) +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]) @@ -329,108 +311,18 @@ function Client:root_tournament_winner(address) return ret end -function Client:maximum_delay(address) +function Reader: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.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() -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 - local cast_advance_template = [[ cast rpc -r "%s" evm_increaseTime %d ]] -function Client:advance_time(seconds) +function Reader:advance_time(seconds) local cmd = string.format( cast_advance_template, self.endpoint, @@ -447,4 +339,4 @@ function Client:advance_time(seconds) end end -return Client +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 b074484a..f4f2e485 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -4,11 +4,16 @@ package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" 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 Client = require "blockchain.client" print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" @@ -26,6 +31,7 @@ local cmds = { local pid_reader = {} local pid_player = {} +time.sleep(2) for i, cmd in ipairs(cmds) do local reader = io.popen(cmd) local pid = reader:read() @@ -42,7 +48,6 @@ setmetatable(pid_reader, { local no_active_players = 0 local all_idle = 0 -local client = Client:new(1) local last_ts = [[01/01/2000 00:00:00]] while true do local players = 0 @@ -70,10 +75,10 @@ while true do all_idle = 0 end - -- if all players are idle for 10 consecutive iterations, advance blockchain - if all_idle == 5 then - print("all players idle, fastforward blockchain for 30 seconds...") - client:advance_time(30) + -- 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 @@ -84,8 +89,8 @@ while true do no_active_players = 0 end - -- if no active player processes for 10 consecutive iterations, break loop - if no_active_players == 10 then + -- 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 diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua index d90e8654..219fb6b5 100755 --- a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -3,26 +3,38 @@ 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 Player = require "player.state" -local Client = require "blockchain.client" +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 strategy = require "player.strategy" +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 p + +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) - p = Player:new(tournament, player_index, builder, machine_path) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) end while true do - p:fetch() - if strategy.react_honestly(p) then break end + 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 index 85654729..36673b8c 100755 --- a/onchain/permissionless-arbitration/offchain/player/honest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -3,25 +3,37 @@ 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 Player = require "player.state" -local Client = require "blockchain.client" +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 strategy = require "player.strategy" +local helper = require 'utils.helper' local player_index = tonumber(arg[1]) local tournament = arg[2] local machine_path = arg[3] -local p + +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) - p = Player:new(tournament, player_index, builder, machine_path) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) end while true do - p:fetch() - if strategy.react_honestly(p) then break end + 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/strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua similarity index 56% rename from onchain/permissionless-arbitration/offchain/player/strategy.lua rename to onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index c48cf0cf..73c984d1 100644 --- a/onchain/permissionless-arbitration/offchain/player/strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -3,67 +3,72 @@ local helper = require 'utils.helper' local Machine = require "computation.machine" -local _react_match_honestly -local _react_tournament_honestly - -local function _join_tournament_if_needed(player, tournament) - if tournament.commitment_status.clock.allowance == 0 then - local f, left, right = tournament.commitment:children(tournament.commitment.root_hash) - assert(f) - local last, proof = tournament.commitment:last() - - helper.log(player.player_index, string.format( - "join tournament %s of level %d with commitment %s", - tournament.address, - tournament.level, - tournament.commitment.root_hash +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 )) - local ok, e = pcall(player.client.tx_join_tournament, - player.client, - tournament.address, - last, - proof, - left, - right - ) - if not ok then - helper.log(player.player_index, string.format( - "join tournament reverted: %s", - e - )) - end - else - helper.touch_player_idle(player.player_index) end end -_react_match_honestly = function(player, match, commitment) +function HonestStrategy:_react_match(state, match, commitment) -- TODO call timeout if needed - helper.log(player.player_index, "HEIGHT: " .. match.current_height) + 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(player.player_index, string.format( + 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(player.machine_path, cycle, ucycle) + local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - helper.log(player.player_index, string.format( + 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 = pcall(player.client.tx_win_leaf_match, - player.client, + local ok, e = self.sender:tx_win_leaf_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -72,19 +77,18 @@ _react_match_honestly = function(player, match, commitment) logs ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win leaf match reverted: %s", e )) end - else - return _react_tournament_honestly(player, match.inner_tournament) + 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 - helper.touch_player_idle(player.player_index) return end @@ -96,14 +100,13 @@ _react_match_honestly = function(player, match, commitment) end if match.tournament.level == 1 then - helper.log(player.player_index, string.format( + 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 = pcall(player.client.tx_seal_leaf_match, - player.client, + local ok, e = self.sender:tx_seal_leaf_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -113,20 +116,19 @@ _react_match_honestly = function(player, match, commitment) proof ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal leaf match reverted: %s", e )) end else - helper.log(player.player_index, string.format( + 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 = pcall(player.client.tx_seal_inner_match, - player.client, + local ok, e = self.sender:tx_seal_inner_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -136,7 +138,7 @@ _react_match_honestly = function(player, match, commitment) proof ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal inner match reverted: %s", e )) @@ -146,7 +148,6 @@ _react_match_honestly = function(player, match, commitment) -- match running local found, left, right = match.current_other_parent:children() if not found then - helper.touch_player_idle(player.player_index) return end @@ -161,15 +162,14 @@ _react_match_honestly = function(player, match, commitment) assert(f) end - helper.log(player.player_index, string.format( + 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 = pcall(player.client.tx_advance_match, - player.client, + local ok, e = self.sender:tx_advance_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -179,7 +179,7 @@ _react_match_honestly = function(player, match, commitment) new_right ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "advance match reverted: %s", e )) @@ -187,45 +187,52 @@ _react_match_honestly = function(player, match, commitment) end end -_react_tournament_honestly = function(player, tournament) +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(player.player_index, "TOURNAMENT FINISHED, HURRAYYY") - helper.log(player.player_index, "Winner commitment: " .. tournament_winner[2]:hex_string()) - helper.log(player.player_index, "Final state: " .. tournament_winner[3]:hex_string()) + 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 = tournament.parent.commitment + 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(player.player_index, "player lost tournament") - player.has_lost = true - return + helper.log(self.sender.index, "player lost tournament") + return true end - if tournament.called_win then - helper.log(player.player_index, "player already called winInnerMatch") + if tournament.commitments[commitment.root_hash].called_win then + helper.log(self.sender.index, "player already called winInnerMatch") return else - tournament.called_win = true + tournament.commitments[commitment.root_hash].called_win = true end - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, - tournament.commitment.root_hash + commitment.root_hash )) local _, left, right = old_commitment:children(old_commitment.root_hash) - local ok, e = pcall(player.client.tx_win_inner_match, - player.client, + local ok, e = self.sender:tx_win_inner_match( tournament.parent.address, tournament.address, left, right ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win inner match reverted: %s", e )) @@ -234,20 +241,18 @@ _react_tournament_honestly = function(player, tournament) end end - if not tournament.latest_match then - _join_tournament_if_needed(player, tournament) + if not tournament.commitments[commitment.root_hash] then + self:_join_tournament(state, tournament, commitment) else - _react_match_honestly(player, tournament.latest_match, tournament.commitment) + 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 -local function _react_honestly(player) - if player.has_lost then - return true - end - return _react_tournament_honestly(player, player.root_tournament) +function HonestStrategy:react(state) + return self:_react_tournament(state, state.root_tournament) end -return { - react_honestly = _react_honestly -} +return HonestStrategy diff --git a/onchain/permissionless-arbitration/offchain/player/state.lua b/onchain/permissionless-arbitration/offchain/player/state.lua index de1a2d5f..7f009538 100644 --- a/onchain/permissionless-arbitration/offchain/player/state.lua +++ b/onchain/permissionless-arbitration/offchain/player/state.lua @@ -1,77 +1,72 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers -local Client = require "blockchain.client" +local Reader = require "blockchain.reader" -local Player = {} -Player.__index = Player +local State = {} +State.__index = State -function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) - local player = { - machine_path = machine_path, +function State:new(root_tournament_address) + local state = { root_tournament = { base_big_cycle = 0, address = root_tournament_address, level = constants.levels, parent = false, - commitment = false, - commitment_status = {}, - tournament_winner = {}, - latest_match = {}, + commitments = {}, + matches = {}, + tournament_winner = {} }, - client = Client:new(player_index), - commitment_builder = commitment_builder, - player_index = player_index + reader = Reader:new() } - setmetatable(player, self) - return player + setmetatable(state, self) + return state end -function Player:fetch() +function State:fetch() return self:_fetch_tournament(self.root_tournament) end -function Player:_fetch_tournament(tournament) - local commitment = tournament.commitment - if not commitment then - commitment = self.commitment_builder:build( - tournament.base_big_cycle, - tournament.level - ) - tournament.commitment = commitment - end +function State:_fetch_tournament(tournament) + local matches = self:_matches(tournament) + local commitments = self.reader:read_commitment_joined(tournament.address) - if not tournament.parent then - tournament.tournament_winner = self.client:root_tournament_winner(tournament.address) - else - tournament.tournament_winner = self.client:inner_tournament_winner(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 - tournament.latest_match = self:_latest_match(tournament) + 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.latest_match then - tournament.commitment_status = self.client:read_commitment(tournament.address, commitment.root_hash) + if not tournament.parent then + tournament.tournament_winner = self.reader:root_tournament_winner(tournament.address) else - self:_fetch_match(tournament.latest_match, commitment) + tournament.tournament_winner = self.reader:inner_tournament_winner(tournament.address) end end -function Player:_fetch_match(match, commitment) +function State:_fetch_match(match) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then - local f, left, right = commitment.root_hash:children() - assert(f) match.finished = - self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() + self.reader:match(match.tournament.address, match.match_id_hash)[1]:is_zero() if match.finished then - match.delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) + match.delay = tonumber(self.reader:maximum_delay(match.tournament.address)[1]) end else - local address = self.client:read_tournament_created( + local address = self.reader:read_tournament_created( match.tournament.address, match.match_id_hash ).new_tournament @@ -81,6 +76,7 @@ function Player:_fetch_match(match, commitment) 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) @@ -88,32 +84,31 @@ function Player:_fetch_match(match, commitment) end end -function Player:_latest_match(tournament) - local commitment = 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 +function State:_matches(tournament) + local matches = self.reader:read_match_created(tournament.address) - 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 + 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 - 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 + + return matches end -return Player +return State diff --git a/onchain/permissionless-arbitration/offchain/utils/helper.lua b/onchain/permissionless-arbitration/offchain/utils/helper.lua index 6dca7d41..8410296b 100644 --- a/onchain/permissionless-arbitration/offchain/utils/helper.lua +++ b/onchain/permissionless-arbitration/offchain/utils/helper.lua @@ -1,7 +1,7 @@ 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 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) @@ -63,7 +63,7 @@ local function is_player_idle(player_index) end local function rm_player_idle(player_index) - os.execute(string.format("rm player%d_idle", player_index)) + os.execute(string.format("rm player%d_idle 2>/dev/null", player_index)) end local function all_players_idle(pid_player) @@ -89,5 +89,6 @@ return { 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_all_players_idle = rm_all_players_idle, + rm_player_idle = rm_player_idle } 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