From b560f3c03096a086b07c5e817dc0105b1fffcf9b Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:21:48 +0800 Subject: [PATCH 1/6] refactor(arbitration): split players into different Lua VMs --- onchain/permissionless-arbitration/Dockerfile | 2 +- .../offchain/blockchain/client.lua | 15 +- .../offchain/blockchain/constants.lua | 30 ++++ .../offchain/blockchain/node.lua | 56 ++---- .../offchain/entrypoint.lua | 164 +++++++----------- .../offchain/player/dishonest_player.lua | 26 +++ .../offchain/player/honest_player.lua | 23 +++ .../honest_strategy.lua} | 42 +++-- .../offchain/utils/color.lua | 133 ++++++++++++++ .../offchain/utils/log.lua | 35 ++++ .../offchain/utils/time.lua | 8 + 11 files changed, 362 insertions(+), 172 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/constants.lua create mode 100755 onchain/permissionless-arbitration/offchain/player/dishonest_player.lua create mode 100755 onchain/permissionless-arbitration/offchain/player/honest_player.lua rename onchain/permissionless-arbitration/offchain/{player.lua => player/honest_strategy.lua} (87%) create mode 100644 onchain/permissionless-arbitration/offchain/utils/color.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/log.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/time.lua 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 index 2db65552..5b33e8c8 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -142,11 +142,12 @@ end local Client = {} Client.__index = Client -function Client:new(blockchain) +function Client:new(account_index) + local blockchain_data = require "blockchain.constants" + local client = { - endpoint = blockchain.endpoint, - account = blockchain:new_account(), - blockchain = blockchain, + endpoint = blockchain_data.endpoint, + pk = blockchain_data.pks[account_index], } setmetatable(client, self) @@ -193,8 +194,6 @@ function Client:_read_logs(tournament_address, sig, topics, data_sig) end local ret = parse_logs(logs, data_sig) - - self.blockchain:read_to("eth_getLogs") return ret end @@ -234,7 +233,6 @@ function Client:_call(address, sig, args) end handle:close() - self.blockchain:read_to("eth_call") return ret end @@ -348,7 +346,7 @@ function Client:_send_tx(tournament_address, sig, args) local cmd = string.format( cast_send_template, - self.account.pk, + self.pk, self.endpoint, tournament_address, sig, @@ -364,7 +362,6 @@ function Client:_send_tx(tournament_address, sig, args) 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) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua new file mode 100644 index 00000000..75a96362 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua @@ -0,0 +1,30 @@ +-- contains default 10 accounts of anvil test node, and deployed tournament contract address +local constants = { + endpoint = "http://127.0.0.1:8545", + addresses = { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + }, + pks = { + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + }, +} + +return constants diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index 3ca56e13..93aaf98c 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -11,7 +11,7 @@ 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 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"]], account_num) local reader = io.popen(cmd) assert(reader, "`popen` returned nil reader") @@ -29,37 +29,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 +50,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 +67,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 +84,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 +101,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 +119,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 +138,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 @@ -179,12 +151,12 @@ function Blockchain:new(account_num) local blockchain = {} local handle = start_blockchain(account_num) - local accounts, endpoint = capture_blockchain_data(handle.reader, account_num) + 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 @@ -211,10 +183,6 @@ function Blockchain:deploy_contract(initial_hash, deployer) 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) diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 800cf3fc..6b220831 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -3,120 +3,86 @@ 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 ps_template = [[ps %s | grep defunct | wc -l]] + +local log = require 'utils.log' +local Blockchain = require "blockchain.node" +local Machine = require "computation.machine" --- local Machine = require "computation.machine" --- Machine:get_logs(machine_path, 0, 0) +local function is_zombie(pid) + local reader = io.popen(string.format(ps_template, pid)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end --- os.exit() +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 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() 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) + stop_players(t) + end +}) -local i = 0 +local no_active_players = 0 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 last_ts = [[01/01/2000 00:00:00]] + local players = 0 + + for pid, reader in pairs(pid_reader) do + players = players + 1 + if is_zombie(pid) then + log.log(pid_player[pid], string.format("player process %s is dead", pid)) + reader:close() + pid_reader[pid] = nil + else + last_ts = log.log_to_ts(reader, last_ts) + 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 10 consecutive iterations, break loop + if no_active_players == 10 then break end + + -- TODO: if all players are idle, advance anvil 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/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua new file mode 100755 index 00000000..ca6bbab8 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -0,0 +1,26 @@ +#!/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 Player = require "player.honest_strategy" +local Client = require "blockchain.client" +local Hash = require "cryptography.hash" + +local time = require "utils.time" + +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 +do + local FakeCommitmentBuilder = require "computation.fake_commitment" + local builder = FakeCommitmentBuilder:new(initial_hash) + p = Player:new(tournament, player_index, builder, machine_path) +end + +while true do + if p:react() 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 new file mode 100755 index 00000000..c9c592f3 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -0,0 +1,23 @@ +#!/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 Player = require "player.honest_strategy" + +local time = require "utils.time" + +local player_index = tonumber(arg[1]) +local tournament = arg[2] +local machine_path = arg[3] +local p +do + local CommitmentBuilder = require "computation.commitment" + local builder = CommitmentBuilder:new(machine_path) + p = Player:new(tournament, player_index, builder, machine_path) +end + +while true do + if p:react() then break end + time.sleep(1) +end diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua similarity index 87% rename from onchain/permissionless-arbitration/offchain/player.lua rename to onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index 9963dd97..fe676b25 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -1,11 +1,14 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers +local log = require 'utils.log' + local Machine = require "computation.machine" +local Client = require "blockchain.client" local Player = {} Player.__index = Player -function Player:new(root_tournament_address, client, commitment_builder, machine_path) +function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) local player = { machine_path = machine_path, root_tournament = { @@ -14,10 +17,11 @@ function Player:new(root_tournament_address, client, commitment_builder, machine level = constants.levels, parent = false, }, - client = client, + client = Client:new(player_index), commitment_builder = commitment_builder, commitments = {}, - called_win = {} + called_win = {}, + player_index = player_index } setmetatable(player, self) @@ -26,7 +30,7 @@ end function Player:react() if self.has_lost then - return + return true end return self:_react_tournament(self.root_tournament) end @@ -44,9 +48,9 @@ function Player:_react_tournament(tournament) 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()) + log.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") + log.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) + log.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) return true end else @@ -54,19 +58,19 @@ function Player:_react_tournament(tournament) 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" + log.log(self.player_index, "player lost tournament") self.has_lost = true return end if self.called_win[tournament.address] then - print "player already called winInnerMatch" + log.log(self.player_index, "player already called winInnerMatch") return else self.called_win[tournament.address] = true end - print(string.format( + log.log(self.player_index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, @@ -90,7 +94,7 @@ end function Player:_react_match(match, commitment) -- TODO call timeout if needed - print("HEIGHT", match.current_height) + log.log(self.player_index, "HEIGHT: " .. match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then @@ -102,11 +106,11 @@ function Player:_react_match(match, commitment) if finished then local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - print("DELAY", delay - os.time()) + log.log(self.player_index, "DELAY", delay - os.time()) return end - print(string.format( + log.log(self.player_index, string.format( "Calculating access logs for step %s", match.running_leaf )) @@ -115,7 +119,7 @@ function Player:_react_match(match, commitment) local ucycle = (match.running_leaf & constants.uarch_span):touinteger() local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - print(string.format( + log.log(self.player_index, string.format( "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -131,7 +135,7 @@ function Player:_react_match(match, commitment) logs ) if not ok then - print(string.format( + log.log(self.player_index, string.format( "win leaf match reverted: %s", e )) @@ -163,7 +167,7 @@ function Player:_react_match(match, commitment) end if match.tournament.level == 1 then - print(string.format( + log.log(self.player_index, string.format( "seal leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -179,7 +183,7 @@ function Player:_react_match(match, commitment) proof ) else - print(string.format( + log.log(self.player_index, string.format( "seal inner match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -226,7 +230,7 @@ function Player:_react_match(match, commitment) assert(f) end - print(string.format( + log.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, @@ -280,7 +284,7 @@ function Player:_join_tournament_if_needed(tournament, commitment) assert(f) local last, proof = commitment:last() - print(string.format( + log.log(self.player_index, string.format( "join tournament %s of level %d with commitment %s", tournament.address, tournament.level, 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/log.lua b/onchain/permissionless-arbitration/offchain/utils/log.lua new file mode 100644 index 00000000..3ed32ff5 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/log.lua @@ -0,0 +1,35 @@ +local color = require "utils.color" + +local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} + +function log(player_index, msg) + local color_index = player_index % #names + 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 + +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 + while true do + local msg = reader:read() + if msg then + print(msg) + + local ts_position = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") + if ts_position then + local timestamp = msg:sub(ts_position) + if timestamp > last_ts then + last_ts = timestamp + break + end + end + else + break + end + end + + return last_ts +end + +return { log = log, log_to_ts = log_to_ts } 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} From eeb6f4f1ecafe1a919606e8b81720c720c2b4b03 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 4 Sep 2023 19:39:41 +0800 Subject: [PATCH 2/6] feat(arbitration): improve lua prototype * Rename `utils.log` to `utils.helper` * Fastforward blockchain when all players idle --- .../offchain/blockchain/client.lua | 21 +++++ .../offchain/entrypoint.lua | 55 +++++------ .../offchain/player/honest_strategy.lua | 40 ++++---- .../offchain/utils/helper.lua | 93 +++++++++++++++++++ .../offchain/utils/log.lua | 35 ------- 5 files changed, 166 insertions(+), 78 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/utils/helper.lua delete mode 100644 onchain/permissionless-arbitration/offchain/utils/log.lua diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 5b33e8c8..d20b13cb 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -426,6 +426,27 @@ function Client:tx_win_leaf_match( ) end +local cast_advance_template = [[ +cast rpc -r "%s" evm_increaseTime %d +]] + +function Client: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 Client diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 6b220831..7cc0cf24 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -4,28 +4,11 @@ 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 ps_template = [[ps %s | grep defunct | wc -l]] -local log = require 'utils.log' +local helper = require 'utils.helper' local Blockchain = require "blockchain.node" local Machine = require "computation.machine" - -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 Client = require "blockchain.client" print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" @@ -53,23 +36,45 @@ end -- gracefully end children processes setmetatable(pid_reader, { __gc = function(t) - stop_players(t) + helper.stop_players(t) end }) 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 last_ts = [[01/01/2000 00:00:00]] local players = 0 for pid, reader in pairs(pid_reader) do + local msg_out = 0 players = players + 1 - if is_zombie(pid) then - log.log(pid_player[pid], string.format("player process %s is dead", pid)) + 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 - last_ts = log.log_to_ts(reader, last_ts) + all_idle = 0 + end + + -- if all players are idle for 10 consecutive iterations, advance blockchain + if all_idle == 10 then + print("all players idle, fastforward blockchain for 30 seconds...") + client:advance_time(30) + all_idle = 0 end end @@ -81,8 +86,6 @@ while true do -- if no active player processes for 10 consecutive iterations, break loop if no_active_players == 10 then break end - - -- TODO: if all players are idle, advance anvil end print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index fe676b25..965e4549 100644 --- a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -1,6 +1,6 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers -local log = require 'utils.log' +local helper = require 'utils.helper' local Machine = require "computation.machine" local Client = require "blockchain.client" @@ -48,9 +48,9 @@ function Player:_react_tournament(tournament) if not tournament.parent then local winner_final_state = self.client:root_tournament_winner(tournament.address) if winner_final_state[1] == "true" then - log.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") - log.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) - log.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) + 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 @@ -58,19 +58,19 @@ function Player:_react_tournament(tournament) if tournament_winner[1] == "true" then local old_commitment = self.commitments[tournament.parent.address] if tournament_winner[2] ~= old_commitment.root_hash then - log.log(self.player_index, "player lost tournament") + helper.log(self.player_index, "player lost tournament") self.has_lost = true return end if self.called_win[tournament.address] then - log.log(self.player_index, "player already called winInnerMatch") + helper.log(self.player_index, "player already called winInnerMatch") return else self.called_win[tournament.address] = true end - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, @@ -94,7 +94,7 @@ end function Player:_react_match(match, commitment) -- TODO call timeout if needed - log.log(self.player_index, "HEIGHT: " .. match.current_height) + helper.log(self.player_index, "HEIGHT: " .. match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then @@ -106,11 +106,11 @@ function Player:_react_match(match, commitment) if finished then local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - log.log(self.player_index, "DELAY", delay - os.time()) + helper.log(self.player_index, "DELAY", delay - os.time()) return end - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "Calculating access logs for step %s", match.running_leaf )) @@ -119,7 +119,7 @@ function Player:_react_match(match, commitment) local ucycle = (match.running_leaf & constants.uarch_span):touinteger() local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - log.log(self.player_index, string.format( + 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, @@ -135,7 +135,7 @@ function Player:_react_match(match, commitment) logs ) if not ok then - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "win leaf match reverted: %s", e )) @@ -157,7 +157,10 @@ function Player:_react_match(match, commitment) 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 + if not found then + helper.touch_player_idle(self.player_index) + return + end local initial_hash, proof if match.running_leaf:iszero() then @@ -167,7 +170,7 @@ function Player:_react_match(match, commitment) end if match.tournament.level == 1 then - log.log(self.player_index, string.format( + 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, @@ -183,7 +186,7 @@ function Player:_react_match(match, commitment) proof ) else - log.log(self.player_index, string.format( + 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, @@ -216,6 +219,7 @@ function Player:_react_match(match, commitment) -- match running local found, left, right = match.current_other_parent:children() if not found then + helper.touch_player_idle(self.player_index) return end @@ -230,7 +234,7 @@ function Player:_react_match(match, commitment) assert(f) end - log.log(self.player_index, string.format( + 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, @@ -284,13 +288,15 @@ function Player:_join_tournament_if_needed(tournament, commitment) assert(f) local last, proof = commitment:last() - log.log(self.player_index, string.format( + 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 diff --git a/onchain/permissionless-arbitration/offchain/utils/helper.lua b/onchain/permissionless-arbitration/offchain/utils/helper.lua new file mode 100644 index 00000000..6dca7d41 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/helper.lua @@ -0,0 +1,93 @@ +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", 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 +} diff --git a/onchain/permissionless-arbitration/offchain/utils/log.lua b/onchain/permissionless-arbitration/offchain/utils/log.lua deleted file mode 100644 index 3ed32ff5..00000000 --- a/onchain/permissionless-arbitration/offchain/utils/log.lua +++ /dev/null @@ -1,35 +0,0 @@ -local color = require "utils.color" - -local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} - -function log(player_index, msg) - local color_index = player_index % #names - 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 - -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 - while true do - local msg = reader:read() - if msg then - print(msg) - - local ts_position = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") - if ts_position then - local timestamp = msg:sub(ts_position) - if timestamp > last_ts then - last_ts = timestamp - break - end - end - else - break - end - end - - return last_ts -end - -return { log = log, log_to_ts = log_to_ts } 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 3/6] 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 +} From 486225c04b22d72c4647749f9977b9aa321209e9 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:27:35 +0800 Subject: [PATCH 4/6] chore(arbitration): increase anvil accounts to 40 --- .../offchain/blockchain/client.lua | 96 ------------------- .../offchain/blockchain/constants.lua | 62 +++++++++++- .../offchain/blockchain/node.lua | 25 ++--- 3 files changed, 70 insertions(+), 113 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index d20b13cb..26ded24d 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -448,99 +448,3 @@ function Client:advance_time(seconds) 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 index 75a96362..3949135c 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua @@ -1,4 +1,4 @@ --- contains default 10 accounts of anvil test node, and deployed tournament contract address +-- contains default 40 accounts of anvil test node local constants = { endpoint = "http://127.0.0.1:8545", addresses = { @@ -12,6 +12,36 @@ local constants = { "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", @@ -24,6 +54,36 @@ local constants = { "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", }, } diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index 93aaf98c..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 > anvil.log 2>&1"]], 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") @@ -147,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 handle = start_blockchain() local accounts, endpoint = capture_blockchain_data() blockchain._handle = handle blockchain._accounts = accounts - blockchain._current_account = 1 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") @@ -178,13 +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 --- local bc = Blockchain:new(100) --- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" --- bc:deploy_contract(initial_hash) - return Blockchain From 72fdb2a0ccecedebb2927d49a20227cc73d8683d Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:03:28 +0800 Subject: [PATCH 5/6] fix(arbitration): use `pcall` on every tx in case of race condition --- .../offchain/player/strategy.lua | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/player/strategy.lua b/onchain/permissionless-arbitration/offchain/player/strategy.lua index b35ac010..c48cf0cf 100644 --- a/onchain/permissionless-arbitration/offchain/player/strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/strategy.lua @@ -18,7 +18,20 @@ local function _join_tournament_if_needed(player, tournament) tournament.level, tournament.commitment.root_hash )) - player.client:tx_join_tournament(tournament.address, last, proof, left, right) + 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 @@ -89,7 +102,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_seal_leaf_match( + local ok, e = pcall(player.client.tx_seal_leaf_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -98,6 +112,12 @@ _react_match_honestly = function(player, match, commitment) initial_hash, proof ) + if not ok then + helper.log(player.player_index, string.format( + "seal leaf match reverted: %s", + e + )) + end else helper.log(player.player_index, string.format( "seal inner match in tournament %s of level %d for commitment %s", @@ -105,7 +125,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_seal_inner_match( + local ok, e = pcall(player.client.tx_seal_inner_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -114,6 +135,12 @@ _react_match_honestly = function(player, match, commitment) initial_hash, proof ) + if not ok then + helper.log(player.player_index, string.format( + "seal inner match reverted: %s", + e + )) + end end else -- match running @@ -141,7 +168,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_advance_match( + local ok, e = pcall(player.client.tx_advance_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -150,6 +178,12 @@ _react_match_honestly = function(player, match, commitment) new_left, new_right ) + if not ok then + helper.log(player.player_index, string.format( + "advance match reverted: %s", + e + )) + end end end @@ -183,7 +217,19 @@ _react_tournament_honestly = function(player, tournament) 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) + local ok, e = pcall(player.client.tx_win_inner_match, + player.client, + tournament.parent.address, + tournament.address, + left, + right + ) + if not ok then + helper.log(player.player_index, string.format( + "win inner match reverted: %s", + e + )) + end return end end From fd8984393cc740773988b13cece06dcc872a117e Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:20:07 +0800 Subject: [PATCH 6/6] feat(arbitration): resolve PR comments * Split `Client` into `Reader` and `Sender` * Add variables to control behaviors in entrypoint * Add `HonestStrategy` concrete type * Move `pcall` into `Sender:_send_tx` * `State` tracks all tournaments/commitments/matches * Optimize `tournament` and `match` structure * Optimize `player.idle` logic * Add `commitmentJoined` event --- .../blockchain/{client.lua => reader.lua} | 184 ++++-------------- .../offchain/blockchain/sender.lua | 161 +++++++++++++++ .../offchain/blockchain/utils.lua | 22 +++ .../offchain/entrypoint.lua | 21 +- .../offchain/player/dishonest_player.lua | 26 ++- .../offchain/player/honest_player.lua | 26 ++- .../{strategy.lua => honest_strategy.lua} | 167 ++++++++-------- .../offchain/player/state.lua | 123 ++++++------ .../offchain/utils/helper.lua | 7 +- .../src/tournament/abstracts/Tournament.sol | 2 + 10 files changed, 423 insertions(+), 316 deletions(-) rename onchain/permissionless-arbitration/offchain/blockchain/{client.lua => reader.lua} (59%) create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/sender.lua create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/utils.lua rename onchain/permissionless-arbitration/offchain/player/{strategy.lua => honest_strategy.lua} (56%) 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..68e88b21 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" @@ -16,6 +21,7 @@ os.execute "cd offchain/program && ./gen_machine_simple.sh" local m = Machine:new_from_path(machine_path) local initial_hash = m:state().root_hash local blockchain = Blockchain:new() +time.sleep(2) local contract = blockchain:deploy_contract(initial_hash) -- add more player instances here @@ -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