diff --git a/Cargo.lock b/Cargo.lock index aaba2eec5fe3..b351a18ece36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1255,6 +1255,39 @@ dependencies = [ "tokio", ] +[[package]] +name = "antelope-client" +version = "0.3.0" +source = "git+https://github.com/telosnetwork/antelope-rs?branch=master#fea2203b2edb5cfcedd5365031e5286b47dc5c66" +dependencies = [ + "antelope-client-macros", + "async-trait", + "base64 0.21.7", + "bs58", + "chrono", + "digest 0.10.7", + "ecdsa", + "flate2", + "hex", + "hmac 0.12.1", + "k256", + "log", + "once_cell", + "p256", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest 0.11.27", + "ripemd", + "serde", + "serde-big-array", + "serde_json", + "sha2 0.10.8", + "signature", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "antelope-client-macros" version = "0.2.0" @@ -5539,7 +5572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -8755,6 +8788,7 @@ dependencies = [ "revm-primitives", "secp256k1", "serde_json", + "sha2 0.10.8", ] [[package]] @@ -9391,7 +9425,7 @@ dependencies = [ "alloy-signer-local", "alloy-sol-types", "alloy-transport-http 0.4.2", - "antelope-client", + "antelope-client 0.3.0", "clap", "derive_more 1.0.0", "env_logger 0.11.5", @@ -9402,6 +9436,7 @@ dependencies = [ "reth-basic-payload-builder", "reth-beacon-consensus", "reth-chainspec", + "reth-db", "reth-e2e-test-utils", "reth-ethereum-engine-primitives", "reth-ethereum-payload-builder", @@ -9417,6 +9452,7 @@ dependencies = [ "reth-rpc", "reth-stages", "reth-telos-rpc", + "reth-telos-rpc-engine-api 1.0.8", "reth-tracing", "reth-transaction-pool", "serde", @@ -10556,6 +10592,7 @@ dependencies = [ "alloy-eips 0.4.2", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types 0.4.2", "alloy-serde 0.4.2", "arbitrary", "bytes", @@ -10573,7 +10610,7 @@ dependencies = [ "alloy-network 0.4.2", "alloy-primitives", "alloy-rpc-types 0.4.2", - "antelope-client", + "antelope-client 0.3.0", "async-trait", "derive_more 1.0.0", "jsonrpsee-types", @@ -10619,6 +10656,7 @@ dependencies = [ "revm", "revm-primitives", "serde", + "sha2 0.10.8", "tracing", ] @@ -10843,7 +10881,7 @@ dependencies = [ [[package]] name = "revm" version = "14.0.3" -source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#af62c2673affaa39636744b2383c9e980aed754c" +source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#1706d6bea3f2771e4603e827994038a8786d1256" dependencies = [ "auto_impl", "cfg-if", @@ -10875,7 +10913,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "10.0.3" -source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#af62c2673affaa39636744b2383c9e980aed754c" +source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#1706d6bea3f2771e4603e827994038a8786d1256" dependencies = [ "revm-primitives", "serde", @@ -10884,7 +10922,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "11.0.3" -source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#af62c2673affaa39636744b2383c9e980aed754c" +source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#1706d6bea3f2771e4603e827994038a8786d1256" dependencies = [ "aurora-engine-modexp", "blst", @@ -10903,7 +10941,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "10.0.0" -source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#af62c2673affaa39636744b2383c9e980aed754c" +source = "git+https://github.com/telosnetwork/telos-revm?branch=telos-main#1706d6bea3f2771e4603e827994038a8786d1256" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -12045,7 +12083,7 @@ dependencies = [ "alloy", "alloy-consensus 0.3.6", "alloy-rlp", - "antelope-client", + "antelope-client 0.2.1", "arrowbatch", "base64 0.22.1", "bytes", @@ -12077,10 +12115,12 @@ dependencies = [ name = "telos-reth" version = "1.0.8" dependencies = [ + "alloy-primitives", "clap", "reth", "reth-chainspec", "reth-cli-util", + "reth-db", "reth-node-builder", "reth-node-telos", "reth-provider", @@ -12097,7 +12137,7 @@ dependencies = [ "alloy-consensus 0.3.6", "alloy-eips 0.3.6", "alloy-rlp", - "antelope-client", + "antelope-client 0.2.1", "bytes", "clap", "dashmap 5.5.3", @@ -13278,7 +13318,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 710e36b25017..62025e2ea230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -601,7 +601,7 @@ reth-node-telos = { path = "crates/telos/node" } reth-telos-rpc = { path = "crates/telos/rpc" } reth-telos-primitives-traits = { path = "crates/telos/primitives-traits" } reth-telos-rpc-engine-api = { path = "crates/telos/rpc-engine-api" } -antelope-client = { git = "https://github.com/telosnetwork/antelope-rs", branch = "development" } +antelope-client = { git = "https://github.com/telosnetwork/antelope-rs", branch = "master" } [patch.crates-io] #alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"} diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 413a3e75411c..7a7989f8e05d 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -20,6 +20,7 @@ reth-revm.workspace = true reth-ethereum-consensus.workspace = true reth-prune-types.workspace = true reth-execution-types.workspace = true +sha2.workspace = true # Ethereum revm-primitives.workspace = true diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 188ca0e21f25..b14a93fc572f 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -38,6 +38,8 @@ use reth_telos_rpc_engine_api::compare::compare_state_diffs; use revm_primitives::{Address, Account, AccountInfo, AccountStatus, Bytecode, HashMap, KECCAK_EMPTY}; #[cfg(feature = "telos")] use alloy_primitives::B256; +#[cfg(feature = "telos")] +use sha2::{Sha256, Digest}; /// Provides executors to execute regular ethereum blocks #[derive(Debug, Clone)] @@ -192,6 +194,7 @@ where // execute transactions let mut cumulative_gas_used = 0; + #[cfg(not(feature = "telos"))] let mut receipts = Vec::with_capacity(block.body.transactions.len()); for (sender, transaction) in block.transactions_with_sender() { #[cfg(feature = "telos")] @@ -233,6 +236,7 @@ where // append gas used cumulative_gas_used += result.gas_used(); + #[cfg(not(feature = "telos"))] // Push transaction changeset and calculate header bloom filter for receipt. receipts.push( #[allow(clippy::needless_update)] // side-effect of optimism fields @@ -259,23 +263,32 @@ where new_addresses_using_create_iter.next(); } - #[cfg(feature = "telos")] { - // Perform state diff comparision - let revm_state_diffs = evm.db_mut().transition_state.clone().unwrap_or_default().transitions; - let block_num = block.block.header.number; - println!( - "Compare: block {block_num} {}", - compare_state_diffs( - &mut evm, - revm_state_diffs, - unwrapped_telos_extra_fields.statediffs_account.unwrap_or_default(), - unwrapped_telos_extra_fields.statediffs_accountstate.unwrap_or_default(), - unwrapped_telos_extra_fields.new_addresses_using_create.unwrap_or_default(), - unwrapped_telos_extra_fields.new_addresses_using_openwallet.unwrap_or_default() - ) - ); + // #[cfg(feature = "telos")] + { + // Perform state diff comparision + let revm_state_diffs = evm.db_mut().transition_state.clone().unwrap_or_default().transitions; + let block_num = block.block.header.number; + println!( + "Compare: block {block_num} {}", + compare_state_diffs( + &mut evm, + revm_state_diffs, + unwrapped_telos_extra_fields.statediffs_account.clone().unwrap_or_default(), + unwrapped_telos_extra_fields.statediffs_accountstate.clone().unwrap_or_default(), + unwrapped_telos_extra_fields.new_addresses_using_create.clone().unwrap_or_default(), + unwrapped_telos_extra_fields.new_addresses_using_openwallet.clone().unwrap_or_default(), + false + ) + ); } + #[cfg(feature = "telos")] + let receipts = if unwrapped_telos_extra_fields.receipts.is_some() { + unwrapped_telos_extra_fields.receipts.clone().unwrap() + } else { + vec![] + }; + let requests = if self.chain_spec.is_prague_active_at_timestamp(block.timestamp) { // Collect all EIP-6110 deposits let deposit_requests = @@ -288,7 +301,44 @@ where vec![] }; - Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used }) + // #[cfg(feature = "telos")] + // { + // let mut addr_to_accstate: HashMap> = HashMap::new(); + + // for sdiff_accstate in unwrapped_telos_extra_fields.clone().statediffs_accountstate.unwrap_or(vec![]) { + // if !addr_to_accstate.contains_key(&sdiff_accstate.address) { + // addr_to_accstate.insert(sdiff_accstate.address, HashMap::new()); + // } + // let mut acc_storage = addr_to_accstate.get_mut(&sdiff_accstate.address).unwrap(); + // acc_storage.insert(sdiff_accstate.key, EvmStorageSlot { original_value: Default::default(), present_value: sdiff_accstate.value, is_cold: false }); + // } + + // let mut state: HashMap = HashMap::new(); + + // for sdiff_acc in unwrapped_telos_extra_fields.clone().statediffs_account.unwrap_or(vec![]) { + // state.insert( + // sdiff_acc.address, + // Account { + // info: AccountInfo { + // balance: sdiff_acc.balance, + // nonce: sdiff_acc.nonce, + // code_hash: B256::from(Sha256::digest(sdiff_acc.code.as_ref()).as_ref()), + // code: Some(Bytecode::LegacyRaw(sdiff_acc.code)), + // }, + // storage: addr_to_accstate.get(&sdiff_acc.address).unwrap_or(&HashMap::new()).clone(), + // status: AccountStatus::Touched | AccountStatus::LoadedAsNotExisting, + // } + // ); + // } + + // evm.db_mut().commit(state); + // } + + Ok(EthExecuteOutput { + receipts, + requests, + gas_used: cumulative_gas_used + }) } } @@ -366,7 +416,12 @@ where let env = self.evm_env_for_block(&block.header, total_difficulty); let output = { let evm = self.executor.evm_config.evm_with_env(&mut self.state, env); - self.executor.execute_state_transitions(block, evm, #[cfg(feature = "telos")] telos_extra_fields) + self.executor.execute_state_transitions( + block, + evm, + #[cfg(feature = "telos")] + telos_extra_fields + ) }?; // 3. apply post execution changes diff --git a/crates/telos/bin/Cargo.toml b/crates/telos/bin/Cargo.toml index 462451dfb839..1669f380b311 100644 --- a/crates/telos/bin/Cargo.toml +++ b/crates/telos/bin/Cargo.toml @@ -16,6 +16,8 @@ reth-chainspec.workspace = true reth-provider.workspace = true reth-node-telos.workspace = true reth-telos-rpc.workspace = true +reth-db.workspace = true +alloy-primitives.workspace = true clap = { workspace = true, features = ["derive", "env"] } diff --git a/crates/telos/bin/src/main.rs b/crates/telos/bin/src/main.rs index 6e73d37e15bb..7f660ee0ee31 100644 --- a/crates/telos/bin/src/main.rs +++ b/crates/telos/bin/src/main.rs @@ -4,6 +4,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); use clap::Parser; +use tracing::{error, info, warn}; use reth::args::utils::EthereumChainSpecParser; use reth_node_builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}; use reth::cli::Cli; @@ -11,10 +12,16 @@ use reth_node_telos::{TelosArgs, TelosNode}; use reth_node_telos::node::TelosAddOns; use reth_provider::providers::BlockchainProvider2; use reth_telos_rpc::TelosClient; +use reth::primitives::BlockId; +use reth::rpc::types::BlockNumberOrTag; +use reth_provider::{DatabaseProviderFactory, StateProviderFactory}; +use reth_db::{PlainAccountState, PlainStorageState}; #[cfg(feature = "telos")] fn main() { + use reth_provider::BlockNumReader; + reth_cli_util::sigsegv_handler::install(); // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. @@ -45,6 +52,10 @@ fn main() { handle.node_exit_future.await }, false => { + let two_way_storage_compare = telos_args.two_way_storage_compare.clone(); + let telos_rpc = telos_args.telos_endpoint.clone(); + let block_delta = telos_args.block_delta.clone(); + let handle = builder .node(TelosNode::new(telos_args.clone())) .extend_rpc_modules(move |ctx| { @@ -59,6 +70,36 @@ fn main() { .launch() .await?; + match two_way_storage_compare { + true => { + if telos_rpc.is_none() { + warn!("Telos RPC Endpoint is not specified, skipping two-way storage compare"); + } else if block_delta.is_none() { + warn!("Block delta is not specified, skipping two-way storage compare"); + } else { + info!("Fetching account and accountstate from Telos native RPC (Can take a long time)..."); + + let (account_table, accountstate_table, block_number) = reth_node_telos::two_way_storage_compare::get_telos_tables(telos_rpc.unwrap().as_str(), block_delta.unwrap()).await; + + if block_number.as_u64().unwrap() <= handle.node.provider.best_block_number().unwrap() { + info!("Two-way comparing state (Reth vs. Telos) at height: {:?}", block_number); + + let state_at_specific_height = handle.node.provider.state_by_block_id(BlockId::Number(BlockNumberOrTag::Number(block_number.as_u64().unwrap()))).unwrap(); + let plain_account_state = handle.node.provider.database_provider_ro().unwrap().table::().unwrap(); + let plain_storage_state = handle.node.provider.database_provider_ro().unwrap().table::().unwrap(); + + let match_counter = reth_node_telos::two_way_storage_compare::two_side_state_compare(account_table, accountstate_table, state_at_specific_height, plain_account_state, plain_storage_state).await; + match_counter.print(); + + info!("Comparing done"); + } else { + error!("Nodeos is ahead of reth, failed to compare state"); + } + } + } + _ => {} + } + handle.node_exit_future.await } } diff --git a/crates/telos/node/Cargo.toml b/crates/telos/node/Cargo.toml index 3d7b8e01117d..a530dd01d848 100644 --- a/crates/telos/node/Cargo.toml +++ b/crates/telos/node/Cargo.toml @@ -34,6 +34,9 @@ reth-stages.workspace = true reth-telos-rpc.workspace = true reth-tracing.workspace = true reth-transaction-pool.workspace = true +reth-telos-rpc-engine-api.workspace = true +alloy-primitives.workspace = true +reth-db.workspace = true clap.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/crates/telos/node/src/args.rs b/crates/telos/node/src/args.rs index f65912512ca2..8c66a55b837d 100644 --- a/crates/telos/node/src/args.rs +++ b/crates/telos/node/src/args.rs @@ -45,6 +45,14 @@ pub struct TelosArgs { /// a batch of downloaded blocks. #[arg(long = "engine.max-execute-block-batch-size", requires = "experimental", default_value_t = DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE)] pub max_execute_block_batch_size: usize, + + /// Enable Two-way storage compare between reth and telos + #[arg(long = "telos.two_way_storage_compare", default_value = "false")] + pub two_way_storage_compare: bool, + + /// Block delta between native and EVM + #[arg(long = "telos.block_delta")] + pub block_delta: Option, } impl From for TelosClientArgs { diff --git a/crates/telos/node/src/lib.rs b/crates/telos/node/src/lib.rs index 962a444aa9c1..3f432da1b519 100644 --- a/crates/telos/node/src/lib.rs +++ b/crates/telos/node/src/lib.rs @@ -11,6 +11,7 @@ pub mod args; pub mod node; +pub mod two_way_storage_compare; pub use crate::args::TelosArgs; pub use crate::node::TelosNode; diff --git a/crates/telos/node/src/two_way_storage_compare.rs b/crates/telos/node/src/two_way_storage_compare.rs new file mode 100644 index 000000000000..e39c03d63f6d --- /dev/null +++ b/crates/telos/node/src/two_way_storage_compare.rs @@ -0,0 +1,367 @@ +//! Two-way storage compare between Reth and Telos + +use antelope::serializer::{Decoder, Encoder, Packer}; +use antelope::chain::name::Name; +use std::collections::HashMap; +use alloy_primitives::{Address, B256, U256}; +use antelope::api::client::{APIClient, DefaultProvider}; +use antelope::api::v1::structs::{GetTableRowsParams, TableIndexType}; +use antelope::{name, StructPacker}; +use antelope::chain::checksum::{Checksum160, Checksum256}; +use serde::{Deserialize, Serialize}; +use tracing::{error, info}; +use reth::primitives::{Account, BlockId}; +use reth_db::common::KeyValue; +use reth::providers::StateProviderBox; +use reth_db::{PlainAccountState, PlainStorageState}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] +struct AccountRow { + index: u64, + address: Checksum160, + account: Name, + nonce: u64, + code: Vec, + balance: Checksum256, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] +struct AccountStateRow { + index: u64, + key: Checksum256, + value: Checksum256, +} + +/// This struct holds matching statistics +#[derive(Debug, Clone, Default)] +pub struct MatchCounter { + total_telos_accounts: u64, + total_telos_storages: u64, + mismatched_telos_accounts: u64, + mismatched_telos_storages: u64, + total_reth_accounts: u64, + total_reth_storages: u64, + mismatched_reth_accounts: u64, + mismatched_reth_storages: u64, +} +impl MatchCounter { + /// Creates a new match counter for two-way storage compare + pub fn new() -> Self { + Self { + total_telos_accounts: 0, + total_telos_storages: 0, + mismatched_telos_accounts: 0, + mismatched_telos_storages: 0, + total_reth_accounts: 0, + total_reth_storages: 0, + mismatched_reth_accounts: 0, + mismatched_reth_storages: 0, + } + } + + /// Prints the match counter + pub fn print(&self) { + info!("Comparing results:"); + info!("Total telos accounts: {}", self.total_telos_accounts); + info!("Total telos storages: {}", self.total_telos_storages); + info!("Mismatched telos accounts: {}", self.mismatched_telos_accounts); + info!("Mismatched telos storages: {}", self.mismatched_telos_storages); + info!("Total reth accounts: {}", self.total_reth_accounts); + info!("Total reth storages: {}", self.total_reth_storages); + info!("Mismatched reth accounts: {}", self.mismatched_reth_accounts); + info!("Mismatched reth storages: {}", self.mismatched_reth_storages); + info!("Matching result: {}",self.matches()); + } + + fn add_telos_total_account(&mut self) { + self.total_telos_accounts += 1; + } + + fn add_telos_total_storage(&mut self) { + self.total_telos_storages += 1; + } + + fn add_telos_mismatched_account(&mut self) { + self.mismatched_telos_accounts += 1; + } + + fn add_telos_mismatched_storage(&mut self) { + self.mismatched_telos_storages += 1; + } + + fn add_reth_total_account(&mut self) { + self.total_reth_accounts += 1; + } + + fn add_reth_total_storage(&mut self) { + self.total_reth_storages += 1; + } + + fn add_reth_mismatched_account(&mut self) { + self.mismatched_reth_accounts += 1; + } + + fn add_reth_mismatched_storage(&mut self) { + self.mismatched_reth_storages += 1; + } + + /// Check whether both sides matches + pub fn matches(&self) -> bool { + self.mismatched_telos_accounts == 0 + && self.mismatched_telos_storages == 0 + && self.mismatched_reth_accounts == 0 + && self.mismatched_reth_storages == 0 + } +} + +/// This function compares reth and telos state against each other at specific height +pub async fn two_side_state_compare( + account_table: HashMap, + accountstate_table: HashMap<(Address, B256), U256>, + state_at_specific_height: StateProviderBox, + plain_account_state: Vec>, + plain_storage_state: Vec>, +) -> MatchCounter { + + let mut match_counter = MatchCounter::new(); + + for (address, telos_account) in &account_table { + let account_at_specific_height = state_at_specific_height.basic_account(*address); + match account_at_specific_height { + Ok(reth_account) => { + match reth_account { + Some(reth_account) => { + if reth_account.balance != telos_account.balance || reth_account.nonce != telos_account.nonce { + match_counter.add_telos_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account); + error!("Reth side: {:?}", reth_account); + } + }, + None => { + if telos_account.balance != U256::ZERO || telos_account.nonce != 0 { + match_counter.add_telos_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account); + error!("Reth side: None"); + } + }, + } + }, + Err(_) => { + match_counter.add_telos_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account); + error!("Reth side: None"); + }, + } + match_counter.add_telos_total_account(); + } + + for ((address, key), telos_value) in &accountstate_table { + let storage_at_specific_height = state_at_specific_height.storage(*address, *key); + match storage_at_specific_height { + Ok(storage) => { + match storage { + Some(reth_value) => { + if reth_value != *telos_value { + match_counter.add_telos_mismatched_storage(); + error!("Difference in accountstate: {:?}, key: {:?}", address, key); + error!("Telos side: {:?}", telos_value); + error!("Reth side: {:?}", reth_value); + } + }, + None => { + match_counter.add_telos_mismatched_storage(); + error!("Difference in accountstate: {:?}, key: {:?}", address, key); + error!("Telos side: {:?}", telos_value); + error!("Reth side: None"); + }, + } + }, + Err(_) => { + match_counter.add_telos_mismatched_storage(); + error!("Difference in accountstate: {:?}, key: {:?}", address, key); + error!("Telos side: {:?}", telos_value); + error!("Reth side: None"); + }, + } + match_counter.add_telos_total_storage(); + } + + + for (address, _) in plain_account_state.iter() { + let account_at_specific_height = state_at_specific_height.basic_account(*address); + let telos_account = account_table.get(address); + match account_at_specific_height { + Ok(account) => { + match account { + Some(reth_account) => { + if telos_account.is_none() { + match_counter.add_reth_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: None"); + error!("Reth side: {:?}", reth_account); + } else { + let telos_account_unwrapped = telos_account.unwrap(); + if reth_account.balance != telos_account_unwrapped.balance || reth_account.nonce != telos_account_unwrapped.nonce { + match_counter.add_reth_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account); + error!("Reth side: {:?}", reth_account); + } + } + + }, + None => { + if telos_account.is_some() { + match_counter.add_reth_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account.unwrap()); + error!("Reth side: None"); + } + }, + } + }, + Err(_) => { + if telos_account.is_some() { + match_counter.add_reth_mismatched_account(); + error!("Difference in account: {:?}", address); + error!("Telos side: {:?}", telos_account.unwrap()); + error!("Reth side: None"); + } + }, + } + match_counter.add_reth_total_account(); + } + + for (address,storage_entry) in plain_storage_state.iter() { + let storage_at_specific_height = state_at_specific_height.storage(*address, storage_entry.key); + let telos_accountstate = accountstate_table.get(&(*address, storage_entry.key)); + match storage_at_specific_height { + Ok(storage) => { + match storage { + Some(reth_value) => { + if telos_accountstate.is_none() { + if reth_value != U256::ZERO { + match_counter.add_reth_mismatched_storage(); + error!("Difference in accountstate: {:?}", address); + error!("Telos side: None"); + error!("Reth side: {:?}", reth_value); + } + } else { + let telos_value = *telos_accountstate.unwrap(); + if reth_value != telos_value { + match_counter.add_reth_mismatched_storage(); + error!("Difference in accountstate: {:?}", address); + error!("Telos side: {:?}", telos_value); + error!("Reth side: {:?}", reth_value); + } + } + }, + None => { + if telos_accountstate.is_some() { + match_counter.add_reth_mismatched_storage(); + error!("Difference in accountstate: {:?}", address); + error!("Telos side: {:?}", telos_accountstate.unwrap()); + error!("Reth side: None"); + } + }, + } + }, + Err(_) => { + if telos_accountstate.is_some() { + match_counter.add_reth_mismatched_storage(); + error!("Difference in accountstate: {:?}", address); + error!("Telos side: {:?}", telos_accountstate.unwrap()); + error!("Reth side: None"); + } + }, + } + match_counter.add_reth_total_storage(); + } + + match_counter +} + +/// This function retrieves account and accountstate tables from native RPC +pub async fn get_telos_tables(telos_rpc: &str, block_delta: u32) -> (HashMap, HashMap<(Address, B256), U256>, BlockId) { + + let api_client = APIClient::::default_provider(telos_rpc.into(), Some(5)).unwrap(); + let info_start = api_client.v1_chain.get_info().await.unwrap(); + + let evm_block_num_start = info_start.head_block_num - block_delta; + + let mut has_more_account = true; + let mut lower_bound_account = Some(TableIndexType::UINT64(0)); + + let evm_block_id = BlockId::from(evm_block_num_start as u64); + + let mut account_table = HashMap::default(); + let mut accountstate_table = HashMap::default(); + + while has_more_account { + let query_params_account = GetTableRowsParams { + code: name!("eosio.evm"), + table: name!("account"), + scope: None, + lower_bound: lower_bound_account, + upper_bound: None, + limit: Some(5000), + reverse: None, + index_position: None, + show_payer: None, + }; + let account_rows = api_client.v1_chain.get_table_rows::(query_params_account).await; + if let Ok(account_rows) = account_rows { + lower_bound_account = account_rows.next_key; + has_more_account = lower_bound_account.is_some(); + for account_row in account_rows.rows { + let address = Address::from_slice(account_row.address.data.as_slice()); + lower_bound_account = Some(TableIndexType::UINT64(account_row.index + 1)); + account_table.insert(address,Account { + nonce: account_row.nonce, + balance: U256::from_be_bytes(account_row.balance.data), + bytecode_hash: None, + }); + let mut has_more_accountstate = true; + let mut lower_bound_accountstate = Some(TableIndexType::UINT64(0)); + while has_more_accountstate { + let query_params_accountstate = GetTableRowsParams { + code: name!("eosio.evm"), + table: name!("accountstate"), + scope: Some(Name::from_u64(account_row.index)), + lower_bound: lower_bound_accountstate, + upper_bound: None, + limit: Some(5000), + reverse: None, + index_position: None, + show_payer: None, + }; + let accountstate_rows = api_client.v1_chain.get_table_rows::(query_params_accountstate).await; + if let Ok(accountstate_rows) = accountstate_rows { + lower_bound_accountstate = accountstate_rows.next_key; + has_more_accountstate = lower_bound_accountstate.is_some(); + for accountstate_row in accountstate_rows.rows { + lower_bound_accountstate = Some(TableIndexType::UINT64(accountstate_row.index + 1)); + accountstate_table.insert((address,B256::from(accountstate_row.key.data)),U256::from_be_bytes(accountstate_row.value.data)); + } + } else { + panic!("Failed to fetch accountstate row"); + } + } + } + } else { + panic!("Failed to fetch account row"); + } + } + + let info_end = api_client.v1_chain.get_info().await.unwrap(); + let evm_block_num_end = info_end.head_block_num - block_delta; + + if evm_block_num_start != evm_block_num_end { + panic!("Nodeos is syncing, it is impossible to get an accurate state from a syncing native RPC"); + } + + (account_table, accountstate_table, evm_block_id) +} \ No newline at end of file diff --git a/crates/telos/node/tests/integration.rs b/crates/telos/node/tests/integration.rs index 268fde39928c..e6f09a9d715d 100644 --- a/crates/telos/node/tests/integration.rs +++ b/crates/telos/node/tests/integration.rs @@ -160,6 +160,8 @@ async fn testing_chain_sync() { persistence_threshold: 0, memory_block_buffer_target: 1, max_execute_block_batch_size: 100, + two_way_storage_compare: false, + block_delta: None, }; let node_handle = NodeBuilder::new(node_config.clone()) diff --git a/crates/telos/node/tests/main.rs b/crates/telos/node/tests/main.rs index 43614c668a2c..a23cd12d3808 100644 --- a/crates/telos/node/tests/main.rs +++ b/crates/telos/node/tests/main.rs @@ -1,4 +1,5 @@ -mod integration; -pub mod live_test_runner; +// mod integration; +// pub mod live_test_runner; +pub mod storage_compare; const fn main() {} \ No newline at end of file diff --git a/crates/telos/node/tests/state_bypass.rs b/crates/telos/node/tests/state_bypass.rs new file mode 100644 index 000000000000..88bfc9fd0d03 --- /dev/null +++ b/crates/telos/node/tests/state_bypass.rs @@ -0,0 +1,456 @@ +use std::{fmt, fs}; +use std::fmt::{Display, Formatter}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; +use alloy_provider::{Provider, ProviderBuilder}; +use reqwest::Url; +use serde_json::json; +use telos_consensus_client::execution_api_client::{ExecutionApiClient, RpcRequest}; +use telos_consensus_client::execution_api_client::ExecutionApiMethod::{ForkChoiceUpdatedV1, NewPayloadV1}; +use tracing::info; +use reth::args::RpcServerArgs; +use reth::builder::NodeConfig; +use alloy_primitives::{Address, B256, hex, U256, Bytes, map::HashMap}; +use alloy_primitives::hex::FromHex; +use alloy_rpc_types::engine::ExecutionPayloadV1; +use reth::primitives::revm_primitives::{Bytecode as RevmBytecode, LegacyAnalyzedBytecode}; +use reth::providers::ProviderError; +use reth::revm; +use reth::revm::db::{CacheDB, EmptyDBTyped, StorageWithOriginalValues, states::StorageSlot}; +use reth::revm::{Database, DatabaseCommit, DatabaseRef, Evm, State, TransitionAccount}; +use reth::revm::primitives::{AccountInfo, EvmStorageSlot}; +use reth::rpc::types::engine::ForkchoiceState; +use reth::tasks::TaskManager; +use reth_chainspec::{ChainSpec, ChainSpecBuilder, TEVMTESTNET}; +use reth_e2e_test_utils::node::NodeTestContext; +use reth_node_builder::NodeBuilder; +use reth_node_telos::{TelosArgs, TelosNode}; +use reth_primitives::constants::{EMPTY_ROOT_HASH, MIN_PROTOCOL_BASE_FEE}; +use reth_primitives::revm_primitives::AccountStatus; +use reth_telos_rpc::TelosClient; +use reth_telos_rpc_engine_api::compare::compare_state_diffs; +use reth_telos_rpc_engine_api::structs::{TelosAccountTableRow, TelosAccountStateTableRow, TelosEngineAPIExtraFields}; +use revm::primitives::Account; + +#[derive(Debug)] +enum MockDBError { + GenericError(String) +} + +impl Into for MockDBError { + fn into(self) -> ProviderError { + match self { + MockDBError::GenericError(msg) => { + ProviderError::NippyJar(msg) + } + } + } +} + +impl Display for MockDBError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + MockDBError::GenericError(msg) => { + f.write_str(&msg) + } + } + } +} + +fn init_reth() -> eyre::Result<(NodeConfig, String)> { + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(TEVMTESTNET.chain) + .genesis(TEVMTESTNET.genesis.clone()) + .frontier_activated() + .homestead_activated() + .tangerine_whistle_activated() + .spurious_dragon_activated() + .byzantium_activated() + .constantinople_activated() + .petersburg_activated() + .istanbul_activated() + .berlin_activated() + .build(), + ); + + let mut rpc_config = RpcServerArgs::default().with_unused_ports().with_http(); + rpc_config.auth_jwtsecret = Some(PathBuf::from("tests/assets/jwt.hex")); + + // Node setup + let node_config = NodeConfig::test().with_chain(chain_spec).with_rpc(rpc_config.clone()); + + let jwt = fs::read_to_string(node_config.rpc.auth_jwtsecret.clone().unwrap())?; + Ok((node_config, jwt)) +} + +#[tokio::test] +async fn test_integration_tevm_only() { + tracing_subscriber::fmt::init(); + + let (node_config, jwt_secret) = init_reth().unwrap(); + + let exec = TaskManager::current(); + let exec = exec.executor(); + + reth_tracing::init_test_tracing(); + + let telos_args = TelosArgs { + telos_endpoint: None, + signer_account: Some("rpc.evm".to_string()), + signer_permission: Some("active".to_string()), + signer_key: Some("5Jr65kdYmn33C3UabzhmWDm2PuqbRfPuDStts3ZFNSBLM7TqaiL".to_string()), + gas_cache_seconds: None, + experimental: false, + persistence_threshold: 0, + memory_block_buffer_target: 0, + max_execute_block_batch_size: 0, + }; + + let node_handle = NodeBuilder::new(node_config.clone()) + .testing_node(exec) + .node(TelosNode::new(telos_args.clone())) + .extend_rpc_modules(move |ctx| { + if telos_args.telos_endpoint.is_some() { + ctx.registry.eth_api().set_telos_client(TelosClient::new(telos_args.into())); + } + + Ok(()) + }) + .launch() + .await + .unwrap(); + + let execution_port = node_handle.node.auth_server_handle().local_addr().port(); + let rpc_port = node_handle.node.rpc_server_handles.rpc.http_local_addr().unwrap().port(); + println!("Starting Reth on RPC port {}!", rpc_port); + let _ = NodeTestContext::new(node_handle.node.clone()).await.unwrap(); + + let custom_balance = U256::from(80085); + + let exec_client = ExecutionApiClient::new(&format!("http://127.0.0.1:{}", execution_port), &jwt_secret).unwrap(); + + let execution_payload = ExecutionPayloadV1 { + parent_hash: B256::from_hex("b25034033c9ca7a40e879ddcc29cf69071a22df06688b5fe8cc2d68b4e0528f9").unwrap(), + fee_recipient: Default::default(), + state_root: EMPTY_ROOT_HASH, + receipts_root: EMPTY_ROOT_HASH, + logs_bloom: Default::default(), + prev_randao: Default::default(), + block_number: 1, + gas_limit: 0x7fffffff, + gas_used: 0, + timestamp: 1728067687, + extra_data: Default::default(), + base_fee_per_gas: U256::try_from(MIN_PROTOCOL_BASE_FEE).unwrap(), + block_hash: B256::from_hex("0a1d73423169c8b4124121d40c0e13eb078621e73effd2d183f9a1d8017537dd").unwrap(), + transactions: vec![], + }; + + let test_addr = Address::from_hex("00000000000000000000000000000000deadbeef").unwrap(); + + let extra_fields = TelosEngineAPIExtraFields { + statediffs_account: Some(vec![TelosAccountTableRow { + removed: false, + address: test_addr, + account: "eosio".to_string(), + nonce: 0, + code: Default::default(), + balance: custom_balance, + }]), + statediffs_accountstate: Some(vec![]), + revision_changes: None, + gasprice_changes: None, + new_addresses_using_create: Some(vec![]), + new_addresses_using_openwallet: Some(vec![]), + receipts: Some(vec![]), + }; + + let block_req = RpcRequest { + method: NewPayloadV1, + params: vec![ + json![execution_payload], + json![extra_fields] + ].into() + }; + + let new_block_result = exec_client.rpc(block_req).await.unwrap(); + + info!("new_block: {:#?}", new_block_result); + + let fork_choice_result = exec_client.rpc(RpcRequest { + method: ForkChoiceUpdatedV1, + params: json![vec![ForkchoiceState { + head_block_hash: execution_payload.block_hash, + safe_block_hash: execution_payload.block_hash, + finalized_block_hash: execution_payload.block_hash + }]] + }).await.unwrap(); + + info!("fork_choice: {:#?}", fork_choice_result); + + + let provider = ProviderBuilder::new() + //.network::() + .on_http(Url::from_str(format!("http://localhost:{}", rpc_port).as_str()).unwrap()); + + let balance = provider.get_balance(test_addr).await.unwrap(); + info!("balance: {:#?}", balance); + + assert_eq!(balance, custom_balance); +} + +#[test] +fn test_db_both_sides_present_but_dif() { + let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); + + let init_balance = U256::from(0); + let custom_balance = U256::from(80085); + + let init_nonce = 0; + let custom_nonce = 69; + + let revm_acc_info = AccountInfo { + balance: init_balance, + nonce: init_nonce, + code_hash: Default::default(), + code: None, + }; + + let mut db = CacheDB::new(EmptyDBTyped::::new()); + db.insert_account_info(test_addr, revm_acc_info); + + let mut state = State::builder().with_database(db).build(); + + let mut evm = Evm::builder().with_db(&mut state).build(); + + let statediffs_account = vec![TelosAccountTableRow { + removed: false, + address: test_addr, + account: "eosio".to_string(), + nonce: custom_nonce, + code: Default::default(), + balance: custom_balance, + }]; + + compare_state_diffs( + &mut evm, + HashMap::default(), + statediffs_account.clone(), + vec![], + vec![], + vec![], + false + ); + + let db_acc = evm.db_mut().basic(test_addr).unwrap().unwrap(); + assert_eq!(db_acc.nonce, statediffs_account[0].nonce); + assert_eq!(db_acc.balance, statediffs_account[0].balance); +} + +#[test] +fn test_db_both_sides_only_code() { + let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); + + let custom_code = Bytes::from(&hex!("ffff")); + let custom_bytecode = RevmBytecode::LegacyRaw(custom_code.clone()); + + let revm_acc_info = AccountInfo { + balance: U256::from(0), + nonce: 0, + code_hash: Default::default(), + code: None, + }; + + let mut db = CacheDB::new(EmptyDBTyped::::new()); + db.insert_account_info(test_addr, revm_acc_info); + + let mut state = State::builder().with_database(db).build(); + + let mut evm = Evm::builder().with_db(&mut state).build(); + + let statediffs_account = vec![TelosAccountTableRow { + removed: false, + address: test_addr, + account: "eosio".to_string(), + nonce: 0, + code: custom_code.clone(), + balance: U256::from(0), + }]; + + compare_state_diffs( + &mut evm, + HashMap::default(), + statediffs_account.clone(), + vec![], + vec![], + vec![], + false + ); + + let db_acc = evm.db_mut().basic(test_addr).unwrap().unwrap(); + assert_eq!(db_acc.code, Some(custom_bytecode)); +} + +#[test] +fn test_revm_state_both_sides_present_but_dif() { + let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); + + let revm_acc_info = AccountInfo { + balance: U256::from(1), + nonce: 0, + code_hash: Default::default(), + code: None, + }; + + let mut revm_state_diffs = HashMap::default(); + + let mut transition_account = TransitionAccount::new_empty_eip161(HashMap::default()); + + transition_account.info = Some(revm_acc_info); + + revm_state_diffs.insert(test_addr, transition_account); + + let mut db = CacheDB::new(EmptyDBTyped::::new()); + + let mut state = State::builder().with_database(db).build(); + + let mut evm = Evm::builder().with_db(&mut state).build(); + + let statediffs_account = vec![TelosAccountTableRow { + removed: false, + address: test_addr, + account: "eosio".to_string(), + nonce: 1, + code: Default::default(), + balance: U256::from(80085), + }]; + + compare_state_diffs( + &mut evm, + revm_state_diffs, + statediffs_account.clone(), + vec![], + vec![], + vec![], + false + ); + + let db_acc = evm.db_mut().basic(test_addr).unwrap().unwrap(); + assert_eq!(db_acc.nonce, statediffs_account[0].nonce); + assert_eq!(db_acc.balance, statediffs_account[0].balance); +} + +#[test] +fn test_tevm_only() { + let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); + + let mut db = CacheDB::new(EmptyDBTyped::::new()); + + let mut state = State::builder().with_database(db).build(); + + let mut evm = Evm::builder().with_db(&mut state).build(); + + let statediffs_account = vec![TelosAccountTableRow { + removed: false, + address: test_addr, + account: "eosio".to_string(), + nonce: 1, + code: Default::default(), + balance: U256::from(80085), + }]; + + compare_state_diffs( + &mut evm, + HashMap::default(), + statediffs_account.clone(), + vec![], + vec![], + vec![], + false + ); + + let db_acc = evm.db_mut().basic(test_addr).unwrap().unwrap(); + assert_eq!(db_acc.nonce, statediffs_account[0].nonce); + assert_eq!(db_acc.balance, statediffs_account[0].balance); +} + +#[test] +fn test_accstate_diff_from_storage() { + let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); + + let revm_acc_info = AccountInfo { + balance: U256::from(1), + nonce: 0, + code_hash: Default::default(), + code: None, + }; + + let key = U256::from(420); + let value = U256::from(0); + let custom_value = U256::from(80085); + + let mut db = CacheDB::new(EmptyDBTyped::::new()); + + let mut storage = HashMap::default(); + storage.insert(key, value); + + let mut state = State::builder().with_database(db).build(); + + state.insert_account_with_storage(test_addr, revm_acc_info, storage); + + let mut evm = Evm::builder().with_db(&mut state).build(); + + let statediffs_accountstate = vec![TelosAccountStateTableRow { + removed: false, + address: test_addr, + key, + value: custom_value + }]; + + compare_state_diffs( + &mut evm, + HashMap::default(), + vec![], + statediffs_accountstate.clone(), + vec![], + vec![], + false + ); + + let db_value = evm.db_mut().storage(test_addr, key).unwrap(); + assert_eq!(db_value, custom_value); +} +// #[test] +// fn test_accstate_telos_only() { +// let test_addr = Address::from_str("00000000000000000000000000000000deadbeef").unwrap(); +// +// let key = U256::from(420); +// let custom_value = U256::from(80085); +// +// let mut db = CacheDB::new(EmptyDBTyped::::new()); +// +// let mut state = State::builder().with_database(db).build(); +// +// // state.insert_not_existing(test_addr); +// +// let mut evm = Evm::builder().with_db(&mut state).build(); +// +// let statediffs_accountstate = vec![TelosAccountStateTableRow { +// removed: false, +// address: test_addr, +// key, +// value: custom_value +// }]; +// +// compare_state_diffs( +// &mut evm, +// HashMap::default(), +// vec![], +// statediffs_accountstate.clone(), +// vec![], +// vec![], +// true +// ); +// } diff --git a/crates/telos/node/tests/storage_compare.rs b/crates/telos/node/tests/storage_compare.rs new file mode 100644 index 000000000000..2fc40cf1cd7b --- /dev/null +++ b/crates/telos/node/tests/storage_compare.rs @@ -0,0 +1,224 @@ +use std::str::FromStr; +use alloy_primitives::{Address, StorageValue, U256}; +use alloy_provider::{Provider, ProviderBuilder, ReqwestProvider}; +use alloy_rpc_types::BlockId; +use antelope::api::client::{APIClient, DefaultProvider}; +use antelope::api::v1::structs::{GetTableRowsParams, TableIndexType}; +use antelope::chain::name::Name; +use antelope::{name, StructPacker}; +use antelope::chain::{Encoder, Decoder, Packer}; +use antelope::chain::checksum::{Checksum160, Checksum256}; +use reqwest::Url; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] +pub struct AccountRow { + pub index: u64, + pub address: Checksum160, + pub account: Name, + pub nonce: u64, + pub code: Vec, + pub balance: Checksum256, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] +pub struct AccountStateRow { + pub index: u64, + pub key: Checksum256, + pub value: Checksum256, +} + +#[derive(Debug)] +struct MatchCounter { + evm_block_number: BlockId, + total_accounts: u64, + total_storage_rows: u64, + mismatched_accounts: u64, + mismatched_storage_rows: u64, +} + +impl MatchCounter { + pub fn new(evm_block_number: BlockId) -> Self { + Self { + evm_block_number, + total_accounts: 0, + total_storage_rows: 0, + mismatched_accounts: 0, + mismatched_storage_rows: 0, + } + } + + pub fn print(&self) { + println!("Compared at block: {:?}", self.evm_block_number); + println!("Mismatched accounts: {}", self.mismatched_accounts); + println!("Mismatched storage rows: {}", self.mismatched_storage_rows); + println!("Matching accounts: {}", self.total_accounts - self.mismatched_accounts); + println!("Matching storage rows: {}", self.total_storage_rows - self.mismatched_storage_rows); + println!("Total accounts: {}", self.total_accounts); + println!("Total storage rows: {}", self.total_storage_rows); + } + + pub fn add_matching_account(&mut self) { + self.total_accounts += 1; + } + + pub fn add_matching_account_storage(&mut self) { + self.total_storage_rows += 1; + } + + pub fn add_mismatched_account(&mut self) { + self.total_accounts += 1; + self.mismatched_accounts += 1; + } + + pub fn add_mismatched_account_storage(&mut self) { + self.total_storage_rows += 1; + self.mismatched_storage_rows += 1; + } + + pub fn matches(&self) -> bool { + self.mismatched_accounts == 0 && self.mismatched_storage_rows == 0 + } + +} + +#[tokio::test] +pub async fn compare() { + // let evm_rpc = "http://38.91.106.49:9545"; + let evm_rpc = "http://localhost:8545"; + // let telos_rpc = "http://192.168.0.20:8884"; + let telos_rpc = "http://38.91.106.49:8899"; + let block_delta = 57; + + assert!(storage_matches(evm_rpc, telos_rpc, block_delta).await); +} + +pub async fn storage_matches(evm_rpc: &str, telos_rpc: &str, block_delta: u32) -> bool { + let api_client = APIClient::::default_provider(telos_rpc.into(), Some(5)).unwrap(); + let info = api_client.v1_chain.get_info().await.unwrap(); + + let provider = ProviderBuilder::new() + .on_http(Url::from_str(evm_rpc).unwrap()); + + let evm_block_num = info.head_block_num - block_delta; + println!("Telos EVM Block Number: {:?}", evm_block_num); + + let mut has_more = true; + let mut lower_bound = Some(TableIndexType::UINT64(0)); + + let mut count = 0; + + let evm_block_id = BlockId::from(evm_block_num as u64); + let mut match_counter = MatchCounter::new(evm_block_id); + + while has_more { + let query_params = GetTableRowsParams { + code: name!("eosio.evm"), + table: name!("account"), + scope: None, + lower_bound, + upper_bound: None, + limit: Some(5000), + reverse: None, + index_position: None, + show_payer: None, + }; + let account_rows = api_client.v1_chain.get_table_rows::(query_params).await; + if let Ok(account_rows) = account_rows { + lower_bound = account_rows.next_key; + has_more = lower_bound.is_some(); + for account_row in account_rows.rows { + let address = Address::from_slice(account_row.address.data.as_slice()); + lower_bound = Some(TableIndexType::UINT64(account_row.index + 1)); + compare_account(&mut match_counter, &account_row, &api_client, &provider).await; + count += 1; + } + } else { + panic!("Failed to fetch account row"); + } + } + + match_counter.print(); + match_counter.matches() +} + +async fn compare_account(match_counter: &mut MatchCounter, account_row: &AccountRow, api_client: &APIClient, provider: &ReqwestProvider) { + let at_block = match_counter.evm_block_number; + let address = Address::from_slice(account_row.address.data.as_slice()); + let telos_balance = U256::from_be_slice(account_row.balance.data.as_slice()); + + let reth_balance = provider.get_balance(address).block_id(at_block).await.unwrap(); + let reth_nonce = provider.get_transaction_count(address).block_id(at_block).await.unwrap(); + let reth_code = provider.get_code_at(address).block_id(at_block).await.unwrap().to_vec(); + + let balance_missmatch = telos_balance != reth_balance; + let nonce_missmatch = account_row.nonce != reth_nonce; + let code_missmatch = account_row.code != reth_code; + + if balance_missmatch || nonce_missmatch || code_missmatch { + println!("ACCOUNT MISMATCH!!!"); + println!("Account: {:?}", address); + println!("Telos balance: {:?}", telos_balance); + println!("Telos nonce: {:?}", account_row.nonce); + println!("Telos code: {:?}", account_row.code); + println!("Reth balance: {:?}", reth_balance); + println!("Reth nonce: {:?}", reth_nonce); + println!("Reth code: {:?}", reth_code); + match_counter.add_mismatched_account(); + } else { + match_counter.add_matching_account(); + } + + compare_account_storage(match_counter, account_row, api_client, provider).await; +} + +async fn compare_account_storage(match_counter: &mut MatchCounter, account_row: &AccountRow, api_client: &APIClient, provider: &ReqwestProvider) { + let address = Address::from_slice(account_row.address.data.as_slice()); + + let mut has_more = true; + let mut lower_bound = Some(TableIndexType::UINT64(0)); + + let mut count = 0; + + while has_more { + let scope = if account_row.index == 0 { + Some(name!("")) + } else { + Some(Name::from_u64(account_row.index)) + }; + let query_params = GetTableRowsParams { + code: name!("eosio.evm"), + table: name!("accountstate"), + scope: Some(scope.unwrap()), + lower_bound, + upper_bound: None, + limit: Some(5000), + reverse: None, + index_position: None, + show_payer: None, + }; + let account_state_rows = api_client.v1_chain.get_table_rows::(query_params).await; + if let Ok(account_state_rows) = account_state_rows { + lower_bound = account_state_rows.next_key; + has_more = lower_bound.is_some(); + for account_state_row in account_state_rows.rows { + let key = U256::from_be_slice(account_state_row.key.data.as_slice()); + let telos_value: U256 = U256::from_be_slice(account_state_row.value.data.as_slice()); + let reth_value: U256 = provider.get_storage_at(address, key).block_id(match_counter.evm_block_number).await.unwrap(); + if telos_value != reth_value { + match_counter.add_mismatched_account_storage(); + println!("STORAGE MISMATCH!!!"); + println!("Storage account: {:?} with scope: {:?} and key: {:?}", address, scope.unwrap(), key); + println!("Telos Storage value: {:?}", telos_value); + println!("Reth storage value: {:?}", reth_value); + } else { + match_counter.add_matching_account_storage(); + } + lower_bound = Some(TableIndexType::UINT64(account_state_row.index + 1)); + count += 1; + } + } else { + panic!("Failed to fetch account state row"); + } + } +} \ No newline at end of file diff --git a/crates/telos/primitives-traits/Cargo.toml b/crates/telos/primitives-traits/Cargo.toml index 08755271599d..d728cd7a679a 100644 --- a/crates/telos/primitives-traits/Cargo.toml +++ b/crates/telos/primitives-traits/Cargo.toml @@ -14,6 +14,7 @@ alloy-consensus = { workspace = true } alloy-primitives = { version = "0.8.5", features = ["rlp"], default-features = false } alloy-eips = { version = "0.4", features = ["kzg-sidecar"], default-features = false } alloy-rlp = { version = "0.3", default-features = false } +alloy-rpc-types = { version = "0.4", default-features = false } alloy-serde = { version = "0.4", optional = true, default-features = false } serde = { version = "1.0", features = ["derive", "alloc"], default-features = false } serde_with = { version = "3.3.0", optional = true } diff --git a/crates/telos/primitives-traits/src/header.rs b/crates/telos/primitives-traits/src/header.rs index a16e63e25e6c..41777da226b1 100644 --- a/crates/telos/primitives-traits/src/header.rs +++ b/crates/telos/primitives-traits/src/header.rs @@ -15,6 +15,7 @@ use alloy_rlp::{ length_of_length, Buf, BufMut, Decodable, Encodable, EMPTY_LIST_CODE, EMPTY_STRING_CODE, }; use core::mem; +use alloy_rpc_types::ConversionError; use reth_codecs::Compact; use crate::TelosBlockExtension; @@ -946,3 +947,35 @@ impl Compact for TelosHeader { (alloy_header, buf) } } + +#[cfg(feature = "serde")] +impl TryFrom for TelosHeader { + type Error = ConversionError; + + fn try_from(header: alloy_rpc_types::Header) -> Result { + Ok(Self { + parent_hash: header.parent_hash, + ommers_hash: header.uncles_hash, + beneficiary: header.miner, + state_root: header.state_root, + transactions_root: header.transactions_root, + receipts_root: header.receipts_root, + logs_bloom: header.logs_bloom, + difficulty: header.difficulty, + number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data: header.extra_data, + mix_hash: header.mix_hash.unwrap_or_default(), + nonce: header.nonce.unwrap_or_default(), + base_fee_per_gas: header.base_fee_per_gas, + withdrawals_root: header.withdrawals_root, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: header.parent_beacon_block_root, + requests_root: header.requests_root, + telos_block_extension: TelosBlockExtension::default(), + }) + } +} \ No newline at end of file diff --git a/crates/telos/rpc-engine-api/Cargo.toml b/crates/telos/rpc-engine-api/Cargo.toml index 30e0d56832e9..417f4537c268 100644 --- a/crates/telos/rpc-engine-api/Cargo.toml +++ b/crates/telos/rpc-engine-api/Cargo.toml @@ -16,6 +16,7 @@ reth-storage-errors.workspace = true revm.workspace = true revm-primitives.workspace = true tracing.workspace = true +sha2.workspace = true [lints] workspace = true diff --git a/crates/telos/rpc-engine-api/src/compare.rs b/crates/telos/rpc-engine-api/src/compare.rs index 5c0367763ed0..b11bed53ce1b 100644 --- a/crates/telos/rpc-engine-api/src/compare.rs +++ b/crates/telos/rpc-engine-api/src/compare.rs @@ -1,13 +1,112 @@ use std::collections::HashSet; use std::fmt::Display; -use alloy_primitives::{Address, B256, U256}; -use revm_primitives::HashMap; -use revm::db::AccountStatus; -use revm::{Database, Evm, State, TransitionAccount}; -use tracing::debug; +use alloy_primitives::{Address, B256, Bytes, U256}; +use revm_primitives::{Account, AccountInfo, Bytecode, EvmStorageSlot, HashMap}; +use revm::{Database, Evm, State, TransitionAccount, db::AccountStatus as DBAccountStatus}; +use revm_primitives::db::DatabaseCommit; +use revm_primitives::state::AccountStatus; +use sha2::{Digest, Sha256}; +use tracing::{debug, warn, info}; +use reth_primitives::KECCAK_EMPTY; use reth_storage_errors::provider::ProviderError; use crate::structs::{TelosAccountStateTableRow, TelosAccountTableRow}; +struct StateOverride { + accounts: HashMap +} + +impl StateOverride { + pub fn new() -> Self { + StateOverride { + accounts: HashMap::default() + } + } + + fn maybe_init_account (&mut self, revm_db: &mut &mut State, address: Address) { + let maybe_acc = self.accounts.get_mut(&address); + if maybe_acc.is_none() { + let mut status = AccountStatus::LoadedAsNotExisting | AccountStatus::Touched; + let info = match revm_db.basic(address) { + Ok(maybe_info) => { + maybe_info.unwrap_or_else(|| AccountInfo::default()) + }, + Err(_) => { + status = AccountStatus::Created | AccountStatus::Touched; + AccountInfo::default() + } + }; + + self.accounts.insert(address, Account { + info, + storage: Default::default(), + status, + }); + } + } + + pub fn override_account (&mut self, revm_db: &mut &mut State, telos_row: &TelosAccountTableRow) { + self.maybe_init_account(revm_db, telos_row.address); + let mut acc = self.accounts.get_mut(&telos_row.address).unwrap(); + acc.info.balance = telos_row.balance; + acc.info.nonce = telos_row.nonce; + if telos_row.code.len() > 0 { + acc.info.code_hash = B256::from_slice(Sha256::digest(telos_row.code.as_ref()).as_slice()); + acc.info.code = Some(Bytecode::LegacyRaw(telos_row.code.clone())); + } else { + acc.info.code_hash = KECCAK_EMPTY; + acc.info.code = None; + } + } + + pub fn override_balance (&mut self, revm_db: &mut &mut State, address: Address, balance: U256) { + self.maybe_init_account(revm_db, address); + let mut acc = self.accounts.get_mut(&address).unwrap(); + acc.info.balance = balance; + } + + pub fn override_nonce (&mut self, revm_db: &mut &mut State, address: Address, nonce: u64) { + self.maybe_init_account(revm_db, address); + let mut acc = self.accounts.get_mut(&address).unwrap(); + acc.info.nonce = nonce; + } + + pub fn override_code (&mut self, revm_db: &mut &mut State, address: Address, maybe_code: &Bytes) { + self.maybe_init_account(revm_db, address); + let mut acc = self.accounts.get_mut(&address).unwrap(); + if maybe_code.len() > 0 { + acc.info.code_hash = B256::from_slice(Sha256::digest(maybe_code.as_ref()).as_slice()); + acc.info.code = Some(Bytecode::LegacyRaw(maybe_code.clone())); + } else { + acc.info.code_hash = KECCAK_EMPTY; + acc.info.code = None; + } + } + + pub fn override_storage (&mut self, revm_db: &mut &mut State, address: Address, key: U256, new_val: U256, old_val: U256) { + self.maybe_init_account(revm_db, address); + let mut acc = self.accounts.get_mut(&address).unwrap(); + acc.storage.insert(key, EvmStorageSlot { + original_value: old_val, + present_value: new_val, + is_cold: false + }); + } + + pub fn apply (&self, revm_db: &mut &mut State) { + revm_db.commit(self.accounts.clone()); + } +} + +macro_rules! maybe_panic { + ($panic_mode:expr, $($arg:tt)*) => { + if $panic_mode { + panic!($($arg)*); + } else { + warn!($($arg)*); + } + }; +} + /// This function compares the state diffs between revm and Telos EVM contract pub fn compare_state_diffs( evm: &mut Evm<'_, Ext, &mut State>, @@ -16,6 +115,7 @@ pub fn compare_state_diffs( statediffs_accountstate: Vec, _new_addresses_using_create: Vec<(u64, U256)>, new_addresses_using_openwallet: Vec<(u64, U256)>, + panic_mode: bool ) -> bool where DB: Database, @@ -34,6 +134,8 @@ where let revm_db: &mut &mut State = evm.db_mut(); + let mut state_override = StateOverride::new(); + let mut new_addresses_using_openwallet_hashset = HashSet::new(); for row in &new_addresses_using_openwallet { new_addresses_using_openwallet_hashset.insert(Address::from_word(B256::from(row.1))); @@ -43,10 +145,10 @@ where for row in &statediffs_account { statediffs_account_hashmap.insert(row.address); } - let mut statediffs_accountstate_hashmap = HashSet::new(); - for row in &statediffs_accountstate { - statediffs_accountstate_hashmap.insert((row.address,row.key)); - } + // let mut statediffs_accountstate_hashmap = HashSet::new(); + // for row in &statediffs_accountstate { + // statediffs_accountstate_hashmap.insert((row.address,row.key)); + // } for row in &statediffs_account { // Skip if address is created using openwallet and is empty @@ -55,80 +157,138 @@ where } // Skip if row is removed if row.removed { + // TODO: Does the Telos EVM contract ever remove account rows, or should this be a panic?? continue } if let Ok(revm_row) = revm_db.basic(row.address) { if let Some(unwrapped_revm_row) = revm_row { // Check balance inequality if unwrapped_revm_row.balance != row.balance { - panic!("Difference in balance, address: {:?} - revm: {:?} - tevm: {:?}",row.address,unwrapped_revm_row.balance,row.balance); + maybe_panic!(panic_mode, "Difference in balance, address: {:?} - revm: {:?} - tevm: {:?}",row.address,unwrapped_revm_row.balance,row.balance); + state_override.override_balance(revm_db, row.address, row.balance); } // Check nonce inequality if unwrapped_revm_row.nonce != row.nonce { - panic!("Difference in nonce, address: {:?} - revm: {:?} - tevm: {:?}",row.address,unwrapped_revm_row.nonce,row.nonce); + maybe_panic!(panic_mode, "Difference in nonce, address: {:?} - revm: {:?} - tevm: {:?}",row.address,unwrapped_revm_row.nonce,row.nonce); + state_override.override_nonce(revm_db, row.address, row.nonce); } // Check code size inequality - if unwrapped_revm_row.clone().code.is_none() && row.code.len() != 0 || unwrapped_revm_row.clone().code.is_some() && !unwrapped_revm_row.clone().code.unwrap().is_empty() && row.code.len() == 0 { - match revm_db.code_by_hash(unwrapped_revm_row.code_hash) { - Ok(code_by_hash) => - if (code_by_hash.is_empty() && row.code.len() != 0) || (!code_by_hash.is_empty() && row.code.len() == 0) { - panic!("Difference in code existence, address: {:?} - revm: {:?} - tevm: {:?}",row.address,code_by_hash,row.code) - }, - Err(_) => panic!("Difference in code existence, address: {:?} - revm: {:?} - tevm: {:?}",row.address,unwrapped_revm_row.code,row.code), + let maybe_revm_code = unwrapped_revm_row.clone().code; + + match maybe_revm_code { + None => { + if row.code.len() != 0 { + match revm_db.code_by_hash(unwrapped_revm_row.code_hash()) { + Ok(bytecode) => { + if bytecode.len() != row.code.len() { + maybe_panic!(panic_mode, "Difference in code size, address: {:?} - revm: {} - tevm: {}",row.address,bytecode.len(),row.code.len()); + state_override.override_code(revm_db, row.address, &row.code); + } + } + Err(_) => { + maybe_panic!(panic_mode, "Difference in code existence, error while fetching db, address: {:?} - revm: Err - tevm: {}",row.address,row.code.len()); + state_override.override_code(revm_db, row.address, &row.code); + } + } + } + } + Some(revm_bytecode) => { + match revm_bytecode { + Bytecode::LegacyRaw(code) => { + if code.len() != row.code.len() { + maybe_panic!(panic_mode, "Difference in legacy code size, address: {:?} - revm: {:?} - tevm: {:?}",row.address,code.len(),row.code.len()); + state_override.override_code(revm_db, row.address, &row.code); + } + } + Bytecode::LegacyAnalyzed(code) => { + if code.original_len() != row.code.len() { + maybe_panic!(panic_mode, "Difference in legacy (analyzed) code size, address: {:?} - revm: {:?} - tevm: {:?}",row.address,code.original_len(),row.code.len()); + state_override.override_code(revm_db, row.address, &row.code); + } + } + Bytecode::Eof(_) => panic!("Eof not implemented!"), + Bytecode::Eip7702(_) => panic!("EIP7702 not implemented!") + } } } // // Check code content inequality // if unwrapped_revm_row.clone().unwrap().code.is_some() && !unwrapped_revm_row.clone().unwrap().code.unwrap().is_empty() && unwrapped_revm_row.clone().unwrap().code.unwrap().bytes() != row.code { - // panic!("Difference in code content, revm: {:?}, tevm: {:?}",unwrapped_revm_row.clone().unwrap().code.unwrap().bytes(),row.code); + // panic!(panic_mode, "Difference in code content, revm: {:?}, tevm: {:?}",unwrapped_revm_row.clone().unwrap().code.unwrap().bytes(),row.code); // } } else { // Skip if address is empty on both sides if !(row.balance == U256::ZERO && row.nonce == 0 && row.code.len() == 0) { if let Some(unwrapped_revm_state_diff) = revm_state_diffs.get(&row.address) { - if !(unwrapped_revm_state_diff.status == AccountStatus::Destroyed && row.nonce == 0 && row.balance == U256::ZERO && row.code.len() == 0) { - panic!("A modified `account` table row was found on both revm state and revm state diffs, but seems to be destroyed on just one side, address: {:?}",row.address); + if !(unwrapped_revm_state_diff.status == DBAccountStatus::Destroyed && row.nonce == 0 && row.balance == U256::ZERO && row.code.len() == 0) { + maybe_panic!(panic_mode, "A modified `account` table row was found on both revm state and revm state diffs, but seems to be destroyed on just one side, address: {:?}",row.address); + state_override.override_account(revm_db, &row); } } else { - panic!("A modified `account` table row was found on revm state, but contains no information, address: {:?}",row.address); + maybe_panic!(panic_mode, "A modified `account` table row was found on revm state, but contains no information, address: {:?}",row.address); + state_override.override_account(revm_db, &row); } } } } else { // Skip if address is empty on both sides if !(row.balance == U256::ZERO && row.nonce == 0 && row.code.len() == 0) { - panic!("A modified `account` table row was not found on revm state, address: {:?}",row.address); + maybe_panic!(panic_mode, "A modified `account` table row was not found on revm state, address: {:?}",row.address); + state_override.override_account(revm_db, &row); } } } - for row in &statediffs_accountstate { - if let Ok(revm_row) = revm_db.storage(row.address, row.key) { - // The values should match, but if it is removed, then the revm value should be zero - if !(revm_row == row.value) && !(revm_row != U256::ZERO || row.removed == true) { - panic!("Difference in value on revm storage, address: {:?}, key: {:?}, revm-value: {:?}, tevm-row: {:?}",row.address,row.key,revm_row,row); - } - } else { - panic!("Key was not found on revm storage, address: {:?}, key: {:?}",row.address,row.key); - } - } + // for row in &statediffs_accountstate { + // if let None = revm_db.cache.accounts.get_mut(&row.address) { + // let cached_account = revm_db.load_cache_account(row.address); + // match cached_account { + // Ok(cached_account) => { + // if cached_account.account.is_none() { + // panic!("An account state modification was made for an account that is not in revm storage, address: {:?}", row.address); + // } + // }, + // Err(_) => { + // panic!("An account state modification was made for an account that returned Err from load_cache_account, address: {:?}", row.address); + // } + // } + // } + // if let Ok(revm_row) = revm_db.storage(row.address, row.key) { + // // The values should match, but if it is removed, then the revm value should be zero + // if revm_row != row.value { + // if revm_row != U256::ZERO && row.removed == true { + // maybe_panic!(panic_mode, "Difference in value on revm storage, removed on Telos, non-ZERO on revm, address: {:?}, key: {:?}, revm-value: {:?}, tevm-row: {:?}", row.address, row.key, revm_row, row); + // state_override.override_storage(revm_db, row.address, row.key, U256::ZERO, revm_row); + // } + // if row.removed == false { + // maybe_panic!(panic_mode, "Difference in value on revm storage, address: {:?}, key: {:?}, revm-value: {:?}, tevm-row: {:?}", row.address, row.key, revm_row, row); + // state_override.override_storage(revm_db, row.address, row.key, row.value, revm_row); + // } + // } + // } else { + // maybe_panic!(panic_mode, "Key was not found on revm storage, address: {:?}, key: {:?}",row.address,row.key); + // state_override.override_storage(revm_db, row.address, row.key, row.value, U256::ZERO); + // } + // } for (address, account) in &revm_state_diffs { if let (Some(info),Some(previous_info)) = (account.info.clone(),account.previous_info.clone()) { if !(info.balance == previous_info.balance && info.nonce == previous_info.nonce && info.code_hash == previous_info.code_hash) { if statediffs_account_hashmap.get(address).is_none() { - panic!("A modified address was not found on tevm state diffs, address: {:?}",address); + panic!("A modified address was not found on tevm state diffs, address: {:?}",address); } } } else { if statediffs_account_hashmap.get(address).is_none() { - panic!("A modified address was not found on tevm state diffs, address: {:?}",address); - } - } - for (key,_) in account.storage.clone() { - if statediffs_accountstate_hashmap.get(&(*address,key)).is_none() { - panic!("A modified storage slot was not found on tevm state diffs, address: {:?}",address); + panic!("A modified address was not found on tevm state diffs, info/previous_info were not Some, address: {:?}",address); } } + // for (key,_) in account.storage.clone() { + // if statediffs_accountstate_hashmap.get(&(*address,key)).is_none() { + // panic!("A modified storage slot was not found on tevm state diffs, address: {:?}, key: {:?}", address, key); + // } + // } } - - return true + + state_override.apply(revm_db); + + true } diff --git a/crates/telos/rpc/src/eth/mod.rs b/crates/telos/rpc/src/eth/mod.rs index fd3227b6a0fa..42e4c8f06aeb 100644 --- a/crates/telos/rpc/src/eth/mod.rs +++ b/crates/telos/rpc/src/eth/mod.rs @@ -26,7 +26,7 @@ use reth_provider::{ BlockIdReader, BlockNumReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StageCheckpointReader, StateProviderFactory, }; -use reth_rpc::eth::{core::EthApiInner, DevSigner}; +use reth_rpc::eth::{core::EthApiInner, DevSigner, EthTxBuilder}; use reth_rpc_eth_api::{ helpers::{ AddDevSigners, EthApiSpec, EthFees, EthSigner, EthState, LoadBlock, LoadFee, LoadState, @@ -104,7 +104,7 @@ where { type Error = TelosEthApiError; type NetworkTypes = AnyNetwork; - type TransactionCompat = (); + type TransactionCompat = EthTxBuilder; } impl EthApiSpec for TelosEthApi