From 6f5c759c132da02742cc191d23d6c7d2a2ee23ae Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:27:05 +0800 Subject: [PATCH] refactor(arbitration): split player state from player strategy --- .../offchain/entrypoint.lua | 7 +- .../offchain/player/dishonest_player.lua | 6 +- .../offchain/player/honest_player.lua | 8 +- .../offchain/player/honest_strategy.lua | 303 ------------------ .../offchain/player/state.lua | 119 +++++++ .../offchain/player/strategy.lua | 207 ++++++++++++ 6 files changed, 341 insertions(+), 309 deletions(-) delete mode 100644 onchain/permissionless-arbitration/offchain/player/honest_strategy.lua create mode 100644 onchain/permissionless-arbitration/offchain/player/state.lua create mode 100644 onchain/permissionless-arbitration/offchain/player/strategy.lua diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 7cc0cf24..b074484a 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -71,7 +71,7 @@ while true do end -- if all players are idle for 10 consecutive iterations, advance blockchain - if all_idle == 10 then + if all_idle == 5 then print("all players idle, fastforward blockchain for 30 seconds...") client:advance_time(30) all_idle = 0 @@ -85,7 +85,10 @@ while true do end -- if no active player processes for 10 consecutive iterations, break loop - if no_active_players == 10 then break end + if no_active_players == 10 then + print("no active players, end program...") + break + end end print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua index ca6bbab8..d90e8654 100755 --- a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -3,11 +3,12 @@ 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.honest_strategy" +local Player = require "player.state" local Client = require "blockchain.client" local Hash = require "cryptography.hash" local time = require "utils.time" +local strategy = require "player.strategy" local player_index = tonumber(arg[1]) local tournament = arg[2] @@ -21,6 +22,7 @@ do end while true do - if p:react() then break end + p:fetch() + if strategy.react_honestly(p) then break 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 c9c592f3..85654729 100755 --- a/onchain/permissionless-arbitration/offchain/player/honest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -3,9 +3,12 @@ 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.honest_strategy" +local Player = require "player.state" +local Client = require "blockchain.client" +local Hash = require "cryptography.hash" local time = require "utils.time" +local strategy = require "player.strategy" local player_index = tonumber(arg[1]) local tournament = arg[2] @@ -18,6 +21,7 @@ do end while true do - if p:react() then break end + p:fetch() + if strategy.react_honestly(p) then break 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 deleted file mode 100644 index 965e4549..00000000 --- a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua +++ /dev/null @@ -1,303 +0,0 @@ -local constants = require "constants" -local bint = require 'utils.bint' (256) -- use 256 bits integers -local helper = require 'utils.helper' - -local Machine = require "computation.machine" -local Client = require "blockchain.client" - -local Player = {} -Player.__index = Player - -function Player:new(root_tournament_address, player_index, 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:new(player_index), - commitment_builder = commitment_builder, - commitments = {}, - called_win = {}, - player_index = player_index - } - - setmetatable(player, self) - return player -end - -function Player:react() - if self.has_lost then - return true - 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 - helper.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") - helper.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) - helper.log(self.player_index, "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 - helper.log(self.player_index, "player lost tournament") - self.has_lost = true - return - end - - if self.called_win[tournament.address] then - helper.log(self.player_index, "player already called winInnerMatch") - return - else - self.called_win[tournament.address] = true - end - - helper.log(self.player_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) - 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 - - helper.log(self.player_index, "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]) - helper.log(self.player_index, "DELAY", delay - os.time()) - return - end - - helper.log(self.player_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.player_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(self.client.tx_win_leaf_match, - self.client, - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - logs - ) - if not ok then - helper.log(self.player_index, 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 - helper.touch_player_idle(self.player_index) - 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.player_index, 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 - helper.log(self.player_index, 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 - helper.touch_player_idle(self.player_index) - 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.player_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 - )) - 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() - - helper.log(self.player_index, 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) - else - helper.touch_player_idle(self.player_index) - end -end - -return Player diff --git a/onchain/permissionless-arbitration/offchain/player/state.lua b/onchain/permissionless-arbitration/offchain/player/state.lua new file mode 100644 index 00000000..de1a2d5f --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/state.lua @@ -0,0 +1,119 @@ +local constants = require "constants" +local bint = require 'utils.bint' (256) -- use 256 bits integers + +local Client = require "blockchain.client" + +local Player = {} +Player.__index = Player + +function Player:new(root_tournament_address, player_index, 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, + commitment = false, + commitment_status = {}, + tournament_winner = {}, + latest_match = {}, + }, + client = Client:new(player_index), + commitment_builder = commitment_builder, + player_index = player_index + } + + setmetatable(player, self) + return player +end + +function Player: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 + + 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) + end + + tournament.latest_match = self:_latest_match(tournament) + + if not tournament.latest_match then + tournament.commitment_status = self.client:read_commitment(tournament.address, commitment.root_hash) + else + self:_fetch_match(tournament.latest_match, commitment) + end +end + +function Player:_fetch_match(match, commitment) + 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() + + if match.finished then + match.delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) + 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 + match.inner_tournament = new_tournament + + return self:_fetch_tournament(new_tournament) + end + 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 + + 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 + +return Player diff --git a/onchain/permissionless-arbitration/offchain/player/strategy.lua b/onchain/permissionless-arbitration/offchain/player/strategy.lua new file mode 100644 index 00000000..b35ac010 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/strategy.lua @@ -0,0 +1,207 @@ +local constants = require "constants" +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 + )) + player.client:tx_join_tournament(tournament.address, last, proof, left, right) + else + helper.touch_player_idle(player.player_index) + end +end + +_react_match_honestly = function(player, match, commitment) + -- TODO call timeout if needed + + helper.log(player.player_index, "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( + "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) + + helper.log(player.player_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, + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + logs + ) + if not ok then + helper.log(player.player_index, string.format( + "win leaf match reverted: %s", + e + )) + end + else + return _react_tournament_honestly(player, 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 + + 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(player.player_index, string.format( + "seal leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + player.client:tx_seal_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + else + helper.log(player.player_index, string.format( + "seal inner match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + player.client:tx_seal_inner_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + end + else + -- match running + local found, left, right = match.current_other_parent:children() + if not found then + helper.touch_player_idle(player.player_index) + 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(player.player_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 + )) + player.client:tx_advance_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + new_left, + new_right + ) + end +end + +_react_tournament_honestly = function(player, tournament) + 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()) + return true + else + local old_commitment = tournament.parent.commitment + if tournament_winner[2] ~= old_commitment.root_hash then + helper.log(player.player_index, "player lost tournament") + player.has_lost = true + return + end + + if tournament.called_win then + helper.log(player.player_index, "player already called winInnerMatch") + return + else + tournament.called_win = true + end + + helper.log(player.player_index, string.format( + "win tournament %s of level %d for commitment %s", + tournament.address, + tournament.level, + tournament.commitment.root_hash + )) + local _, left, right = old_commitment:children(old_commitment.root_hash) + player.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) + return + end + end + + if not tournament.latest_match then + _join_tournament_if_needed(player, tournament) + else + _react_match_honestly(player, tournament.latest_match, tournament.commitment) + end +end + +local function _react_honestly(player) + if player.has_lost then + return true + end + return _react_tournament_honestly(player, player.root_tournament) +end + +return { + react_honestly = _react_honestly +}