From 1da6a0e268e334a4a17d02f5da00e5ae3350a0c7 Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Fri, 16 May 2025 11:40:11 +0100 Subject: [PATCH 1/6] RISC-V: fix ethereum specific bugs in the revm benchmark 1) Addresses zero cannot be used 2) The contract originator (`minter`) was changed to fix a bug introducing a new one. Previously the contract address corresponding to minter address 1, nonce 0 had been hardcoded. Now we calculate the correct address in general # Conflicts: # src/riscv/revm/bench/src/generate.rs --- src/riscv/revm/Cargo.lock | 1 + src/riscv/revm/bench/src/generate.rs | 25 ++++++++------- src/riscv/revm/kernel/Cargo.toml | 1 + src/riscv/revm/kernel/src/main.rs | 46 ++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/riscv/revm/Cargo.lock b/src/riscv/revm/Cargo.lock index 6904ffbc7f..da22c55807 100644 --- a/src/riscv/revm/Cargo.lock +++ b/src/riscv/revm/Cargo.lock @@ -2762,6 +2762,7 @@ dependencies = [ name = "revm-kernel" version = "0.0.0" dependencies = [ + "alloy-sol-types", "bincode", "revm", "tezos-smart-rollup", diff --git a/src/riscv/revm/bench/src/generate.rs b/src/riscv/revm/bench/src/generate.rs index 83e9aa32c9..7a8e9563b5 100644 --- a/src/riscv/revm/bench/src/generate.rs +++ b/src/riscv/revm/bench/src/generate.rs @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: MIT -use std::error::Error; use std::path::Path; use std::vec; +use std::{error::Error, u64}; use alloy_sol_types::{SolCall, sol}; use jstz_crypto::{keypair_from_passphrase, public_key::PublicKey, secret_key::SecretKey}; @@ -23,7 +23,7 @@ use utils::crypto::SignedOperation; const GLD_CONTRACT_BYTECODE: &str = include_str!("../../contract.bin"); // This is fragile since it is hardcoded for the GLDToken contract of originator with address 0x1 -const CONTRACT_ADDRESS: Address = address!("Bd770416a3345F91E4B34576cb804a576fa48EB1"); +//const contract: Address = address!("Bd770416a3345F91E4B34576cb804a576fa48EB1"); // Big enough that it doesn't clash with the 0..num accounts const MINTER: Address = address!("9999999999999999999999999999999999999999"); const EXTERNAL_FRAME_SIZE: usize = 21; @@ -80,6 +80,7 @@ impl Account { data: abi_call, caller: self.address, nonce: self.nonce, + //gas_limit: u64::MAX, ..TxEnv::default() }; self.nonce += 1; @@ -117,7 +118,9 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu }; let len = accounts_for_transfers(transfers); - let mut accounts: Vec = (0..len) + // Account address cannot be 0. This is reserved in ethereum and transactions revert if we try + // to use it + let mut accounts: Vec = (1..=len) .map(|i| { let (sk, pk) = keypair_from_passphrase(&i.to_string())?; Ok(Account { @@ -129,6 +132,9 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu }) .collect::>()?; + // contract addresses in ethereum are a function of the originator and the nonce + // we need to calculate it beforehand in order to send transaction to that address + let contract: Address = minter.address.create(minter.nonce); // deploy the contract let bytecode: Vec = hex::decode(GLD_CONTRACT_BYTECODE)?; messages.push(minter.operation_to_message(rollup_addr, TxKind::Create, bytecode.into())?); @@ -139,7 +145,7 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu // Solidity source code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC20/IERC20.sol // and my extension of it `GLDToken.sol` sol! { - function mint(address to, uint256 amount) public; + function mint(address to, uint256 amount) public onlyOwner; } let amount = len + 1; @@ -149,11 +155,8 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu amount: U256::from(amount), } .abi_encode(); - let msg = minter.operation_to_message( - rollup_addr, - TxKind::Call(CONTRACT_ADDRESS), - mint_call.into(), - )?; + let msg = + minter.operation_to_message(rollup_addr, TxKind::Call(contract), mint_call.into())?; messages.push(msg); } @@ -178,7 +181,7 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu .abi_encode(); let msg = accounts[from % len].operation_to_message( rollup_addr, - TxKind::Call(CONTRACT_ADDRESS), + TxKind::Call(contract), call_data.into(), )?; messages.push(msg); @@ -198,7 +201,7 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu .abi_encode(); let msg = minter.operation_to_message( rollup_addr, - TxKind::Call(CONTRACT_ADDRESS), + TxKind::Call(contract), balance_call.into(), )?; messages.push(msg); diff --git a/src/riscv/revm/kernel/Cargo.toml b/src/riscv/revm/kernel/Cargo.toml index 047c19551a..004050ec21 100644 --- a/src/riscv/revm/kernel/Cargo.toml +++ b/src/riscv/revm/kernel/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] +alloy-sol-types.workspace = true bincode.workspace = true tezos-smart-rollup.workspace = true tezos_crypto_rs.workspace = true diff --git a/src/riscv/revm/kernel/src/main.rs b/src/riscv/revm/kernel/src/main.rs index 7641cb267e..5e436cab2c 100644 --- a/src/riscv/revm/kernel/src/main.rs +++ b/src/riscv/revm/kernel/src/main.rs @@ -2,9 +2,11 @@ // // SPDX-License-Identifier: MIT +use alloy_sol_types::{SolCall, sol}; use revm::{ ExecuteCommitEvm, MainBuilder, MainContext, context::{Context, TxEnv}, + context_interface::result::{ExecutionResult, Output}, database::CacheDB, database_interface::EmptyDB, }; @@ -80,12 +82,52 @@ pub fn entry(host: &mut impl Runtime) { .with_db(CacheDB::::default()) .build_mainnet(); + sol! { + function balanceOf(address account) external view returns (uint256); + } let rollup_address_hash = host.reveal_metadata().address(); loop { match get_inbox_message(host, &rollup_address_hash) { Ok(Some(tx)) => match evm.transact_commit(tx) { - Ok(_res) => { - debug_msg!(host, "Successful transaction\n"); + Ok(res) => { + debug_msg!( + host, + "Successful transaction? {:?}\n", //logs:\n{:?}\n", + res.is_success() //res.logs() + ); + //debug_msg!(host, "returned value: {:#?}\n", res); + match res { + ExecutionResult::Success { + output: output, //Output::Call(value), + .. + } => match output { + Output::Call(value) => { + //debug_msg!( + // host, + // "Raw return value len: {}\n", + // value.len(), + //); + let ret = balanceOfCall::abi_decode_returns(&value); + match ret { + Ok(foo) => { + debug_msg!(host, "returned value: {:?}\n", foo) + } + Err(err) => { + debug_msg!(host, "no value returned: {err:?}\n") + } + } + } + Output::Create(_, _) => { + debug_msg!(host, "Contract creation\n") + } + }, + ExecutionResult::Revert { .. } => { + debug_msg!(host, "Execution reverted\n") + } + ExecutionResult::Halt { reason, .. } => { + debug_msg!(host, "Halt: reason - {:?}\n", reason) + } + }; } Err(err) => { debug_msg!(host, "Unsuccessful transaction: \n{:?}\n", err); -- GitLab From f7ee6332a36801ad87053ba2d26b8844d2db1c3d Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Thu, 15 May 2025 12:29:20 +0100 Subject: [PATCH 2/6] RISC-V: setup for results.rs: sol calls and relevant data in debug_msg # Conflicts: # src/riscv/.gitignore # src/riscv/revm/kernel/src/main.rs # src/riscv/revm/utils/src/lib.rs --- src/riscv/revm/Cargo.lock | 1 + src/riscv/revm/bench/src/generate.rs | 18 +------- src/riscv/revm/kernel/src/main.rs | 65 ++++++++++------------------ src/riscv/revm/utils/Cargo.toml | 1 + src/riscv/revm/utils/src/abi.rs | 10 +++++ src/riscv/revm/utils/src/lib.rs | 1 + 6 files changed, 38 insertions(+), 58 deletions(-) create mode 100644 src/riscv/revm/utils/src/abi.rs diff --git a/src/riscv/revm/Cargo.lock b/src/riscv/revm/Cargo.lock index da22c55807..31d75ce50e 100644 --- a/src/riscv/revm/Cargo.lock +++ b/src/riscv/revm/Cargo.lock @@ -3783,6 +3783,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" name = "utils" version = "0.1.0" dependencies = [ + "alloy-sol-types", "bincode", "jstz_crypto", "revm", diff --git a/src/riscv/revm/bench/src/generate.rs b/src/riscv/revm/bench/src/generate.rs index 7a8e9563b5..9d90bcd2be 100644 --- a/src/riscv/revm/bench/src/generate.rs +++ b/src/riscv/revm/bench/src/generate.rs @@ -6,7 +6,7 @@ use std::path::Path; use std::vec; use std::{error::Error, u64}; -use alloy_sol_types::{SolCall, sol}; +use alloy_sol_types::SolCall; // for using `abi_encode` use jstz_crypto::{keypair_from_passphrase, public_key::PublicKey, secret_key::SecretKey}; use revm::{ context::TxEnv, @@ -18,6 +18,7 @@ use tezos_smart_rollup::types::SmartRollupAddress; use tezos_smart_rollup::utils::inbox::file::InboxFile; use tezos_smart_rollup::utils::inbox::file::Message; +use utils::abi::{balanceOfCall, mintCall, transferCall}; use utils::crypto::Operation; use utils::crypto::SignedOperation; @@ -141,13 +142,6 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu // mint coins for everyone - // Generate abi for the function we want to call from the contract - // Solidity source code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC20/IERC20.sol - // and my extension of it `GLDToken.sol` - sol! { - function mint(address to, uint256 amount) public onlyOwner; - } - let amount = len + 1; for acc in &accounts { let mint_call = mintCall { @@ -162,10 +156,6 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu // Generate transfers - sol! { - function transfer(address to, uint256 value) external returns (bool); - } - let expected_len = messages.len() + transfers; 'outer: for token_id in 0..len { @@ -190,10 +180,6 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu // Query everyone's balance - sol! { - function balanceOf(address account) external view returns (uint256); - } - for acc in &accounts { let balance_call = balanceOfCall { account: acc.address, diff --git a/src/riscv/revm/kernel/src/main.rs b/src/riscv/revm/kernel/src/main.rs index 5e436cab2c..184c78abb6 100644 --- a/src/riscv/revm/kernel/src/main.rs +++ b/src/riscv/revm/kernel/src/main.rs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -use alloy_sol_types::{SolCall, sol}; use revm::{ ExecuteCommitEvm, MainBuilder, MainContext, context::{Context, TxEnv}, @@ -82,52 +81,12 @@ pub fn entry(host: &mut impl Runtime) { .with_db(CacheDB::::default()) .build_mainnet(); - sol! { - function balanceOf(address account) external view returns (uint256); - } let rollup_address_hash = host.reveal_metadata().address(); loop { match get_inbox_message(host, &rollup_address_hash) { Ok(Some(tx)) => match evm.transact_commit(tx) { Ok(res) => { - debug_msg!( - host, - "Successful transaction? {:?}\n", //logs:\n{:?}\n", - res.is_success() //res.logs() - ); - //debug_msg!(host, "returned value: {:#?}\n", res); - match res { - ExecutionResult::Success { - output: output, //Output::Call(value), - .. - } => match output { - Output::Call(value) => { - //debug_msg!( - // host, - // "Raw return value len: {}\n", - // value.len(), - //); - let ret = balanceOfCall::abi_decode_returns(&value); - match ret { - Ok(foo) => { - debug_msg!(host, "returned value: {:?}\n", foo) - } - Err(err) => { - debug_msg!(host, "no value returned: {err:?}\n") - } - } - } - Output::Create(_, _) => { - debug_msg!(host, "Contract creation\n") - } - }, - ExecutionResult::Revert { .. } => { - debug_msg!(host, "Execution reverted\n") - } - ExecutionResult::Halt { reason, .. } => { - debug_msg!(host, "Halt: reason - {:?}\n", reason) - } - }; + handle_res(host, res); } Err(err) => { debug_msg!(host, "Unsuccessful transaction: \n{:?}\n", err); @@ -140,3 +99,25 @@ pub fn entry(host: &mut impl Runtime) { } } } + +fn handle_res(host: &mut impl Runtime, res: ExecutionResult) -> Result<()> { + debug_msg!( + host, + "Successful transaction? {:?}\n", //logs:\n{:?}\n", + res.is_success() //res.logs() + ); + //debug_msg!(host, "returned value: {:#?}\n", res); + match res { + ExecutionResult::Success { + output, //Output::Call(value), + .. + } => debug_msg!(host, "{:?}\n", output), + ExecutionResult::Revert { .. } => { + debug_msg!(host, "Execution reverted\n") + } + ExecutionResult::Halt { reason, .. } => { + debug_msg!(host, "Halt: reason - {:?}\n", reason) + } + }; + Ok(()) +} diff --git a/src/riscv/revm/utils/Cargo.toml b/src/riscv/revm/utils/Cargo.toml index 9e1c1439d5..5eccf11c04 100644 --- a/src/riscv/revm/utils/Cargo.toml +++ b/src/riscv/revm/utils/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" jstz_crypto.workspace = true tezos_crypto_rs.workspace = true revm.workspace = true +alloy-sol-types.workspace = true serde.workspace = true bincode.workspace = true diff --git a/src/riscv/revm/utils/src/abi.rs b/src/riscv/revm/utils/src/abi.rs new file mode 100644 index 0000000000..9e697da4b7 --- /dev/null +++ b/src/riscv/revm/utils/src/abi.rs @@ -0,0 +1,10 @@ +use alloy_sol_types::sol; + +// Generate abi for the function we want to call from the contract +// Solidity source code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC20/IERC20.sol +// and its extension in `src/riscv/revm/GLDToken.sol` +sol! { + function mint(address to, uint256 amount) public onlyOwner; + function transfer(address to, uint256 value) external returns (bool); + function balanceOf(address account) external view returns (uint256); +} diff --git a/src/riscv/revm/utils/src/lib.rs b/src/riscv/revm/utils/src/lib.rs index 2a4bfe63ed..99596e8275 100644 --- a/src/riscv/revm/utils/src/lib.rs +++ b/src/riscv/revm/utils/src/lib.rs @@ -2,4 +2,5 @@ // // SPDX-License-Identifier: MIT +pub mod abi; pub mod crypto; -- GitLab From 39f7af70315661a6aed0935c79ba8c7fce4bc46e Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Thu, 15 May 2025 12:32:56 +0100 Subject: [PATCH 3/6] RISC-V adapt jstz results reporting and bench script # Conflicts: # src/riscv/revm/kernel/src/main.rs --- src/riscv/revm/Cargo.lock | 2 + src/riscv/revm/Cargo.toml | 1 + src/riscv/revm/Makefile | 6 +- src/riscv/revm/bench/Cargo.toml | 2 + src/riscv/revm/bench/src/main.rs | 2 +- src/riscv/revm/bench/src/results.rs | 290 +++++++++++++++++++++++++++- src/riscv/revm/kernel/src/main.rs | 45 ++++- src/riscv/scripts/revm-bench.sh | 193 ++++++++++++++++++ 8 files changed, 523 insertions(+), 18 deletions(-) create mode 100755 src/riscv/scripts/revm-bench.sh diff --git a/src/riscv/revm/Cargo.lock b/src/riscv/revm/Cargo.lock index 31d75ce50e..f5e3356d18 100644 --- a/src/riscv/revm/Cargo.lock +++ b/src/riscv/revm/Cargo.lock @@ -1781,6 +1781,8 @@ dependencies = [ "clap", "jstz_crypto", "revm", + "serde", + "serde_json", "tezos-smart-rollup", "tezos_data_encoding", "utils", diff --git a/src/riscv/revm/Cargo.toml b/src/riscv/revm/Cargo.toml index 3a2273c019..68f2dbb879 100644 --- a/src/riscv/revm/Cargo.toml +++ b/src/riscv/revm/Cargo.toml @@ -17,6 +17,7 @@ tezos_data_encoding = { path = "../../../sdk/rust/encoding" } [workspace.dependencies] alloy-sol-types = "1.1.0" +serde_json = "1.0.115" [workspace.dependencies.bincode] version = "2.0.0-rc.3" diff --git a/src/riscv/revm/Makefile b/src/riscv/revm/Makefile index 9f2ce9f00c..79f0a65c59 100644 --- a/src/riscv/revm/Makefile +++ b/src/riscv/revm/Makefile @@ -40,9 +40,9 @@ build-kernel-native: .PHONY: test test: @cargo test --no-default-features $(NATIVE_OPT) - @../scripts/jstz-bench.sh -t 1 - @../scripts/jstz-bench.sh -t 1 -s - @../scripts/jstz-bench.sh -t 1 -sn + @../scripts/revm-bench.sh -t 1 + @../scripts/revm-bench.sh -t 1 -s + @../scripts/revm-bench.sh -t 1 -sn .PHONY: run run: diff --git a/src/riscv/revm/bench/Cargo.toml b/src/riscv/revm/bench/Cargo.toml index e118d8dded..faf491c092 100644 --- a/src/riscv/revm/bench/Cargo.toml +++ b/src/riscv/revm/bench/Cargo.toml @@ -11,4 +11,6 @@ tezos_data_encoding.workspace = true alloy-sol-types.workspace = true revm.workspace = true clap.workspace = true +serde_json.workspace = true +serde.workspace = true utils = { path = "../utils" } diff --git a/src/riscv/revm/bench/src/main.rs b/src/riscv/revm/bench/src/main.rs index b89388c43a..60ceb9151d 100644 --- a/src/riscv/revm/bench/src/main.rs +++ b/src/riscv/revm/bench/src/main.rs @@ -14,7 +14,7 @@ use results::handle_results; mod generate; mod results; -const DEFAULT_ROLLUP_ADDRESS: &str = "sr1UNDWPUYVeomgG15wn5jSw689EJ4RNnVQa"; +const DEFAULT_ROLLUP_ADDRESS: &str = "sr163Lv22CdE8QagCwf48PWDTquk6isQwv57"; type Result = std::result::Result>; diff --git a/src/riscv/revm/bench/src/results.rs b/src/riscv/revm/bench/src/results.rs index 6ffba0eeae..b06c429a10 100644 --- a/src/riscv/revm/bench/src/results.rs +++ b/src/riscv/revm/bench/src/results.rs @@ -2,13 +2,293 @@ // // SPDX-License-Identifier: MIT -use crate::Result; +use std::collections::HashSet; +use std::fmt; +use std::fs::read_to_string; use std::path::Path; +use std::time::Duration; + +use serde::Deserialize; +use tezos_smart_rollup::utils::inbox::file::InboxFile; +use tezos_smart_rollup::utils::inbox::file::Message; + +use crate::Result; + +// Three sets of messages: +// 1. Deployment +// 2. Minting & Transfers +// 3. Balance Checks +// ... but all contained in one level +const EXPECTED_LEVELS: usize = 1; pub fn handle_results( - _inbox: Box, - _all_logs: Vec>, - _expected_transfers: usize, + inbox: Box, + all_logs: Vec>, + expected_transfers: usize, ) -> Result<()> { - unimplemented!("results reporting for TPS benchmark") + let inbox = InboxFile::load(&inbox)?; + + let all_metrics = all_logs + .iter() + .map(|logs| { + let logs = read_to_string(logs)? + .lines() + .map(serde_json::from_str) + .filter_map(|l| l.map(LogLine::classify).transpose()) + .collect::, _>>()?; + + let levels = logs_to_levels(logs)?; + + if inbox.0.len() != levels.len() || levels.len() != EXPECTED_LEVELS { + return Err(format!( + "InboxFile contains {} levels, found {} in logs, expected {EXPECTED_LEVELS}", + inbox.0.len(), + levels.len() + ) + .into()); + } + let expected_accounts = accounts_for_transfers(expected_transfers); + + let [results]: [_; EXPECTED_LEVELS] = levels.try_into().unwrap(); + + check_deploy(&results)?; + let metrics = check_transfer_metrics(&results, expected_accounts, expected_transfers)?; + check_balances( + &results, + &inbox.0[0][1 + expected_accounts + expected_transfers..], + expected_transfers, + )?; + + Ok(metrics) + }) + .collect::>>()?; + + if all_metrics.len() > 1 { + let len = all_metrics.len(); + + for (num, metrics) in all_metrics.iter().enumerate() { + println!("Run {} / {len} => {metrics}", num + 1); + } + + let agg_metrics = TransferMetrics::aggregate(&all_metrics); + println!("\nAggregate => {agg_metrics}"); + } else if let Some(metrics) = all_metrics.first() { + println!("{metrics}"); + } + + Ok(()) +} + +fn check_deploy(level: &Level) -> Result<()> { + if level.deployments.len() != 1 { + return Err("Expected ERC-20 contract deployment".into()); + } + + if level.executions.is_empty() { + return Err("Expected ERC-20 token minting".into()); + } + + Ok(()) +} + +#[derive(Clone, Debug, Default)] +struct TransferMetrics { + transfers: usize, + duration: Duration, + tps: f64, +} + +impl TransferMetrics { + fn aggregate(metrics: &[TransferMetrics]) -> TransferMetrics { + let summed = metrics.iter().fold(Self::default(), |acc, m| Self { + transfers: acc.transfers + m.transfers, + duration: acc.duration + m.duration, + tps: acc.tps + m.tps, + }); + + Self { + tps: summed.tps / metrics.len() as f64, + ..summed + } + } +} + +impl fmt::Display for TransferMetrics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} ERC-20 transfers took {:?} @ {:.3} TPS", + self.transfers, self.duration, self.tps + ) + } +} + +fn check_transfer_metrics( + level: &Level, + accounts: usize, + transfers: usize, +) -> Result { + if transfers + 2 * accounts != level.executions.len() { + return Err(format!( + "Expected {transfers} transfers, got total transactions {}, noting that there are {accounts} accounts", + level.executions.len() + ) + .into()); + } + + // The first execution is the minting call. We collect the time elapsed at the _end_ of the + // minting, all the way up to the _end_ of the last execution (transfer). + let duration = + level.executions[accounts + transfers].elapsed - level.executions[accounts].elapsed; + let tps = (transfers as f64) / duration.as_secs_f64(); + + Ok(TransferMetrics { + transfers, + duration, + tps, + }) +} + +// The generated transfers (for a number of accounts N), has a target final state: +// Every account should hold one of every token. +// +// This requires (N - 1) * num_tokens transfers. +// +// Therefore, if an account has `0` of a token, there's a transfer missing below this maximum +// number. +fn check_balances(level: &Level, messages: &[Message], num_transfers: usize) -> Result<()> { + // let re = Regex::new(r#"^.*"([\w0-9]+) has ([0-9]+) of token ([0-9]+)".*$"#).unwrap(); + // + // let mut accounts = HashSet::new(); + // let mut tokens = HashSet::new(); + // let mut skipped_receives = 0; + // + // for m in level.balance_checks.iter().map(|l| &l.message) { + // for (_, [address, balance, token]) in re.captures_iter(m).map(|c| c.extract()) { + // accounts.insert(address); + // tokens.insert(token.parse::()?); + // + // let balance = balance.parse::()?; + // + // if balance == 0 { + // skipped_receives += 1; + // } + // } + // } + // + // // Checks + // if accounts.len() != tokens.len() { + // return Err(format!( + // "Expected {} accounts to equal {} tokens", + // accounts.len(), + // tokens.len() + // ) + // .into()); + // } + // + // if accounts.len() != messages.len() { + // return Err(format!( + // "Have {} accounts but only {} messages for checking balances", + // accounts.len(), + // messages.len() + // ) + // .into()); + // } + // + // let expected_transfers = (accounts.len() - 1) * tokens.len() - skipped_receives; + // + // if expected_transfers != num_transfers { + // return Err(format!( + // "Found {} transfer messages, vs {} transfers completed", + // num_transfers, expected_transfers + // ) + // .into()); + // } + // + Ok(()) +} + +fn logs_to_levels(logs: Vec) -> Result> { + let mut levels = Vec::new(); + + let mut level = Level::default(); + + let mut balance_checks = Vec::new(); + for line in logs.into_iter() { + match line { + LogType::StartOfLevel(_) => { + if level != Level::default() { + return Err( + format!("StartOfLevel message not at start of level {level:?}").into(), + ); + } + } + LogType::EndOfLevel(_) => { + levels.push(level); + level = Default::default(); + } + LogType::Deploy(l) => level.deployments.push(l), + LogType::Success(l) if balance_checks.is_empty() => level.executions.push(l), + LogType::Success(_) => level.balance_checks.append(&mut balance_checks), + LogType::SmartFunctionLog(l) => balance_checks.push(l), + } + } + + if level != Level::default() { + return Err("Final level missing EndOfLevel message {last:?}".into()); + } + + Ok(levels) +} + +#[derive(Deserialize, Debug, PartialEq)] +struct LogLine { + elapsed: Duration, + message: String, +} + +impl LogLine { + fn classify(self) -> Option { + let m = &self.message; + + if m.starts_with(SOL) { + Some(LogType::StartOfLevel(self)) + } else if m.starts_with(EOL) { + Some(LogType::EndOfLevel(self)) + } else if m.starts_with(DEPLOY) { + Some(LogType::Deploy(self)) + } else if m.starts_with(SUCCESS) { + Some(LogType::Success(self)) + } else if m.starts_with(LOG) { + Some(LogType::SmartFunctionLog(self)) + } else { + None + } + } +} + +#[derive(Debug)] +enum LogType { + StartOfLevel(#[allow(unused)] LogLine), + Deploy(LogLine), + Success(LogLine), + EndOfLevel(#[allow(unused)] LogLine), + SmartFunctionLog(LogLine), +} + +const SOL: &str = "Message: Internal(StartOfLevel)"; +const DEPLOY: &str = "[📜] Smart function deployed"; +const SUCCESS: &str = "🚀 Smart function executed successfully"; +const EOL: &str = "Internal message: end of level"; +const LOG: &str = "[JSTZ:SMART_FUNCTION:LOG]"; + +#[derive(Default, Debug, PartialEq)] +struct Level { + deployments: Vec, + executions: Vec, + balance_checks: Vec, +} + +fn accounts_for_transfers(transfers: usize) -> usize { + f64::sqrt(transfers as f64).ceil() as usize + 1 } diff --git a/src/riscv/revm/kernel/src/main.rs b/src/riscv/revm/kernel/src/main.rs index 184c78abb6..9d34e5f77a 100644 --- a/src/riscv/revm/kernel/src/main.rs +++ b/src/riscv/revm/kernel/src/main.rs @@ -13,13 +13,19 @@ use std::error::Error; use tezos_crypto_rs::hash::SmartRollupHash; use tezos_smart_rollup::entrypoint; use tezos_smart_rollup::inbox::ExternalMessageFrame; -use tezos_smart_rollup::inbox::InboxMessage; +use tezos_smart_rollup::inbox::{InboxMessage, InternalInboxMessage}; use tezos_smart_rollup::michelson::MichelsonUnit; use tezos_smart_rollup::prelude::Runtime; use tezos_smart_rollup::prelude::*; use utils::crypto::Operation; use utils::crypto::SignedOperation; +const SOL: &str = "Message: Internal(StartOfLevel)"; +const DEPLOY: &str = "[📜] Smart function deployed"; +const SUCCESS: &str = "🚀 Smart function executed successfully"; +const EOL: &str = "Internal message: end of level"; +const LOG: &str = "[JSTZ:SMART_FUNCTION:LOG]"; + type Result = std::result::Result>; /// # Returns @@ -62,8 +68,28 @@ fn get_inbox_message( Ok(Some(tx)) } } - InboxMessage::Internal(_) => { + InboxMessage::Internal(msg) => { // Ignore any other message + match msg { + InternalInboxMessage::StartOfLevel => { + debug_msg!(host, "{}\n", SOL) + } + InternalInboxMessage::InfoPerLevel(info) => { + debug_msg!( + host, + "Internal message: level info \ + (block predecessor: {}, predecessor_timestamp: {}\n", + info.predecessor, + info.predecessor_timestamp + ); + } + InternalInboxMessage::EndOfLevel => { + debug_msg!(host, "{}\n", EOL) + } + InternalInboxMessage::Transfer(_) => { + debug_msg!(host, "Internal message: transfer\n") + } + } Err("ignore internal message\n".into()) } } @@ -101,17 +127,18 @@ pub fn entry(host: &mut impl Runtime) { } fn handle_res(host: &mut impl Runtime, res: ExecutionResult) -> Result<()> { - debug_msg!( - host, - "Successful transaction? {:?}\n", //logs:\n{:?}\n", - res.is_success() //res.logs() - ); - //debug_msg!(host, "returned value: {:#?}\n", res); match res { ExecutionResult::Success { output, //Output::Call(value), .. - } => debug_msg!(host, "{:?}\n", output), + } => match output { + Output::Create(_, _) => { + debug_msg!(host, "{}\n", DEPLOY); + } + Output::Call(bytes) => { + debug_msg!(host, "{}, {:?}\n", SUCCESS, bytes); + } + }, ExecutionResult::Revert { .. } => { debug_msg!(host, "Execution reverted\n") } diff --git a/src/riscv/scripts/revm-bench.sh b/src/riscv/scripts/revm-bench.sh new file mode 100755 index 0000000000..8b38cb0d0a --- /dev/null +++ b/src/riscv/scripts/revm-bench.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2024-2025 TriliTech +# +# SPDX-License-Identifier: MIT + +# Build and run the revm TPS benchmark with the specified number of transfers + +set -e + +USAGE="Usage: -t [ -s: static inbox ] [ -p: profile with samply ] [ -n: run natively ] [ -i : number of runs ] [ -j: enable inline jit ] [ -m : enable metrics ]" +DEFAULT_ROLLUP_ADDRESS="sr163Lv22CdE8QagCwf48PWDTquk6isQwv57" + +ITERATIONS="1" +TX="" +STATIC_INBOX="" +SANDBOX_BIN="riscv-sandbox" +SANDBOX_ENABLE_FEATURES=() +PROFILING_WRAPPER="" +SAMPLY_OUT="riscv-sandbox-profile.json" +METRICS="" +METRICS_ARGS=() +NATIVE="" +REVM_SANDBOX_PARAMS=("--input" "revm/target/riscv64gc-unknown-linux-musl/release/revm-kernel") + +CURR=$(pwd) +RISCV_DIR=$(dirname "$0")/.. +cd "$RISCV_DIR" + +while getopts "i:t:m:spnj" OPTION; do + case "$OPTION" in + i) + ITERATIONS="$OPTARG" + ;; + t) + TX="$OPTARG" + ;; + s) + STATIC_INBOX="y" + ;; + p) + SANDBOX_BIN="riscv-sandbox.prof" + PROFILING_WRAPPER="samply record -s -o $SAMPLY_OUT" + ;; + n) + NATIVE=$(make --silent -C revm print-native-target | grep -wv make) + ;; + j) + SANDBOX_ENABLE_FEATURES+=("inline-jit") + ;; + m) + SANDBOX_ENABLE_FEATURES+=("metrics") + METRICS="y" + + case "$OPTARG" in + all) ;; + jit-unsupported) + METRICS_ARGS+=("--exclude-supported-instructions") + ;; + *) + echo "$USAGE" + exit 1 + ;; + esac + ;; + *) + echo "$USAGE" + exit 1 + ;; + esac +done + +if [ -z "$TX" ]; then + echo "$USAGE" + exit 1 +fi + +if [ -n "$NATIVE" ] && [ -z "$STATIC_INBOX" ]; then + echo "Native compilation without static inbox unsupported" + echo "$USAGE" + exit 1 +fi + +echo "[INFO]: building sandbox" +make "SANDBOX_ENABLE_FEATURES=${SANDBOX_ENABLE_FEATURES[*]}" "$SANDBOX_BIN" &> /dev/null +echo "[INFO]: building bench tool" +make -C revm inbox-bench &> /dev/null + +DATA_DIR=${DATA_DIR:=$(mktemp -d)} +echo $DATA_DIR + +echo "[INFO]: generating $TX transfers" +INBOX_FILE="${DATA_DIR}/inbox.json" +RUN_INBOX="$INBOX_FILE" +./revm/inbox-bench generate --inbox-file "$INBOX_FILE" --transfers "$TX" + +log_file_args=() + +BLOCK_METRICS_FILE="${DATA_DIR}/block-metrics.out" +if [ -n "$METRICS" ]; then + METRICS_ARGS+=("--block-metrics-file" "${BLOCK_METRICS_FILE}") +fi + +########## +# RISC-V # +########## +build_revm_riscv() { + if [ "$STATIC_INBOX" = "y" ]; then + INBOX_FILE="$INBOX_FILE" make -C revm build-kernel-static &> /dev/null + RUN_INBOX="$DATA_DIR"/empty.json + echo "[]" > "$RUN_INBOX" + else + make -C revm build-kernel &> /dev/null + fi +} + +run_revm_riscv() { + LOG="$DATA_DIR/log.$1.log" + $PROFILING_WRAPPER "./$SANDBOX_BIN" run \ + "${REVM_SANDBOX_PARAMS[@]}" \ + --inbox-file "$RUN_INBOX" \ + --address "$DEFAULT_ROLLUP_ADDRESS" \ + "${METRICS_ARGS[@]}" \ + --timings > "$LOG" + log_file_args+=("--log-file=$LOG") +} + +########## +# Native # +########## +build_revm_native() { + INBOX_FILE=$INBOX_FILE make -C revm build-kernel-native &> /dev/null +} + +run_revm_native() { + LOG="$DATA_DIR/log.$1.log" + $PROFILING_WRAPPER ./revm/target/"$NATIVE"/release/revm-kernel \ + --timings > "$LOG" 2> /dev/null + log_file_args+=("--log-file=$LOG") +} + +######### +# Build # +######### +echo "[INFO]: building revm" + +if [ -z "$NATIVE" ]; then + build_revm_riscv + echo "[INFO]: running $TX transfers (riscv) " +else + build_revm_native + echo "[INFO]: running $TX transfers ($NATIVE) " +fi + +################# +# Run & Collect # +################# +run_revm() { + echo -ne "\r\033[2K[INFO]: Run $1 / $ITERATIONS" + if [ -z "$NATIVE" ]; then + run_revm_riscv "$1" + else + run_revm_native "$1" + fi + + if [ -n "$PROFILING_WRAPPER" ]; then + echo -e "\n[INFO]: Samply data saved to: $SAMPLY_OUT" + fi +} + +collect() { + echo -e "\033[1m" + ./revm/inbox-bench results --inbox-file "$INBOX_FILE" "${log_file_args[@]}" --expected-transfers "$TX" + echo -e "\033[0m" +} + +for i in $(seq "$ITERATIONS"); do + run_revm "$i" +done + +collect + +# This loads the profile of the last run +if [ -n "$PROFILING_WRAPPER" ]; then + echo "[INFO]: collecting results" + samply load $SAMPLY_OUT +fi + +if [ -n "${METRICS}" ]; then + echo "Block metrics at ${BLOCK_METRICS_FILE}" +fi + +cd "$CURR" -- GitLab From d9bc7cc680b6481c5cf7641a8a90f34aff17953f Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Thu, 15 May 2025 12:41:03 +0100 Subject: [PATCH 4/6] RISC-V: structured data transfer between kernel and results.rs # Conflicts: # src/riscv/revm/kernel/src/main.rs # src/riscv/revm/utils/src/lib.rs --- src/riscv/revm/Cargo.lock | 1 + src/riscv/revm/bench/src/generate.rs | 2 +- src/riscv/revm/bench/src/results.rs | 129 ++++++++------- src/riscv/revm/kernel/Cargo.toml | 1 + src/riscv/revm/kernel/src/main.rs | 156 +++++++++--------- .../utils/src/{abi.rs => data_interface.rs} | 12 ++ src/riscv/revm/utils/src/lib.rs | 2 +- 7 files changed, 166 insertions(+), 137 deletions(-) rename src/riscv/revm/utils/src/{abi.rs => data_interface.rs} (62%) diff --git a/src/riscv/revm/Cargo.lock b/src/riscv/revm/Cargo.lock index f5e3356d18..b0b216ea62 100644 --- a/src/riscv/revm/Cargo.lock +++ b/src/riscv/revm/Cargo.lock @@ -2767,6 +2767,7 @@ dependencies = [ "alloy-sol-types", "bincode", "revm", + "serde_json", "tezos-smart-rollup", "tezos_crypto_rs", "utils", diff --git a/src/riscv/revm/bench/src/generate.rs b/src/riscv/revm/bench/src/generate.rs index 9d90bcd2be..b85724c5c9 100644 --- a/src/riscv/revm/bench/src/generate.rs +++ b/src/riscv/revm/bench/src/generate.rs @@ -18,9 +18,9 @@ use tezos_smart_rollup::types::SmartRollupAddress; use tezos_smart_rollup::utils::inbox::file::InboxFile; use tezos_smart_rollup::utils::inbox::file::Message; -use utils::abi::{balanceOfCall, mintCall, transferCall}; use utils::crypto::Operation; use utils::crypto::SignedOperation; +use utils::data_interface::{balanceOfCall, mintCall, transferCall}; const GLD_CONTRACT_BYTECODE: &str = include_str!("../../contract.bin"); // This is fragile since it is hardcoded for the GLDToken contract of originator with address 0x1 diff --git a/src/riscv/revm/bench/src/results.rs b/src/riscv/revm/bench/src/results.rs index b06c429a10..4eff36be9d 100644 --- a/src/riscv/revm/bench/src/results.rs +++ b/src/riscv/revm/bench/src/results.rs @@ -8,11 +8,14 @@ use std::fs::read_to_string; use std::path::Path; use std::time::Duration; +use alloy_sol_types::SolCall; +use revm::primitives::U256; use serde::Deserialize; use tezos_smart_rollup::utils::inbox::file::InboxFile; use tezos_smart_rollup::utils::inbox::file::Message; use crate::Result; +use utils::data_interface::{LogType, balanceOfCall, transferCall}; // Three sets of messages: // 1. Deployment @@ -31,13 +34,13 @@ pub fn handle_results( let all_metrics = all_logs .iter() .map(|logs| { - let logs = read_to_string(logs)? + let logs: Vec = read_to_string(logs)? .lines() .map(serde_json::from_str) .filter_map(|l| l.map(LogLine::classify).transpose()) .collect::, _>>()?; - let levels = logs_to_levels(logs)?; + let levels = logs_to_levels(logs, expected_transfers)?; if inbox.0.len() != levels.len() || levels.len() != EXPECTED_LEVELS { return Err(format!( @@ -52,7 +55,7 @@ pub fn handle_results( let [results]: [_; EXPECTED_LEVELS] = levels.try_into().unwrap(); check_deploy(&results)?; - let metrics = check_transfer_metrics(&results, expected_accounts, expected_transfers)?; + let metrics = check_transfer_metrics(&results, expected_transfers)?; check_balances( &results, &inbox.0[0][1 + expected_accounts + expected_transfers..], @@ -84,10 +87,14 @@ fn check_deploy(level: &Level) -> Result<()> { return Err("Expected ERC-20 contract deployment".into()); } - if level.executions.is_empty() { + if level.mints.is_empty() { return Err("Expected ERC-20 token minting".into()); } + if level.transfers.is_empty() { + return Err("Expected ERC-20 token transfers".into()); + } + Ok(()) } @@ -123,23 +130,18 @@ impl fmt::Display for TransferMetrics { } } -fn check_transfer_metrics( - level: &Level, - accounts: usize, - transfers: usize, -) -> Result { - if transfers + 2 * accounts != level.executions.len() { +fn check_transfer_metrics(level: &Level, transfers: usize) -> Result { + if transfers != level.transfers.len() { return Err(format!( - "Expected {transfers} transfers, got total transactions {}, noting that there are {accounts} accounts", - level.executions.len() + "Expected {transfers} transfers, got {}.", + level.transfers.len() ) .into()); } - // The first execution is the minting call. We collect the time elapsed at the _end_ of the + // The first `account` executions are the minting calls. We collect the time elapsed at the _end_ of the // minting, all the way up to the _end_ of the last execution (transfer). - let duration = - level.executions[accounts + transfers].elapsed - level.executions[accounts].elapsed; + let duration = level.transfers.last().unwrap().elapsed - level.mints.last().unwrap().elapsed; let tps = (transfers as f64) / duration.as_secs_f64(); Ok(TransferMetrics { @@ -208,30 +210,52 @@ fn check_balances(level: &Level, messages: &[Message], num_transfers: usize) -> Ok(()) } -fn logs_to_levels(logs: Vec) -> Result> { +fn logs_to_levels(logs: Vec, transfers: usize) -> Result> { + let accounts = accounts_for_transfers(transfers); let mut levels = Vec::new(); let mut level = Level::default(); - let mut balance_checks = Vec::new(); + let mut i = 0; for line in logs.into_iter() { - match line { - LogType::StartOfLevel(_) => { + match line.log_type { + LogType::StartOfLevel => { if level != Level::default() { return Err( format!("StartOfLevel message not at start of level {level:?}").into(), ); } } - LogType::EndOfLevel(_) => { + LogType::EndOfLevel => { levels.push(level); level = Default::default(); } - LogType::Deploy(l) => level.deployments.push(l), - LogType::Success(l) if balance_checks.is_empty() => level.executions.push(l), - LogType::Success(_) => level.balance_checks.append(&mut balance_checks), - LogType::SmartFunctionLog(l) => balance_checks.push(l), + LogType::Deploy => level.deployments.push(line), + LogType::Execute(ref bytes) => { + if i < accounts { + level.mints.push(line); + } else if i < accounts + transfers { + let success = transferCall::abi_decode_returns(bytes)?; + if !success { + return Err("Revm transfer transaction didn't succeed".into()); + } + level.transfers.push(line); + } else if i < 2 * accounts + transfers { + let balance: U256 = balanceOfCall::abi_decode_returns(bytes)?; + level.balance_checks.push((line, balance.try_into()?)); + } else { + return Err( + "More transactions (either of mints transfers or balance checks) than expected +Expected {i+1} got more than that" + .into(), + ); + } + i += 1; + } + LogType::Error(e) => return Err(e.into()), + LogType::Info(_) => (), } + println!("Levels: {levels:?}\n") } if level != Level::default() { @@ -241,52 +265,41 @@ fn logs_to_levels(logs: Vec) -> Result> { Ok(levels) } +// There are 3 layers of parsing going on here and 3 data structures representing the target of +// each stage +// 1) Parse from PVM's json format to `LogLine` +// 2) Parse `message` within a `LogLine` into `LogType`, which was constructed by the kernel +// 3) Abi decode the `LogType::Execute`'s `bytes` which was the smart contract's result value as +// returned by revm #[derive(Deserialize, Debug, PartialEq)] struct LogLine { elapsed: Duration, message: String, } -impl LogLine { - fn classify(self) -> Option { - let m = &self.message; - - if m.starts_with(SOL) { - Some(LogType::StartOfLevel(self)) - } else if m.starts_with(EOL) { - Some(LogType::EndOfLevel(self)) - } else if m.starts_with(DEPLOY) { - Some(LogType::Deploy(self)) - } else if m.starts_with(SUCCESS) { - Some(LogType::Success(self)) - } else if m.starts_with(LOG) { - Some(LogType::SmartFunctionLog(self)) - } else { - None - } - } +#[derive(Deserialize, Debug, PartialEq)] +struct ParsedLogLine { + elapsed: Duration, + log_type: LogType, } -#[derive(Debug)] -enum LogType { - StartOfLevel(#[allow(unused)] LogLine), - Deploy(LogLine), - Success(LogLine), - EndOfLevel(#[allow(unused)] LogLine), - SmartFunctionLog(LogLine), +impl LogLine { + fn classify(self) -> Option { + // If it can't be parsed it's some other message like level info which is dropped + let log_type: LogType = serde_json::from_str(&self.message).ok()?; + Some(ParsedLogLine { + elapsed: self.elapsed, + log_type, + }) + } } -const SOL: &str = "Message: Internal(StartOfLevel)"; -const DEPLOY: &str = "[📜] Smart function deployed"; -const SUCCESS: &str = "🚀 Smart function executed successfully"; -const EOL: &str = "Internal message: end of level"; -const LOG: &str = "[JSTZ:SMART_FUNCTION:LOG]"; - #[derive(Default, Debug, PartialEq)] struct Level { - deployments: Vec, - executions: Vec, - balance_checks: Vec, + deployments: Vec, + mints: Vec, + transfers: Vec, + balance_checks: Vec<(ParsedLogLine, usize)>, // contains a balance as well } fn accounts_for_transfers(transfers: usize) -> usize { diff --git a/src/riscv/revm/kernel/Cargo.toml b/src/riscv/revm/kernel/Cargo.toml index 004050ec21..835fabbcce 100644 --- a/src/riscv/revm/kernel/Cargo.toml +++ b/src/riscv/revm/kernel/Cargo.toml @@ -9,6 +9,7 @@ bincode.workspace = true tezos-smart-rollup.workspace = true tezos_crypto_rs.workspace = true revm.workspace = true +serde_json.workspace = true utils = { path = "../utils" } [features] diff --git a/src/riscv/revm/kernel/src/main.rs b/src/riscv/revm/kernel/src/main.rs index 9d34e5f77a..b880d90bbc 100644 --- a/src/riscv/revm/kernel/src/main.rs +++ b/src/riscv/revm/kernel/src/main.rs @@ -9,7 +9,6 @@ use revm::{ database::CacheDB, database_interface::EmptyDB, }; -use std::error::Error; use tezos_crypto_rs::hash::SmartRollupHash; use tezos_smart_rollup::entrypoint; use tezos_smart_rollup::inbox::ExternalMessageFrame; @@ -19,14 +18,25 @@ use tezos_smart_rollup::prelude::Runtime; use tezos_smart_rollup::prelude::*; use utils::crypto::Operation; use utils::crypto::SignedOperation; +use utils::data_interface::LogType; -const SOL: &str = "Message: Internal(StartOfLevel)"; -const DEPLOY: &str = "[📜] Smart function deployed"; -const SUCCESS: &str = "🚀 Smart function executed successfully"; -const EOL: &str = "Internal message: end of level"; -const LOG: &str = "[JSTZ:SMART_FUNCTION:LOG]"; +enum InboxResult { + InboxEmpty, + Log(LogType), + TxEnv(TxEnv), +} +use InboxResult::*; -type Result = std::result::Result>; +fn to_inbox_result(res: Result, f: F) -> InboxResult +where + F: FnOnce(T) -> InboxResult, + R: std::fmt::Debug, +{ + match res { + Err(e) => Log(LogType::Error(format!("{:?}", e))), + Ok(t) => f(t), + } +} /// # Returns /// Err(_) . Failed to retrieve a valid external failed message. In either case we recover and @@ -39,62 +49,49 @@ type Result = std::result::Result>; fn get_inbox_message( host: &mut impl Runtime, rollup_address_hash: &SmartRollupHash, -) -> Result> { - match host.read_input()? { - None => Ok(None), - Some(input) => { - let (_, message) = InboxMessage::::parse(input.as_ref()) - .map_err(|e| (format!("{:?}", e)))?; - match message { - InboxMessage::External(bytes) => { - let ExternalMessageFrame::Targetted { address, contents } = - // err in Result<_,err> returned by parse does not implement std::error:Error so we - // map it to a str - ExternalMessageFrame::parse(bytes) - .map_err(|e| format!("{:?}", e))?; - if rollup_address_hash != address.hash() { - Err(format!( - "Skipping message: External message targets another rollup. Expected: {}. Found: {}\n", - rollup_address_hash, - address.hash() - ).into()) - } else { - let (signed_op, _): (SignedOperation, usize) = - bincode::serde::decode_from_slice( - contents, - bincode::config::standard(), - )?; - let Operation(tx) = signed_op.verify()?; - Ok(Some(tx)) - } - } - InboxMessage::Internal(msg) => { - // Ignore any other message - match msg { - InternalInboxMessage::StartOfLevel => { - debug_msg!(host, "{}\n", SOL) - } - InternalInboxMessage::InfoPerLevel(info) => { - debug_msg!( - host, - "Internal message: level info \ - (block predecessor: {}, predecessor_timestamp: {}\n", - info.predecessor, - info.predecessor_timestamp - ); - } - InternalInboxMessage::EndOfLevel => { - debug_msg!(host, "{}\n", EOL) - } - InternalInboxMessage::Transfer(_) => { - debug_msg!(host, "Internal message: transfer\n") +) -> InboxResult { + to_inbox_result(host.read_input(), |maybe_inp| match maybe_inp { + None => InboxEmpty, + Some(input) => to_inbox_result( + InboxMessage::::parse(input.as_ref()), + |(_, message)| match message { + InboxMessage::External(bytes) => to_inbox_result( + ExternalMessageFrame::parse(bytes), + |ExternalMessageFrame::Targetted { address, contents }| { + if rollup_address_hash != address.hash() { + Log(LogType::Info(format!( + "Skipping message: External message targets another rollup. Expected: {}. Found: {}", + rollup_address_hash, + address.hash() + ))) + } else { + to_inbox_result( + bincode::serde::decode_from_slice( + contents, + bincode::config::standard(), + ), + |(signed_op, _): (SignedOperation, usize)| { + to_inbox_result(signed_op.verify(), |Operation(tx)| TxEnv(tx)) + }, + ) } + }, + ), + InboxMessage::Internal(msg) => match msg { + InternalInboxMessage::StartOfLevel => Log(LogType::StartOfLevel), + InternalInboxMessage::InfoPerLevel(info) => Log(LogType::Info(format!( + "Internal message: level info \ + (block predecessor: {}, predecessor_timestamp: {}", + info.predecessor, info.predecessor_timestamp + ))), + InternalInboxMessage::EndOfLevel => Log(LogType::EndOfLevel), + InternalInboxMessage::Transfer(_) => { + Log(LogType::Info("Internal message: transfer".into())) } - Err("ignore internal message\n".into()) - } - } - } - } + }, + }, + ), + }) } #[entrypoint::main] @@ -110,41 +107,46 @@ pub fn entry(host: &mut impl Runtime) { let rollup_address_hash = host.reveal_metadata().address(); loop { match get_inbox_message(host, &rollup_address_hash) { - Ok(Some(tx)) => match evm.transact_commit(tx) { + TxEnv(tx) => match evm.transact_commit(tx) { Ok(res) => { - handle_res(host, res); + let log = handle_res(res); + if let Ok(ser) = serde_json::to_string(&log) { + debug_msg!(host, "{}\n", ser); + } } Err(err) => { - debug_msg!(host, "Unsuccessful transaction: \n{:?}\n", err); + let err = LogType::Error(format!("Unsuccessful transaction: \n{:?}", err)); + if let Ok(ser) = serde_json::to_string(&err) { + debug_msg!(host, "{}\n", ser); + } } }, - Ok(None) => { + InboxEmpty => { break; } - Err(e) => debug_msg!(host, "{}", e), + Log(log) => { + if let Ok(ser) = serde_json::to_string(&log) { + debug_msg!(host, "{}\n", ser); + } + } } } } -fn handle_res(host: &mut impl Runtime, res: ExecutionResult) -> Result<()> { +fn handle_res(res: ExecutionResult) -> LogType { match res { ExecutionResult::Success { output, //Output::Call(value), .. } => match output { - Output::Create(_, _) => { - debug_msg!(host, "{}\n", DEPLOY); - } - Output::Call(bytes) => { - debug_msg!(host, "{}, {:?}\n", SUCCESS, bytes); - } + Output::Create(_, _) => LogType::Deploy, + Output::Call(bytes) => LogType::Execute(bytes), }, ExecutionResult::Revert { .. } => { - debug_msg!(host, "Execution reverted\n") + LogType::Error("Smart contract execution reverted".into()) } ExecutionResult::Halt { reason, .. } => { - debug_msg!(host, "Halt: reason - {:?}\n", reason) + LogType::Error(format!("Halt: reason - {:?}", reason)) } - }; - Ok(()) + } } diff --git a/src/riscv/revm/utils/src/abi.rs b/src/riscv/revm/utils/src/data_interface.rs similarity index 62% rename from src/riscv/revm/utils/src/abi.rs rename to src/riscv/revm/utils/src/data_interface.rs index 9e697da4b7..96ad599018 100644 --- a/src/riscv/revm/utils/src/abi.rs +++ b/src/riscv/revm/utils/src/data_interface.rs @@ -1,4 +1,6 @@ use alloy_sol_types::sol; +use revm::primitives::Bytes; +use serde::{Deserialize, Serialize}; // Generate abi for the function we want to call from the contract // Solidity source code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC20/IERC20.sol @@ -8,3 +10,13 @@ sol! { function transfer(address to, uint256 value) external returns (bool); function balanceOf(address account) external view returns (uint256); } + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub enum LogType { + StartOfLevel, + Deploy, + Execute(Bytes), + EndOfLevel, + Error(String), + Info(String), // logged info that `results.rs` doesn't care about +} diff --git a/src/riscv/revm/utils/src/lib.rs b/src/riscv/revm/utils/src/lib.rs index 99596e8275..e38dd80289 100644 --- a/src/riscv/revm/utils/src/lib.rs +++ b/src/riscv/revm/utils/src/lib.rs @@ -2,5 +2,5 @@ // // SPDX-License-Identifier: MIT -pub mod abi; pub mod crypto; +pub mod data_interface; -- GitLab From 7ab06e5be65b61545ae61328aa96ebbdfd8467bd Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Thu, 15 May 2025 12:45:33 +0100 Subject: [PATCH 5/6] RISC-V: check queried balance for every account is correct # Conflicts: # src/riscv/Makefile # src/riscv/revm/kernel/src/main.rs --- src/riscv/Makefile | 4 +- src/riscv/revm/bench/src/results.rs | 94 ++++++++++++----------------- 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/riscv/Makefile b/src/riscv/Makefile index fad1bec332..57da7abc12 100644 --- a/src/riscv/Makefile +++ b/src/riscv/Makefile @@ -36,7 +36,7 @@ build-revm: assets/revm-kernel.elf assets/revm-inbox.json riscv-sandbox .PHONY: run-revm run-revm: build-revm - @cargo run --release -- run -m supervisor --input assets/revm-kernel.elf --inbox-file assets/revm-inbox.json + @cargo run --release -- run -m supervisor --address "sr163Lv22CdE8QagCwf48PWDTquk6isQwv57" --input assets/revm-kernel.elf --inbox-file assets/revm-inbox.json .PHONY: riscv-sandbox riscv-sandbox:: @@ -68,7 +68,7 @@ assets/revm-kernel.elf assets/revm-kernel.elf.checksum:: @sha256sum $@ > $@.checksum assets/revm-inbox.json:: - @cargo run --manifest-path=revm/Cargo.toml --bin inbox-bench --release -- generate --transfers 1 + @cargo run --manifest-path=revm/Cargo.toml --bin inbox-bench --release -- generate --address "sr163Lv22CdE8QagCwf48PWDTquk6isQwv57" --transfers 16 @mv inbox.json $@ assets/jstz assets/jstz.checksum:: diff --git a/src/riscv/revm/bench/src/results.rs b/src/riscv/revm/bench/src/results.rs index 4eff36be9d..8320bb40ff 100644 --- a/src/riscv/revm/bench/src/results.rs +++ b/src/riscv/revm/bench/src/results.rs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -use std::collections::HashSet; use std::fmt; use std::fs::read_to_string; use std::path::Path; @@ -17,11 +16,8 @@ use tezos_smart_rollup::utils::inbox::file::Message; use crate::Result; use utils::data_interface::{LogType, balanceOfCall, transferCall}; -// Three sets of messages: -// 1. Deployment -// 2. Minting & Transfers -// 3. Balance Checks -// ... but all contained in one level +// Deployment, Minting, Transfers, Balance Checks +// all contained in one level const EXPECTED_LEVELS: usize = 1; pub fn handle_results( @@ -87,6 +83,8 @@ fn check_deploy(level: &Level) -> Result<()> { return Err("Expected ERC-20 contract deployment".into()); } + // We just check these two since there would be a panic otherwise when taking + // last of these vectors in `check_transfer_metrics` if level.mints.is_empty() { return Err("Expected ERC-20 token minting".into()); } @@ -158,56 +156,39 @@ fn check_transfer_metrics(level: &Level, transfers: usize) -> Result Result<()> { - // let re = Regex::new(r#"^.*"([\w0-9]+) has ([0-9]+) of token ([0-9]+)".*$"#).unwrap(); - // - // let mut accounts = HashSet::new(); - // let mut tokens = HashSet::new(); - // let mut skipped_receives = 0; - // - // for m in level.balance_checks.iter().map(|l| &l.message) { - // for (_, [address, balance, token]) in re.captures_iter(m).map(|c| c.extract()) { - // accounts.insert(address); - // tokens.insert(token.parse::()?); - // - // let balance = balance.parse::()?; - // - // if balance == 0 { - // skipped_receives += 1; - // } - // } - // } - // - // // Checks - // if accounts.len() != tokens.len() { - // return Err(format!( - // "Expected {} accounts to equal {} tokens", - // accounts.len(), - // tokens.len() - // ) - // .into()); - // } - // - // if accounts.len() != messages.len() { - // return Err(format!( - // "Have {} accounts but only {} messages for checking balances", - // accounts.len(), - // messages.len() - // ) - // .into()); - // } - // - // let expected_transfers = (accounts.len() - 1) * tokens.len() - skipped_receives; - // - // if expected_transfers != num_transfers { - // return Err(format!( - // "Found {} transfer messages, vs {} transfers completed", - // num_transfers, expected_transfers - // ) - // .into()); - // } - // - Ok(()) +fn check_balances(level: &Level, messages: &[Message], transfers: usize) -> Result<()> { + // rerun transfer generation and check if the balances match + + // The same transfer generation strategy from `generate.rs` is adapted here + // to calculate what the expected balances would be if all the transactions were + // successful + let len = accounts_for_transfers(transfers); + let mut balances = vec![len + 1; len]; + let mut i = 0; + + 'outer: for token_id in 0..len { + for (from, amount) in (token_id..(token_id + len)).zip(1..len) { + if i == transfers { + break 'outer; + } + let value = len - amount; + balances[from % len] -= value; + balances[(from + 1) % len] += value; + i += 1; + } + } + + let observed_balances: Vec = level.balance_checks.iter().map(|x| x.1).collect(); + for i in 0..len { + if observed_balances[i] != balances[i] { + return Err(format!( + "Balances didn't match expected {:?} got {:?}", + observed_balances, balances + ) + .into()); + } + } + return Ok(()); } fn logs_to_levels(logs: Vec, transfers: usize) -> Result> { @@ -255,7 +236,6 @@ Expected {i+1} got more than that" LogType::Error(e) => return Err(e.into()), LogType::Info(_) => (), } - println!("Levels: {levels:?}\n") } if level != Level::default() { -- GitLab From 7321f5e09c441ccbcbd81b0201390828a8953fcb Mon Sep 17 00:00:00 2001 From: Edwin Fernando Date: Fri, 16 May 2025 12:41:19 +0100 Subject: [PATCH 6/6] RISC-V Some more checks and balances before reporting results --- src/riscv/revm/bench/src/generate.rs | 4 +- src/riscv/revm/bench/src/main.rs | 2 +- src/riscv/revm/bench/src/results.rs | 83 +++++++++++++++------- src/riscv/revm/utils/src/data_interface.rs | 3 + 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/riscv/revm/bench/src/generate.rs b/src/riscv/revm/bench/src/generate.rs index b85724c5c9..cdaade013b 100644 --- a/src/riscv/revm/bench/src/generate.rs +++ b/src/riscv/revm/bench/src/generate.rs @@ -23,8 +23,6 @@ use utils::crypto::SignedOperation; use utils::data_interface::{balanceOfCall, mintCall, transferCall}; const GLD_CONTRACT_BYTECODE: &str = include_str!("../../contract.bin"); -// This is fragile since it is hardcoded for the GLDToken contract of originator with address 0x1 -//const contract: Address = address!("Bd770416a3345F91E4B34576cb804a576fa48EB1"); // Big enough that it doesn't clash with the 0..num accounts const MINTER: Address = address!("9999999999999999999999999999999999999999"); const EXTERNAL_FRAME_SIZE: usize = 21; @@ -198,6 +196,6 @@ fn create_operations(rollup_addr: &SmartRollupAddress, transfers: usize) -> Resu /// The generation strategy supports up to `num_accounts ^ 2` transfers, /// find the smallest number of accounts which will allow for this. -fn accounts_for_transfers(transfers: usize) -> usize { +pub fn accounts_for_transfers(transfers: usize) -> usize { f64::sqrt(transfers as f64).ceil() as usize + 1 } diff --git a/src/riscv/revm/bench/src/main.rs b/src/riscv/revm/bench/src/main.rs index 60ceb9151d..6a3b076f23 100644 --- a/src/riscv/revm/bench/src/main.rs +++ b/src/riscv/revm/bench/src/main.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-FileCopyrightText: 2024 TriliTech // // SPDX-License-Identifier: MIT diff --git a/src/riscv/revm/bench/src/results.rs b/src/riscv/revm/bench/src/results.rs index 8320bb40ff..4e2179e429 100644 --- a/src/riscv/revm/bench/src/results.rs +++ b/src/riscv/revm/bench/src/results.rs @@ -14,12 +14,16 @@ use tezos_smart_rollup::utils::inbox::file::InboxFile; use tezos_smart_rollup::utils::inbox::file::Message; use crate::Result; +use crate::generate::accounts_for_transfers; use utils::data_interface::{LogType, balanceOfCall, transferCall}; // Deployment, Minting, Transfers, Balance Checks // all contained in one level const EXPECTED_LEVELS: usize = 1; +/// The `results` command of the cli is implemented by this function. It makes sure the `all_logs` +/// `expected_transfers` and `inbox` are all consistent with each other. +/// If so reports the TPS pub fn handle_results( inbox: Box, all_logs: Vec>, @@ -50,13 +54,9 @@ pub fn handle_results( let [results]: [_; EXPECTED_LEVELS] = levels.try_into().unwrap(); - check_deploy(&results)?; + check_counts(&results, &inbox.0[0], expected_accounts, expected_transfers)?; let metrics = check_transfer_metrics(&results, expected_transfers)?; - check_balances( - &results, - &inbox.0[0][1 + expected_accounts + expected_transfers..], - expected_transfers, - )?; + check_balances(&results, expected_transfers)?; Ok(metrics) }) @@ -78,19 +78,53 @@ pub fn handle_results( Ok(()) } -fn check_deploy(level: &Level) -> Result<()> { +fn check_counts( + level: &Level, + messages: &Vec, + accounts: usize, + transfers: usize, +) -> Result<()> { + // We allow for more messages. Say there were some messages for another rollup + // Note: 1 for deployment, `account` many for both minting and balance_checks + // and `transfers` many for transfers + if messages.len() < 1 + 2 * accounts + transfers { + return Err(format!( + "Expected atleast {} inbox messages. Found {}", + 1 + 2 * accounts + transfers, + messages.len() + ) + .into()); + } + if level.deployments.len() != 1 { return Err("Expected ERC-20 contract deployment".into()); } - // We just check these two since there would be a panic otherwise when taking - // last of these vectors in `check_transfer_metrics` - if level.mints.is_empty() { - return Err("Expected ERC-20 token minting".into()); + if level.mints.len() != accounts { + return Err(format!( + "Expected {} minting operations. Found {}", + accounts, + level.mints.len() + ) + .into()); } - if level.transfers.is_empty() { - return Err("Expected ERC-20 token transfers".into()); + if level.transfers.len() != transfers { + return Err(format!( + "Expected {} transfer operations. Found {}", + transfers, + level.transfers.len() + ) + .into()); + } + + if level.balance_checks.len() != accounts { + return Err(format!( + "Expected {} minting operations. Found {}", + accounts, + level.balance_checks.len() + ) + .into()); } Ok(()) @@ -156,7 +190,7 @@ fn check_transfer_metrics(level: &Level, transfers: usize) -> Result Result<()> { +fn check_balances(level: &Level, transfers: usize) -> Result<()> { // rerun transfer generation and check if the balances match // The same transfer generation strategy from `generate.rs` is adapted here @@ -179,16 +213,15 @@ fn check_balances(level: &Level, messages: &[Message], transfers: usize) -> Resu } let observed_balances: Vec = level.balance_checks.iter().map(|x| x.1).collect(); - for i in 0..len { - if observed_balances[i] != balances[i] { - return Err(format!( - "Balances didn't match expected {:?} got {:?}", - observed_balances, balances - ) - .into()); - } + if balances == observed_balances { + Ok(()) + } else { + Err(format!( + "Balances didn't match expected {:?} got {:?}", + observed_balances, balances + ) + .into()) } - return Ok(()); } fn logs_to_levels(logs: Vec, transfers: usize) -> Result> { @@ -281,7 +314,3 @@ struct Level { transfers: Vec, balance_checks: Vec<(ParsedLogLine, usize)>, // contains a balance as well } - -fn accounts_for_transfers(transfers: usize) -> usize { - f64::sqrt(transfers as f64).ceil() as usize + 1 -} diff --git a/src/riscv/revm/utils/src/data_interface.rs b/src/riscv/revm/utils/src/data_interface.rs index 96ad599018..85374d3bf3 100644 --- a/src/riscv/revm/utils/src/data_interface.rs +++ b/src/riscv/revm/utils/src/data_interface.rs @@ -11,6 +11,9 @@ sol! { function balanceOf(address account) external view returns (uint256); } +/// The data structure the kernel uses to send messages through the log file to be interpreted by +/// benchmark cli when reporting results. Specifically this datatype is serialized in +/// `kernel/src/main.rs` and deserialized in `bench/src/results.rs` #[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum LogType { StartOfLevel, -- GitLab