diff --git a/Cargo.lock b/Cargo.lock index 2b136c0eac..78ec3c7fb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1241,6 +1241,7 @@ dependencies = [ "cfx-math", "cfx-parameters", "cfx-parity-trace-types", + "cfx-rpc-eth-types", "cfx-statedb", "cfx-storage", "cfx-types", diff --git a/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs index 0da8ddae5c..9f2e34d34e 100644 --- a/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs +++ b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs @@ -62,7 +62,7 @@ use cfx_execute_helper::estimation::{ EstimateExt, EstimateRequest, EstimationContext, }; use cfx_executor::{ - executive::ExecutionOutcome, + executive::{ExecutionOutcome, ExecutiveContext}, machine::Machine, state::{ distribute_pos_interest, update_pos_status, CleanupMode, State, @@ -73,6 +73,7 @@ use cfx_vm_types::{Env, Spec}; use geth_tracer::GethTraceWithHash; use alloy_rpc_types_trace::geth::GethDebugTracingOptions; +use cfx_rpc_eth_types::EvmOverrides; use self::epoch_execution::{GethTask, VirtualCall}; @@ -636,9 +637,15 @@ impl ConsensusExecutor { pub fn call_virtual( &self, tx: &SignedTransaction, epoch_id: &H256, epoch_size: usize, - request: EstimateRequest, + request: EstimateRequest, evm_overrides: EvmOverrides, ) -> CoreResult<(ExecutionOutcome, EstimateExt)> { - self.handler.call_virtual(tx, epoch_id, epoch_size, request) + self.handler.call_virtual( + tx, + epoch_id, + epoch_size, + request, + evm_overrides, + ) } pub fn collect_blocks_geth_trace( @@ -1578,7 +1585,7 @@ impl ConsensusExecutionHandler { pub fn call_virtual( &self, tx: &SignedTransaction, epoch_id: &H256, epoch_size: usize, - request: EstimateRequest, + request: EstimateRequest, evm_overrides: EvmOverrides, ) -> CoreResult<(ExecutionOutcome, EstimateExt)> { let best_block_header = self.data_man.block_header_by_hash(epoch_id); if best_block_header.is_none() { @@ -1617,11 +1624,20 @@ impl ConsensusExecutionHandler { Space::Native => None, Space::Ethereum => Some(Space::Ethereum), }; - let mut state = self.get_state_by_epoch_id_and_space( + let statedb = self.get_statedb_by_epoch_id_and_space( epoch_id, best_block_header.height(), state_space, )?; + let mut state = if evm_overrides.has_state() { + State::new_with_override( + statedb, + &evm_overrides.state.as_ref().unwrap(), + tx.space(), + )? + } else { + State::new(statedb)? + }; let time_stamp = best_block_header.timestamp(); @@ -1637,7 +1653,7 @@ impl ConsensusExecutionHandler { let burnt_gas_price = base_gas_price.map_all(|x| state.burnt_gas_price(x)); - let env = Env { + let mut env = Env { chain_id: self.machine.params().chain_id_map(block_height), number: start_block_number, author: miner, @@ -1655,6 +1671,12 @@ impl ConsensusExecutionHandler { base_gas_price, burnt_gas_price, }; + if evm_overrides.has_block() { + ExecutiveContext::apply_env_overrides( + &mut env, + evm_overrides.block.unwrap(), + ); + } let spec = self.machine.spec(env.number, env.epoch_height); let mut ex = EstimationContext::new( &mut state, @@ -1706,6 +1728,19 @@ impl ConsensusExecutionHandler { fn get_state_by_epoch_id_and_space( &self, epoch_id: &H256, epoch_height: u64, state_space: Option, ) -> DbResult { + let state_db = self.get_statedb_by_epoch_id_and_space( + epoch_id, + epoch_height, + state_space, + )?; + let state = State::new(state_db)?; + + Ok(state) + } + + fn get_statedb_by_epoch_id_and_space( + &self, epoch_id: &H256, epoch_height: u64, state_space: Option, + ) -> DbResult { // Keep the lock until we get the desired State, otherwise the State may // expire. let state_availability_boundary = @@ -1734,11 +1769,10 @@ impl ConsensusExecutionHandler { )? .ok_or("state deleted")?, ); - let state = State::new(state_db)?; drop(state_availability_boundary); - Ok(state) + Ok(state_db) } } diff --git a/crates/cfxcore/core/src/consensus/mod.rs b/crates/cfxcore/core/src/consensus/mod.rs index f0b5b33a1e..7abde25379 100644 --- a/crates/cfxcore/core/src/consensus/mod.rs +++ b/crates/cfxcore/core/src/consensus/mod.rs @@ -49,6 +49,7 @@ use cfx_execute_helper::{ use cfx_executor::{ executive::ExecutionOutcome, spec::CommonParams, state::State, }; +use cfx_rpc_eth_types::EvmOverrides; use geth_tracer::GethTraceWithHash; use alloy_rpc_types_trace::geth::GethDebugTracingOptions; @@ -1429,7 +1430,7 @@ impl ConsensusGraph { pub fn call_virtual( &self, tx: &SignedTransaction, epoch: EpochNumber, - request: EstimateRequest, + request: EstimateRequest, evm_overrides: EvmOverrides, ) -> CoreResult<(ExecutionOutcome, EstimateExt)> { // only allow to call against stated epoch self.validate_stated_epoch(&epoch)?; @@ -1440,8 +1441,13 @@ impl ConsensusGraph { } else { bail!("cannot get block hashes in the specified epoch, maybe it does not exist?"); }; - self.executor - .call_virtual(tx, &epoch_id, epoch_size, request) + self.executor.call_virtual( + tx, + &epoch_id, + epoch_size, + request, + evm_overrides, + ) } pub fn collect_epoch_geth_trace( diff --git a/crates/cfxcore/execute-helper/Cargo.toml b/crates/cfxcore/execute-helper/Cargo.toml index 8ee5b9a374..30dcc0597a 100644 --- a/crates/cfxcore/execute-helper/Cargo.toml +++ b/crates/cfxcore/execute-helper/Cargo.toml @@ -30,7 +30,7 @@ serde_json = { workspace = true, default-features = false, features = [ solidity-abi = { workspace = true } strum_macros = { workspace = true } pow-types = { workspace = true } -typemap = { package = "typemap-ors", version = "1.0"} +typemap = { package = "typemap-ors", version = "1.0" } alloy-primitives = { workspace = true } alloy-sol-types = "0.7.1" diff --git a/crates/cfxcore/executor/Cargo.toml b/crates/cfxcore/executor/Cargo.toml index c07adcf8ed..42442118c7 100644 --- a/crates/cfxcore/executor/Cargo.toml +++ b/crates/cfxcore/executor/Cargo.toml @@ -54,6 +54,7 @@ c-kzg = { version = "1.0.2", default-features = false} once_cell = { workspace = true } rayon = { workspace = true } cfx-parity-trace-types = { workspace = true } +cfx-rpc-eth-types = { workspace = true } [dev-dependencies] cfx-statedb = { workspace = true, features = ["testonly_code"]} diff --git a/crates/cfxcore/executor/src/executive/fresh_executive.rs b/crates/cfxcore/executor/src/executive/fresh_executive.rs index a411ed78c2..fb5b792500 100644 --- a/crates/cfxcore/executor/src/executive/fresh_executive.rs +++ b/crates/cfxcore/executor/src/executive/fresh_executive.rs @@ -63,7 +63,9 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { context: ExecutiveContext<'a>, tx: &'a SignedTransaction, options: TransactOptions, ) -> Self { - let TransactOptions { observer, settings } = options; + let TransactOptions { + observer, settings, .. + } = options; let base_gas = gas_required_for( tx.action() == &Action::Create, &tx.data(), diff --git a/crates/cfxcore/executor/src/executive/mod.rs b/crates/cfxcore/executor/src/executive/mod.rs index 7f1df81f6f..2b257ed139 100644 --- a/crates/cfxcore/executor/src/executive/mod.rs +++ b/crates/cfxcore/executor/src/executive/mod.rs @@ -9,10 +9,11 @@ mod pre_checked_executive; mod tests; pub mod transact_options; +use cfx_rpc_eth_types::BlockOverrides; use cfx_statedb::Result as DbResult; use cfx_types::{ - address_util::AddressUtil, AddressSpaceUtil, AddressWithSpace, Space, H256, - U256, + address_util::AddressUtil, AddressSpaceUtil, AddressWithSpace, Space, + SpaceMap, H256, U256, }; use cfx_vm_types::{CreateContractAddress, Env, Spec}; use primitives::{AccessList, SignedTransaction}; @@ -61,6 +62,36 @@ impl<'a> ExecutiveContext<'a> { Err(execution_outcome) => execution_outcome, }) } + + pub fn apply_env_overrides( + env: &mut Env, block_override: Box, + ) { + if let Some(number) = block_override.number { + env.number = number.as_u64(); + } + if let Some(difficulty) = block_override.difficulty { + env.difficulty = difficulty; + } + if let Some(timestamp) = block_override.time { + env.timestamp = timestamp; + } + if let Some(gas_limit) = block_override.gas_limit { + env.gas_limit = U256::from(gas_limit); + } + if let Some(author) = block_override.coinbase { + env.author = author; + } + if let Some(_random) = block_override.random { + // conflux doesn't have random(prevRandao) + } + if let Some(base_fee) = block_override.base_fee { + env.base_gas_price = SpaceMap::new(base_fee, base_fee); // use same base_fee for both spaces + } + + if let Some(_block_hash) = &block_override.block_hash { + // TODO impl + } + } } pub fn gas_required_for( diff --git a/crates/cfxcore/executor/src/state/overlay_account/account_entry.rs b/crates/cfxcore/executor/src/state/overlay_account/account_entry.rs index dade5e6516..f4621f1dc9 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/account_entry.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/account_entry.rs @@ -17,6 +17,7 @@ pub enum AccountEntry { } use cfx_parameters::genesis::GENESIS_ACCOUNT_ADDRESS; +use cfx_rpc_eth_types::AccountOverride; use cfx_types::AddressWithSpace; use primitives::Account; use AccountEntry::*; @@ -37,6 +38,21 @@ impl AccountEntry { } } + pub fn from_loaded_with_override( + address: &AddressWithSpace, account: Option, + acc_overrides: &AccountOverride, + ) -> Self { + let acc = account.unwrap_or_else(|| Account::new_empty(address)); + Cached( + OverlayAccount::from_loaded_with_override( + address, + acc, + acc_overrides, + ), + true, + ) + } + pub fn is_dirty(&self) -> bool { matches!(self, Cached(_, true)) } pub fn is_db_absent(&self) -> bool { matches!(self, DbAbsent) } diff --git a/crates/cfxcore/executor/src/state/overlay_account/factory.rs b/crates/cfxcore/executor/src/state/overlay_account/factory.rs index a915074687..c23822d91c 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/factory.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/factory.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use cfx_rpc_eth_types::AccountOverride; use cfx_types::{Address, AddressSpaceUtil, AddressWithSpace, Space, U256}; use keccak_hash::KECCAK_EMPTY; use parking_lot::RwLock; @@ -30,6 +31,7 @@ impl Default for OverlayAccount { code: None, is_newly_created_contract: false, pending_db_clear: false, + storage_overrided: false, } } } @@ -53,6 +55,48 @@ impl OverlayAccount { overlay_account } + pub fn from_loaded_with_override( + address: &AddressWithSpace, account: Account, + acc_overrides: &AccountOverride, + ) -> Self { + let mut acc = Self::from_loaded(address, account); + + if let Some(balance) = acc_overrides.balance { + let curr_balance = *acc.balance(); + if curr_balance > U256::zero() { + acc.sub_balance(&curr_balance); + } + acc.add_balance(&balance); + } + + if let Some(nonce) = acc_overrides.nonce { + acc.set_nonce(&U256::from(nonce)); + } + + if let Some(code) = acc_overrides.code.as_ref() { + acc.init_code(code.clone(), address.address); + } + + match ( + acc_overrides.state.as_ref(), + acc_overrides.state_diff.as_ref(), + ) { + (Some(state_override), None) => { + acc.override_storage_read_cache(state_override, true); + } + (None, Some(diff)) => { + acc.override_storage_read_cache(diff, false); + } + (_, _) => {} + } + + if acc_overrides.move_precompile_to.is_some() { + // TODO: impl move precompile to logic + } + + acc + } + /// Create an OverlayAccount of basic account when the account doesn't exist /// before. pub fn new_basic(address: &AddressWithSpace, balance: U256) -> Self { @@ -157,6 +201,7 @@ impl OverlayAccount { WriteCheckpointLayer::new_empty(checkpoint_id), ), storage_layout_change: self.storage_layout_change.clone(), + storage_overrided: self.storage_overrided, } } @@ -188,6 +233,7 @@ impl OverlayAccount { )), transient_storage_checkpoint: None, storage_layout_change: self.storage_layout_change.clone(), + storage_overrided: self.storage_overrided, } } } diff --git a/crates/cfxcore/executor/src/state/overlay_account/mod.rs b/crates/cfxcore/executor/src/state/overlay_account/mod.rs index 7b4556cbe8..550945fabf 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/mod.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/mod.rs @@ -156,6 +156,12 @@ pub struct OverlayAccount { /// cleared later. It will be set when such a contract has been killed /// since last commit. pending_db_clear: bool, + + /// Indicates whether the storage cache entries of this account have been + /// overrided by the passed-in storage entries. + /// When this flag is set, the storage entries will only be read from the + /// cache + storage_overrided: bool, } impl OverlayAccount { @@ -183,6 +189,7 @@ impl OverlayAccount { && self.address.address.is_builtin_address(); (self.is_newly_created_contract && !builtin_address) || self.pending_db_clear + || self.storage_overrided } } diff --git a/crates/cfxcore/executor/src/state/overlay_account/storage.rs b/crates/cfxcore/executor/src/state/overlay_account/storage.rs index 07b14fb68b..adf84ccefb 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/storage.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/storage.rs @@ -7,12 +7,12 @@ use cfx_parameters::{ staking::COLLATERAL_UNITS_PER_STORAGE_KEY, }; use cfx_statedb::{Result as DbResult, StateDbExt, StateDbGeneric}; -use cfx_types::{Address, Space, U256}; +use cfx_types::{Address, Space, H256, U256}; use primitives::{ SkipInputCheck, StorageKey, StorageKeyWithSpace, StorageValue, }; -use std::collections::hash_map::Entry::*; +use std::collections::{hash_map::Entry::*, HashMap}; use super::OverlayAccount; @@ -196,8 +196,8 @@ impl OverlayAccount { pub fn storage_entry_at( &self, db: &StateDbGeneric, key: &[u8], ) -> DbResult { - Ok(if let Some(owner) = self.cached_entry_at(key) { - owner + Ok(if let Some(value) = self.cached_entry_at(key) { + value } else if self.fresh_storage() { StorageValue::default() } else { @@ -240,6 +240,29 @@ impl OverlayAccount { && self.address.address != SYSTEM_STORAGE_ADDRESS } + pub fn override_storage_read_cache( + &mut self, account_storage: &HashMap, + complete_override: bool, + ) { + if complete_override { + self.storage_read_cache.write().clear(); + self.storage_overrided = true; + } + assert!(self.storage_write_checkpoint.is_none()); + for (key, value) in account_storage { + let key = key.as_bytes().to_vec(); + let value = U256::from_big_endian(value.as_bytes()); + // self.update_storage_read_cache(key, value); + let owner = if self.address.space == Space::Native { + Some(self.address.address) + } else { + None + }; + let storage_value = StorageValue { owner, value }; + self.storage_read_cache.write().insert(key, storage_value); + } + } + pub fn change_storage_value( &mut self, db: &StateDbGeneric, key: &[u8], value: U256, ) -> DbResult<()> { diff --git a/crates/cfxcore/executor/src/state/state_object/mod.rs b/crates/cfxcore/executor/src/state/state_object/mod.rs index 3d0cd6c571..be05034840 100644 --- a/crates/cfxcore/executor/src/state/state_object/mod.rs +++ b/crates/cfxcore/executor/src/state/state_object/mod.rs @@ -43,6 +43,8 @@ mod storage_entry; mod reward; +mod state_override; + #[cfg(test)] mod tests; @@ -64,8 +66,9 @@ use super::{ overlay_account::{AccountEntry, OverlayAccount, RequireFields}, }; use crate::substate::Substate; +use cfx_rpc_eth_types::StateOverride; use cfx_statedb::{Result as DbResult, StateDbExt, StateDbGeneric as StateDb}; -use cfx_types::AddressWithSpace; +use cfx_types::{AddressWithSpace, Space}; use parking_lot::RwLock; use std::collections::{BTreeSet, HashMap}; @@ -109,6 +112,14 @@ impl State { }) } + pub fn new_with_override( + db: StateDb, state_override: &StateOverride, space: Space, + ) -> DbResult { + let mut state = Self::new(db)?; + state.apply_override(state_override, space)?; + Ok(state) + } + pub fn prefetch_accounts( &self, addresses: BTreeSet, pool: &rayon::ThreadPool, ) -> DbResult<()> { diff --git a/crates/cfxcore/executor/src/state/state_object/state_override.rs b/crates/cfxcore/executor/src/state/state_object/state_override.rs new file mode 100644 index 0000000000..99d9e718ab --- /dev/null +++ b/crates/cfxcore/executor/src/state/state_object/state_override.rs @@ -0,0 +1,33 @@ +use super::State; +use crate::state::overlay_account::AccountEntry; +use cfx_rpc_eth_types::StateOverride; +use cfx_statedb::{Result as DbResult, StateDbExt}; +use cfx_types::{AddressWithSpace, Space}; + +/// Apply the state override to the state object, only used for rpc call eg +/// eth_call, eth_estimateGas etc. +impl State { + pub(super) fn apply_override( + &mut self, state_override: &StateOverride, space: Space, + ) -> DbResult<()> { + assert!(self.checkpoints.read().is_empty()); + + let mut cache = self.cache.write(); + for (address, account) in state_override.iter() { + let addr_with_space = AddressWithSpace { + address: address.to_owned(), + space, + }; + + let loaded_account = self.db.get_account(&addr_with_space)?; + let account_entry = AccountEntry::from_loaded_with_override( + &addr_with_space, + loaded_account, + account, + ); + + cache.insert(addr_with_space, account_entry); + } + Ok(()) + } +} diff --git a/crates/client/src/rpc/impls/cfx/cfx_handler.rs b/crates/client/src/rpc/impls/cfx/cfx_handler.rs index 7851187701..764a97ea3f 100644 --- a/crates/client/src/rpc/impls/cfx/cfx_handler.rs +++ b/crates/client/src/rpc/impls/cfx/cfx_handler.rs @@ -1504,7 +1504,12 @@ impl RpcImpl { )?; trace!("call tx {:?}", signed_tx); - consensus_graph.call_virtual(&signed_tx, epoch.into(), estimate_request) + consensus_graph.call_virtual( + &signed_tx, + epoch.into(), + estimate_request, + Default::default(), + ) } fn current_sync_phase(&self) -> CoreResult { diff --git a/crates/client/src/rpc/impls/eth/eth_handler.rs b/crates/client/src/rpc/impls/eth/eth_handler.rs index a87d438467..c915e1cdd0 100644 --- a/crates/client/src/rpc/impls/eth/eth_handler.rs +++ b/crates/client/src/rpc/impls/eth/eth_handler.rs @@ -9,8 +9,8 @@ use crate::rpc::{ types::{ eth::{ AccountPendingTransactions, Block as RpcBlock, BlockNumber, - EthRpcLogFilter, Log, Receipt, SyncStatus, Transaction, - TransactionRequest, + BlockOverrides, EthRpcLogFilter, EvmOverrides, Log, Receipt, + StateOverride, SyncStatus, Transaction, TransactionRequest, }, Bytes, FeeHistory, Index, U64 as HexU64, }, @@ -263,14 +263,21 @@ impl Eth for EthHandler { fn call( &self, request: TransactionRequest, block_number_or_hash: Option, + state_overrides: Option, + block_overrides: Option>, ) -> RpcResult { debug!( "RPC Request: eth_call(request={:?}, block_num={:?})", request, block_number_or_hash ); - let (execution, _estimation) = - self.inner.exec_transaction(request, block_number_or_hash)?; + let evm_overrides = EvmOverrides::new(state_overrides, block_overrides); + + let (execution, _estimation) = self.inner.exec_transaction( + request, + block_number_or_hash, + evm_overrides, + )?; Ok(execution.output.into()) } @@ -278,13 +285,18 @@ impl Eth for EthHandler { fn estimate_gas( &self, request: TransactionRequest, block_number_or_hash: Option, + state_override: Option, ) -> RpcResult { debug!( "RPC Request: eth_estimateGas(request={:?}, block_num={:?})", request, block_number_or_hash ); - let (_, estimated_gas) = - self.inner.exec_transaction(request, block_number_or_hash)?; + let evm_overrides = EvmOverrides::new(state_override, None); + let (_, estimated_gas) = self.inner.exec_transaction( + request, + block_number_or_hash, + evm_overrides, + )?; Ok(estimated_gas) } diff --git a/crates/client/src/rpc/traits/eth_space/eth.rs b/crates/client/src/rpc/traits/eth_space/eth.rs index 65701b7832..e4d21fa4de 100644 --- a/crates/client/src/rpc/traits/eth_space/eth.rs +++ b/crates/client/src/rpc/traits/eth_space/eth.rs @@ -26,8 +26,9 @@ use jsonrpc_derive::rpc; use crate::rpc::types::{ eth::{ - AccountPendingTransactions, Block, BlockNumber, EthRpcLogFilter, Log, - Receipt, SyncStatus, Transaction, TransactionRequest, + AccountPendingTransactions, Block, BlockNumber, BlockOverrides, + EthRpcLogFilter, Log, Receipt, StateOverride, SyncStatus, Transaction, + TransactionRequest, }, Bytes, FeeHistory, Index, }; @@ -163,12 +164,15 @@ pub trait Eth { #[rpc(name = "eth_call")] fn call( &self, transaction: TransactionRequest, block: Option, + state_overrides: Option, + block_overrides: Option>, ) -> Result; /// Estimate gas needed for execution of given contract. #[rpc(name = "eth_estimateGas")] fn estimate_gas( &self, transaction: TransactionRequest, block: Option, + state_override: Option, ) -> Result; /// Get transaction by its hash. diff --git a/crates/client/src/rpc/types/eth/mod.rs b/crates/client/src/rpc/types/eth/mod.rs index 4488166769..6b012aad07 100644 --- a/crates/client/src/rpc/types/eth/mod.rs +++ b/crates/client/src/rpc/types/eth/mod.rs @@ -4,8 +4,9 @@ pub use cfx_rpc_eth_types::{ eth_pubsub, trace_filter::TraceFilter, AccountPendingTransactions, Block, - BlockNumber, EthRpcLogFilter, FilterChanges, Header, Log, Receipt, - SyncInfo, SyncStatus, Transaction, TransactionRequest, + BlockNumber, BlockOverrides, EthRpcLogFilter, EvmOverrides, FilterChanges, + Header, Log, Receipt, StateOverride, SyncInfo, SyncStatus, Transaction, + TransactionRequest, }; pub use cfx_rpc_cfx_types::trace_eth::{LocalizedTrace, Res}; diff --git a/crates/rpc/rpc-eth-api/src/eth.rs b/crates/rpc/rpc-eth-api/src/eth.rs index 98023e443b..48596abd4c 100644 --- a/crates/rpc/rpc-eth-api/src/eth.rs +++ b/crates/rpc/rpc-eth-api/src/eth.rs @@ -1,6 +1,7 @@ use cfx_rpc_eth_types::{ - Block, BlockNumber as BlockId, EthRpcLogFilter as Filter, FeeHistory, - Header, Log, Receipt, SyncStatus, Transaction, TransactionRequest, + Block, BlockNumber as BlockId, BlockOverrides, EthRpcLogFilter as Filter, + FeeHistory, Header, Log, Receipt, StateOverride, SyncStatus, Transaction, + TransactionRequest, }; use cfx_rpc_primitives::{Bytes, Index}; use cfx_types::{Address, H256, H64, U256, U64}; @@ -199,11 +200,9 @@ pub trait EthApi { /// on the block chain. #[method(name = "call")] async fn call( - &self, - request: TransactionRequest, - block_number: Option, - // state_overrides: Option, - // block_overrides: Option>, + &self, request: TransactionRequest, block_number: Option, + state_overrides: Option, + block_overrides: Option>, ) -> RpcResult; /// Simulate arbitrary number of transactions at an arbitrary blockchain @@ -243,10 +242,8 @@ pub trait EthApi { /// the transaction to complete. #[method(name = "estimateGas")] async fn estimate_gas( - &self, - request: TransactionRequest, - block_number: Option, - // state_override: Option, + &self, request: TransactionRequest, block_number: Option, + state_override: Option, ) -> RpcResult; /// Returns the current price per gas in wei. diff --git a/crates/rpc/rpc-eth-impl/src/eth.rs b/crates/rpc/rpc-eth-impl/src/eth.rs index 5dfe181faf..e3f9ff1324 100644 --- a/crates/rpc/rpc-eth-impl/src/eth.rs +++ b/crates/rpc/rpc-eth-impl/src/eth.rs @@ -10,9 +10,9 @@ use cfx_rpc_cfx_types::{ }; use cfx_rpc_eth_api::EthApiServer; use cfx_rpc_eth_types::{ - Block, BlockNumber as BlockId, EthRpcLogFilter, EthRpcLogFilter as Filter, - FeeHistory, Header, Log, Receipt, SyncInfo, SyncStatus, Transaction, - TransactionRequest, + Block, BlockNumber as BlockId, BlockOverrides, EthRpcLogFilter, + EthRpcLogFilter as Filter, EvmOverrides, FeeHistory, Header, Log, Receipt, + StateOverride, SyncInfo, SyncStatus, Transaction, TransactionRequest, }; use cfx_rpc_primitives::{Bytes, Index, U64 as HexU64}; use cfx_rpc_utils::error::{ @@ -99,7 +99,7 @@ impl EthApi { pub fn exec_transaction( &self, mut request: TransactionRequest, - block_number_or_hash: Option, + block_number_or_hash: Option, evm_overrides: EvmOverrides, ) -> CoreResult<(Executed, U256)> { let consensus_graph = self.consensus_graph(); @@ -125,6 +125,19 @@ impl EthApi { } } + if evm_overrides.has_state() { + let state_override = evm_overrides.state.as_ref().unwrap(); + let invalid_item = state_override.iter().any(|(_, item)| { + item.state.is_some() && item.state_diff.is_some() + }); + if invalid_item { + return Err(invalid_params_msg( + "Cannot set both state and stateDiff in stateOverride", + ) + .into()); + } + } + let epoch = match block_number_or_hash.unwrap_or_default() { BlockNumber::Hash { hash, .. } => { match consensus_graph.get_block_epoch_number(&hash) { @@ -167,6 +180,7 @@ impl EthApi { &signed_tx, epoch, estimate_request, + evm_overrides, )?; let executed = match execution_outcome { @@ -1236,14 +1250,13 @@ impl EthApiServer for EthApi { /// Executes a new message call immediately without creating a transaction /// on the block chain. async fn call( - &self, - request: TransactionRequest, - block_number: Option, - // state_overrides: Option, - // block_overrides: Option>, + &self, request: TransactionRequest, block_number: Option, + state_overrides: Option, + block_overrides: Option>, ) -> RpcResult { + let evm_overrides = EvmOverrides::new(state_overrides, block_overrides); let (execution, _estimation) = - self.exec_transaction(request, block_number)?; + self.exec_transaction(request, block_number, evm_overrides)?; Ok(execution.output.into()) } @@ -1282,13 +1295,12 @@ impl EthApiServer for EthApi { /// Generates and returns an estimate of how much gas is necessary to allow /// the transaction to complete. async fn estimate_gas( - &self, - request: TransactionRequest, - block_number: Option, - // state_override: Option, + &self, request: TransactionRequest, block_number: Option, + state_override: Option, ) -> RpcResult { + let evm_overrides = EvmOverrides::new(state_override, None); let (_, estimated_gas) = - self.exec_transaction(request, block_number)?; + self.exec_transaction(request, block_number, evm_overrides)?; Ok(estimated_gas) }