From 31a2b5285c89df7526731c72d2e71756d3534012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ege=20Okan=20=C3=9Cnald=C4=B1?= <35339130+exeokan@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:31:38 +0300 Subject: [PATCH] Enable pending block tag in simulation endpoints (#1303) * treat pending tag same as latest * fix lint * return new sealed block for pending * handle pending tag externally * revert enabling pending in some endpoints * get blockenv instead of sealed block * address review comments * implement tests for eth_call, eth_estimateGas, eth_createAccessList pending blocks * rename tests --------- Co-authored-by: eyusufatik --- crates/ethereum-rpc/src/lib.rs | 9 +- crates/ethereum-rpc/src/trace.rs | 8 +- crates/evm/Cargo.toml | 2 +- crates/evm/src/query.rs | 120 +++++++++++++----- crates/evm/src/tests/call_tests.rs | 38 +++++- .../src/tests/queries/estimate_gas_tests.rs | 61 ++++++++- 6 files changed, 197 insertions(+), 41 deletions(-) diff --git a/crates/ethereum-rpc/src/lib.rs b/crates/ethereum-rpc/src/lib.rs index cf19183cf..960369bbd 100644 --- a/crates/ethereum-rpc/src/lib.rs +++ b/crates/ethereum-rpc/src/lib.rs @@ -421,10 +421,15 @@ fn register_rpc_methods( let mut working_set = WorkingSet::::new(ethereum.storage.clone()); let evm = Evm::::default(); + let latest_block_number: u64 = evm.block_number(&mut working_set)?.saturating_to(); + let block_number = match block_number { BlockNumberOrTag::Number(block_number) => block_number, - BlockNumberOrTag::Latest => evm.block_number(&mut working_set)?.saturating_to(), - _ => return Err(EthApiError::Unsupported("Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber").into()), + BlockNumberOrTag::Latest => latest_block_number, + _ => return Err(EthApiError::Unsupported( + "Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber", + ) + .into()), }; debug_trace_by_block_number(block_number, None, ðereum, &evm, &mut working_set, opts) diff --git a/crates/ethereum-rpc/src/trace.rs b/crates/ethereum-rpc/src/trace.rs index 7ca650931..81738a00a 100644 --- a/crates/ethereum-rpc/src/trace.rs +++ b/crates/ethereum-rpc/src/trace.rs @@ -61,9 +61,11 @@ pub async fn handle_debug_trace_chain latest_block_number, _ => { - pending.reject(EthApiError::Unsupported( - "Earliest, pending, safe and finalized are not supported for traceChain end block", - )).await; + pending + .reject(EthApiError::Unsupported( + "Earliest, pending, safe and finalized are not supported for traceChain end block", + )) + .await; return; } }; diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index e384051b4..d0c41eb7a 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -59,7 +59,7 @@ revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip sov-modules-api = { path = "../sovereign-sdk/module-system/sov-modules-api", features = ["macros"] } sov-prover-storage-manager = { path = "../sovereign-sdk/full-node/sov-prover-storage-manager", features = ["test-utils"] } sov-rollup-interface = { path = "../sovereign-sdk/rollup-interface", features = ["testing"] } -sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner" } +sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner", features = ["native"] } tempfile = { workspace = true } tracing-subscriber = { workspace = true } walkdir = "2.3.3" diff --git a/crates/evm/src/query.rs b/crates/evm/src/query.rs index e64db104c..038c1b88a 100644 --- a/crates/evm/src/query.rs +++ b/crates/evm/src/query.rs @@ -5,6 +5,7 @@ use alloy_consensus::Eip658Value; use alloy_eips::eip2930::AccessListWithGasUsed; use alloy_primitives::Uint; use alloy_rlp::Encodable; +use citrea_primitives::basefee::calculate_next_block_base_fee; use jsonrpsee::core::RpcResult; use reth_primitives::TxKind::{Call, Create}; use reth_primitives::{ @@ -39,7 +40,6 @@ use crate::evm::DbAccount; use crate::handler::{diff_size_send_eth_eoa, TxInfo}; use crate::rpc_helpers::*; use crate::{BloomFilter, Evm, EvmChainConfig, FilterBlockOption, FilterError}; - /// Gas per transaction not creating a contract. pub const MIN_TRANSACTION_GAS: u64 = 21_000u64; @@ -148,7 +148,6 @@ impl Evm { Some(sealed_block) => sealed_block, None => return Ok(None), // if block doesn't exist return null }; - // Build rpc header response let mut header = from_primitive_with_hash(sealed_block.header.clone()); header.total_difficulty = Some(header.difficulty); @@ -511,11 +510,15 @@ impl Evm { }; let (block_env, mut cfg_env) = { - let block = self - .get_sealed_block_by_number(Some(block_number), working_set)? - .ok_or(EthApiError::UnknownBlockNumber)?; - let block_env = BlockEnv::from(&block); - + let block_env = match block_number { + BlockNumberOrTag::Pending => get_pending_block_env(self, working_set), + _ => { + let block = self + .get_sealed_block_by_number(Some(block_number), working_set)? + .ok_or(EthApiError::UnknownBlockNumber)?; + BlockEnv::from(&block) + } + }; // Set evm state to block if needed match block_number { BlockNumberOrTag::Pending | BlockNumberOrTag::Latest => {} @@ -581,11 +584,22 @@ impl Evm { let mut request = request.clone(); let (l1_fee_rate, block_env, mut cfg_env) = { - let block = self - .get_sealed_block_by_number(block_number, working_set)? - .ok_or(EthApiError::UnknownBlockNumber)?; - let block_env = BlockEnv::from(&block); - + let (l1_fee_rate, block_env) = match block_number { + Some(BlockNumberOrTag::Pending) => { + let l1_fee_rate = self + .blocks + .last(&mut working_set.accessory_state()) + .expect("Head block must be set") + .l1_fee_rate; + (l1_fee_rate, get_pending_block_env(self, working_set)) + } + _ => { + let block = self + .get_sealed_block_by_number(block_number, working_set)? + .ok_or(EthApiError::UnknownBlockNumber)?; + (block.l1_fee_rate, BlockEnv::from(&block)) + } + }; match block_number { None | Some(BlockNumberOrTag::Pending | BlockNumberOrTag::Latest) => {} _ => set_state_to_end_of_evm_block(block_env.number, working_set), @@ -597,7 +611,7 @@ impl Evm { .expect("EVM chain config should be set"); let cfg_env = get_cfg_env(&block_env, cfg); - (block.l1_fee_rate, block_env, cfg_env) + (l1_fee_rate, block_env, cfg_env) }; // we want to disable this in eth_createAccessList, since this is common practice used by @@ -673,23 +687,29 @@ impl Evm { working_set: &mut WorkingSet, ) -> RpcResult { let (l1_fee_rate, block_env, cfg_env) = { - let block = self - .get_sealed_block_by_number(block_number, working_set)? - .ok_or(EthApiError::UnknownBlockNumber)?; - let block_env = BlockEnv::from(&block); - - match block_number { - None | Some(BlockNumberOrTag::Pending | BlockNumberOrTag::Latest) => {} - _ => set_state_to_end_of_evm_block(block_env.number, working_set), + let (l1_fee_rate, block_env) = match block_number { + Some(BlockNumberOrTag::Pending) => { + let l1_fee_rate = self + .blocks + .last(&mut working_set.accessory_state()) + .expect("Head block must be set") + .l1_fee_rate; + (l1_fee_rate, get_pending_block_env(self, working_set)) + } + _ => { + let block = self + .get_sealed_block_by_number(block_number, working_set)? + .ok_or(EthApiError::UnknownBlockNumber)?; + (block.l1_fee_rate, BlockEnv::from(&block)) + } }; - let cfg = self .cfg .get(working_set) .expect("EVM chain config should be set"); let cfg_env = get_cfg_env(&block_env, cfg); - (block.l1_fee_rate, block_env, cfg_env) + (l1_fee_rate, block_env, cfg_env) }; self.estimate_gas_with_env(request, l1_fee_rate, block_env, cfg_env, working_set) @@ -1364,13 +1384,15 @@ impl Evm { block_id: &BlockNumberOrTag, working_set: &mut WorkingSet, ) -> Result { + let latest_block_number = self + .blocks + .last(&mut working_set.accessory_state()) + .map(|block| block.header.number) + .expect("Head block must be set"); match block_id { BlockNumberOrTag::Earliest => Ok(0), - BlockNumberOrTag::Latest => Ok(self - .blocks - .last(&mut working_set.accessory_state()) - .map(|block| block.header.number) - .expect("Head block must be set")), + BlockNumberOrTag::Latest => Ok(latest_block_number), + BlockNumberOrTag::Pending => Err(EthApiError::UnknownBlockNumber), BlockNumberOrTag::Number(block_number) => { if *block_number < self.blocks.len(&mut working_set.accessory_state()) as u64 { Ok(*block_number) @@ -1379,7 +1401,7 @@ impl Evm { } } _ => Err(EthApiError::InvalidParams( - "Please provide a number or earliest/latest tag".to_string(), + "Please provide a number or earliest/latest/pending tag".to_string(), )), } } @@ -1673,3 +1695,43 @@ fn set_state_to_end_of_evm_block( // so every block is offset by 1 working_set.set_archival_version(block_number + 1); } + +/// Creates the next blocks `BlockEnv` based on the latest block +/// Also updates `Evm::latest_block_hashes` with the new block hash +fn get_pending_block_env( + evm: &Evm, + working_set: &mut WorkingSet, +) -> BlockEnv { + let latest_block = evm + .blocks + .last(&mut working_set.accessory_state()) + .expect("Head block must be set"); + + evm.latest_block_hashes.set( + &U256::from(latest_block.header.number), + &latest_block.header.hash(), + working_set, + ); + + let cfg = evm + .cfg + .get(working_set) + .expect("EVM chain config should be set"); + + let mut block_env = BlockEnv::from(&latest_block); + block_env.number += 1; + block_env.basefee = calculate_next_block_base_fee( + latest_block.header.gas_used as u128, + latest_block.header.gas_limit as u128, + latest_block.header.base_fee_per_gas, + cfg.base_fee_params, + ) + .unwrap_or_default(); + + if block_env.number > 256 { + evm.latest_block_hashes + .remove(&U256::from(block_env.number - 257), working_set); + } + + block_env +} diff --git a/crates/evm/src/tests/call_tests.rs b/crates/evm/src/tests/call_tests.rs index 19693572a..0e87bfabd 100644 --- a/crates/evm/src/tests/call_tests.rs +++ b/crates/evm/src/tests/call_tests.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use alloy_eips::BlockId; use reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT; use reth_primitives::{address, b256, Address, BlockNumberOrTag, Bytes, Log, LogData, TxKind, U64}; use reth_rpc_types::request::{TransactionInput, TransactionRequest}; @@ -786,10 +787,7 @@ fn test_block_hash_in_evm() { for i in 0..=1000 { request.input.input = Some(BlockHashContract::default().get_block_hash(i).into()); let resp = evm.get_call(request.clone(), None, None, None, &mut working_set); - if !(260..=515).contains(&i) { - // Should be 0, there is more than 256 blocks between the last block and the block number - assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]); - } else { + if (260..=515).contains(&i) { // Should be equal to the hash in accessory state let block = evm .blocks @@ -798,8 +796,40 @@ fn test_block_hash_in_evm() { resp.unwrap().to_vec(), block.unwrap().header.hash().to_vec() ); + } else { + // Should be 0, there is more than 256 blocks between the last block and the block number + assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]); } } + + // last produced block is 516, eth_call with pending should return latest block's hash + let latest_block = evm.blocks.get(516, &mut working_set.accessory_state()); + request.input.input = Some(BlockHashContract::default().get_block_hash(516).into()); + + let resp = evm.get_call( + request.clone(), + Some(BlockId::pending()), + None, + None, + &mut working_set, + ); + + assert_eq!( + resp.unwrap().to_vec(), + latest_block.unwrap().header.hash().to_vec() + ); + + // but not 260's hash + request.input.input = Some(BlockHashContract::default().get_block_hash(260).into()); + let resp = evm.get_call( + request.clone(), + Some(BlockId::pending()), + None, + None, + &mut working_set, + ); + + assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]); } #[test] diff --git a/crates/evm/src/tests/queries/estimate_gas_tests.rs b/crates/evm/src/tests/queries/estimate_gas_tests.rs index eaf062bed..e461d1bdd 100644 --- a/crates/evm/src/tests/queries/estimate_gas_tests.rs +++ b/crates/evm/src/tests/queries/estimate_gas_tests.rs @@ -20,7 +20,7 @@ use crate::{EstimatedDiffSize, Evm}; type C = DefaultContext; #[test] -fn payable_contract_value_test() { +fn test_payable_contract_value() { let (evm, mut working_set, signer) = init_evm_single_block(); let tx_req = TransactionRequest { @@ -338,7 +338,7 @@ fn test_access_list() { } #[test] -fn estimate_gas_with_varied_inputs_test() { +fn test_estimate_gas_with_varied_inputs() { let (evm, mut working_set, signer, _) = init_evm(); let simple_call_data = 0; @@ -363,6 +363,63 @@ fn estimate_gas_with_varied_inputs_test() { ); } +#[test] +fn test_pending_env() { + let (evm, mut working_set, signer) = init_evm_single_block(); + + let tx_req = TransactionRequest { + from: Some(signer.address()), + to: Some(TxKind::Call(address!( + "819c5497b157177315e1204f52e588b393771719" + ))), // Address of the payable contract. + gas: Some(100000), + gas_price: Some(100000000), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + value: Some(U256::from(3100000)), + input: TransactionInput { + input: None, + data: None, + }, + nonce: Some(1u64), + chain_id: Some(1u64), + access_list: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + transaction_type: None, + sidecar: None, + }; + + let result = evm + .eth_estimate_gas( + tx_req.clone(), + Some(BlockNumberOrTag::Latest), + &mut working_set, + ) + .unwrap(); + + let result_pending = evm.eth_estimate_gas( + tx_req.clone(), + Some(BlockNumberOrTag::Pending), + &mut working_set, + ); + assert_eq!(result_pending.unwrap(), result); + + let result = evm + .create_access_list(tx_req.clone(), None, &mut working_set) + .unwrap(); + + let result_pending = evm + .create_access_list( + tx_req.clone(), + Some(BlockNumberOrTag::Pending), + &mut working_set, + ) + .unwrap(); + + assert_eq!(result_pending, result); +} + fn test_estimate_gas_with_input( evm: &Evm, working_set: &mut WorkingSet,