diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs b/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs index 160db18d..8cd72296 100644 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs +++ b/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs @@ -166,7 +166,10 @@ impl PRTConfig { ) .expect("could not create `state_manager`"); - let mut machine = state_manager.latest_snapshot().unwrap(); + let mut machine = state_manager + .snapshot(0) + .unwrap() + .expect("epoch zero should always exist"); assert_eq!( machine.state_hash().unwrap(), address_book.initial_hash, diff --git a/prt/client-lua/player/reader.lua b/prt/client-lua/player/reader.lua index 143e9a10..8cf0a789 100644 --- a/prt/client-lua/player/reader.lua +++ b/prt/client-lua/player/reader.lua @@ -129,8 +129,8 @@ function Reader:new(endpoint) return reader end -function Reader._get_block_number(block) - local cmd = string.format("cast block " .. block .. " 2>&1") +function Reader:_get_block_number(block) + local cmd = string.format("cast block %s --rpc-url %s 2>&1", block, self.endpoint) local handle = io.popen(cmd) assert(handle) @@ -302,7 +302,7 @@ function Reader:read_commitment(tournament_address, commitment_hash) assert(allowance) assert(last_resume) - local block_number = Reader._get_block_number("latest") + local block_number = self:_get_block_number("latest") local commitment = { clock = CommitmentClock:new(allowance, last_resume, block_number), diff --git a/prt/tests/common/runners/sybil_runner.lua b/prt/tests/common/runners/sybil_runner.lua index d1685810..64638bea 100755 --- a/prt/tests/common/runners/sybil_runner.lua +++ b/prt/tests/common/runners/sybil_runner.lua @@ -22,20 +22,24 @@ local function sybil_player(root_tournament, strategy, blockchain_endpoint) end) end -local function sybil_runner(commitment_builder, machine_path, root_tournament, inputs, player_id) +local function sybil_runner(commitment_builder, machine_path, root_tournament, inputs, player_id, config) + config = config or {} player_id = player_id or 1 + local pk = config.pk or blockchain_consts.pks[player_id] + local endpoint = config.endpoint or blockchain_consts.endpoint + local strategy = HonestStrategy:new( commitment_builder, inputs, machine_path, - Sender:new(blockchain_consts.pks[player_id], player_id, blockchain_consts.endpoint) + Sender:new(pk, player_id, endpoint) ) strategy:disable_gc() local react = sybil_player( root_tournament, strategy, - blockchain_consts.endpoint + endpoint ) return react diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 771cdc3a..70ce368b 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -1,6 +1,8 @@ local eth_abi = require "utils.eth_abi" local blockchain_constants = require "blockchain.constants" local InnerReader = require "player.reader" +local uint256 = require "utils.bint" (256) +local time = require "utils.time" local function parse_topics(json) local _, _, topics = json:find( @@ -62,13 +64,15 @@ end local Reader = {} Reader.__index = Reader -function Reader:new(input_box_address, consensus_address, endpoint) +function Reader:new(input_box_address, consensus_address, endpoint, genesis) + genesis = genesis or 0 endpoint = endpoint or blockchain_constants.endpoint local reader = { input_box_address = input_box_address, consensus_address = consensus_address, endpoint = assert(endpoint), - inner_reader = InnerReader:new(endpoint) + inner_reader = InnerReader:new(endpoint), + genesis = genesis, } setmetatable(reader, self) @@ -77,7 +81,7 @@ end local cast_logs_template = [==[ cast rpc -r "%s" eth_getLogs \ - '[{"fromBlock": "earliest", "toBlock": "latest", "address": "%s", "topics": [%s]}]' -w 2>&1 + '[{"fromBlock":"0x%x", "toBlock":"0x%x", "address": "%s", "topics": [%s]}]' -w 2>&1 ]==] function Reader:_read_logs(contract_address, sig, topics, data_sig) @@ -98,23 +102,61 @@ function Reader:_read_logs(contract_address, sig, topics, data_sig) end local topic_str = table.concat(topics_strs, ", ") - local cmd = string.format( - cast_logs_template, - self.endpoint, - contract_address, - topic_str - ) + local latest + do + local cmd = string.format("cast block-number --rpc-url %s", self.endpoint) + local handle = io.popen(cmd) + assert(handle) + latest = handle:read() + local tail = handle:read "*a" + if latest:find "Error" or tail:find "error" then + handle:close() + error(string.format("Call `%s` failed:\n%s%s", cmd, latest, tail)) + end + handle:close() + latest = tonumber(latest) + end - local handle = io.popen(cmd) - assert(handle) - local logs = handle:read "*a" - handle:close() + local function call(from, to) + local cmd = string.format( + cast_logs_template, + self.endpoint, + from, + to, + contract_address, + topic_str + ) + + local handle = io.popen(cmd) + assert(handle) + local logs = handle:read "*a" + handle:close() + + if logs:find "Error" then + error(string.format("Read logs `%s` failed:\n%s", sig, logs)) + end - if logs:find "Error" then - error(string.format("Read logs `%s` failed:\n%s", sig, logs)) + local ret = parse_logs(logs, data_sig) + return ret + end + + local ret = {} + local from = self.genesis + while true do + local to = math.min(from + 1000, latest) + local r = call(from, to) + for _, value in ipairs(r) do + table.insert(ret, value) + end + + if to == latest then + break + end + + from = to + 1 + time.sleep_ms(500) end - local ret = parse_logs(logs, data_sig) return ret end @@ -206,7 +248,7 @@ function Reader:root_tournament_winner(address) end function Reader:commitment_exists(tournament, commitment) - local commitments = self.inner_reader:read_commitment_joined(tournament) + local commitments = self.inner_reader:read_commitment_joined(tournament) for _, log in ipairs(commitments) do if log.root == commitment then @@ -217,4 +259,20 @@ function Reader:commitment_exists(tournament, commitment) return false end +function Reader:balance(address) + local cmd = string.format("cast balance %s --rpc-url %s", address, self.endpoint) + local handle = io.popen(cmd) + assert(handle) + + local balance = handle:read() + local tail = handle:read "*a" + if balance:find "Error" or tail:find "error" then + handle:close() + error(string.format("Call `%s` failed:\n%s%s", cmd, balance, tail)) + end + handle:close() + + return uint256.new(balance) +end + return Reader diff --git a/prt/tests/rollups/dave/sender.lua b/prt/tests/rollups/dave/sender.lua index 32c14628..acaa2898 100644 --- a/prt/tests/rollups/dave/sender.lua +++ b/prt/tests/rollups/dave/sender.lua @@ -78,6 +78,9 @@ function Sender:_send_tx(contract_address, sig, args) if ret:find "Error" then handle:close() error(string.format("Send transaction `%s` reverted:\n%s", cmd, ret)) + else + print("Transaction sent:") + print(ret) end self.tx_count = self.tx_count + 1 @@ -95,9 +98,7 @@ end function Sender:tx_add_inputs(inputs) for _,payload in ipairs(inputs) do - self:advance_blocks(1) self:tx_add_input(payload) - self:advance_blocks(1) end end diff --git a/prt/tests/rollups/justfile b/prt/tests/rollups/justfile index 54830d4e..a2133d64 100644 --- a/prt/tests/rollups/justfile +++ b/prt/tests/rollups/justfile @@ -34,3 +34,15 @@ test-honeypot-case CASE: # read logs from PRT Rollups node, run in separate terminal after `test-echo` read-node-logs: cat dave.log; tail -f dave.log + +# sepolia-honeypot +download-sepolia-honeypot: clean-sepolia-honeypot-snapshot + mkdir -p sepolia/machine-image + curl -L \ + https://github.com/cartesi/honeypot/releases/download/v2.0.0-rc-3/honeypot-snapshot-sepolia.tar.gz | \ + tar -xz -C sepolia/machine-image +clean-sepolia-honeypot-snapshot: + rm -rf sepolia/machine-image + +run-sepolia SCRIPT: + ( set -a; source sepolia/.env; exec lua sepolia/{{SCRIPT}}.lua) diff --git a/prt/tests/rollups/sepolia/.gitignore b/prt/tests/rollups/sepolia/.gitignore new file mode 100644 index 00000000..01c0d385 --- /dev/null +++ b/prt/tests/rollups/sepolia/.gitignore @@ -0,0 +1,2 @@ +machine-image +.env diff --git a/prt/tests/rollups/sepolia/add_input.lua b/prt/tests/rollups/sepolia/add_input.lua new file mode 100644 index 00000000..26db8cac --- /dev/null +++ b/prt/tests/rollups/sepolia/add_input.lua @@ -0,0 +1,10 @@ +require "setup_path" + +local env = require "sepolia.setup_env" +local conversion = require "utils.conversion" + +local big_input = conversion.bin_from_hex_n("0x6228290203658fd4987e40cbb257cabf258f9c288cdee767eaba6b234a73a2f9") + :rep((1 << 11) - 10) +assert(big_input:len() == (1 << 16) - 320) + +env.sender:tx_add_inputs { conversion.hex_from_bin_n(big_input) } diff --git a/prt/tests/rollups/sepolia/dispute.lua b/prt/tests/rollups/sepolia/dispute.lua new file mode 100644 index 00000000..c8c24170 --- /dev/null +++ b/prt/tests/rollups/sepolia/dispute.lua @@ -0,0 +1,74 @@ +require "setup_path" + +local PatchedCommitmentBuilder = require "runners.helpers.patched_commitment" +local CommitmentBuilder = require "computation.commitment" +local Hash = require "cryptography.hash" +local Machine = require "computation.machine" +local start_sybil = require "runners.sybil_runner" +local time = require "utils.time" + +local env = require "sepolia.setup_env" + +local sealed_epochs = env.reader:read_epochs_sealed() +local sealed_epoch = sealed_epochs[#sealed_epochs] + +-- get all inputs +local all_inputs = env.reader:read_inputs_added(sealed_epoch.epoch_number) + +-- slice inputs into `sealed_epoch` inputs +local inputs = {} +for i = sealed_epoch.input_lower_bound + 1, sealed_epoch.input_upper_bound do + table.insert(inputs, all_inputs[i].data) +end + +-- Get `sealed_epoch` machine path. + +-- Compute honest commitment +-- 44 is the initial log2_stride currently configured in the smart contracts. +local initial_state, commitment = Machine.root_rollup_commitment(env.template_machine, 44, inputs) +assert(sealed_epoch.initial_machine_state_hash, initial_state) + +local patches = { + -- add input 2 + ustep + { hash = Hash.zero, meta_cycle = (1 << 44) }, + { hash = Hash.zero, meta_cycle = (1 << 27) }, + { hash = Hash.zero, meta_cycle = (1) }, +} + +local honest_commitment_builder = CommitmentBuilder:new(env.template_machine, inputs, commitment) +local patched_commitment_builder = PatchedCommitmentBuilder:new(patches, honest_commitment_builder) +local player = start_sybil(patched_commitment_builder, env.template_machine, sealed_epoch.tournament, inputs, 1, {pk = env.pk, endpoint = env.gateway}) + +local function player_react(player_coroutine) + local success, log = coroutine.resume(player_coroutine) + assert(success, string.format("player fail to resume with error: %s", log)) + return coroutine.status(player_coroutine), log +end + +local function drive_player_until(player_coroutine, condition_f) + local ret + while true do + ret = { condition_f(player_react(player_coroutine)) } + + if ret[1] then + return table.unpack(ret) + end + + time.sleep(15) + end +end + +local function drive_player(player_coroutine) + return drive_player_until(player_coroutine, function(status, log) + if log.has_lost then + return "lost" + elseif status == "dead" then + return "dead" + end + end) +end + +-- Run player till completion +print("Run Sybil") +assert(drive_player(player) == "lost") +print "Sybil has lost" diff --git a/prt/tests/rollups/sepolia/setup_env.lua b/prt/tests/rollups/sepolia/setup_env.lua new file mode 100644 index 00000000..b5600316 --- /dev/null +++ b/prt/tests/rollups/sepolia/setup_env.lua @@ -0,0 +1,36 @@ +local Reader = require "dave.reader" +local Sender = require "dave.sender" + +-- addresses +local APP_ADDRESS = assert(os.getenv("APP_ADDRESS")) +local CONSENSUS_ADDRESS = assert(os.getenv("CONSENSUS_ADDRESS")) +local INPUT_BOX_ADDRESS = assert(os.getenv("INPUT_BOX_ADDRESS")) +local PK_ADDRESS = assert(os.getenv("PK_ADDRESS")) + +print(string.format([[ +APP_ADDRESS = %s +CONSENSUS_ADDRESS = %s +INPUT_BOX_ADDRESS = %s +PK_ADDRESS = %s +]], APP_ADDRESS, CONSENSUS_ADDRESS, INPUT_BOX_ADDRESS, PK_ADDRESS)) + +local GATEWAY = assert(os.getenv("GATEWAY")) +local PK = assert(os.getenv("PK")) + +local reader = Reader:new(INPUT_BOX_ADDRESS, CONSENSUS_ADDRESS, GATEWAY, 8467207) +local sender = Sender:new(INPUT_BOX_ADDRESS, APP_ADDRESS, PK, GATEWAY) + +print(string.format("BALANCE = %s", reader:balance(PK_ADDRESS))) + +return { + app_address = APP_ADDRESS, + consensus_address = CONSENSUS_ADDRESS, + input_box_address = INPUT_BOX_ADDRESS, + signer_address = PK_ADDRESS, + gateway = GATEWAY, + pk = PK, + template_machine = "sepolia/machine-image", + + reader = reader, + sender = sender, +}