refactor(rpc): extract prepare_call_env logic (#2012)

This commit is contained in:
Matthias Seitz
2023-03-28 18:33:56 +02:00
committed by GitHub
parent b55b2d6182
commit cf2a2dbf97
3 changed files with 130 additions and 129 deletions

View File

@@ -2,33 +2,27 @@
use crate::{
eth::{
error::{EthApiError, EthResult, InvalidTransactionError, RevertError},
error::{EthResult, InvalidTransactionError, RevertError},
revm_utils::{
build_call_evm_env, cap_tx_gas_limit_with_caller_allowance, get_precompiles, inspect,
transact,
prepare_call_env, transact,
},
EthTransactions,
},
EthApi,
};
use ethers_core::utils::get_contract_address;
use reth_primitives::{AccessList, Address, BlockId, BlockNumberOrTag, U256};
use reth_primitives::{AccessList, BlockId, BlockNumberOrTag, U256};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory};
use reth_revm::{
access_list::AccessListInspector,
database::{State, SubState},
};
use reth_rpc_types::{
state::{AccountOverride, StateOverride},
CallRequest,
};
use reth_rpc_types::{state::StateOverride, CallRequest};
use reth_transaction_pool::TransactionPool;
use revm::{
db::{CacheDB, DatabaseRef},
primitives::{
BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo,
},
Database,
db::DatabaseRef,
primitives::{BlockEnv, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo},
};
use tracing::trace;
@@ -43,7 +37,7 @@ where
Network: Send + Sync + 'static,
{
/// Executes the call request at the given [BlockId]
pub(crate) async fn execute_call_at(
pub(crate) async fn transact_call_at(
&self,
request: CallRequest,
at: BlockId,
@@ -51,53 +45,9 @@ where
) -> EthResult<(ResultAndState, Env)> {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
let state = self.state_at(at)?;
self.call_with(cfg, block_env, request, state, state_overrides)
}
/// Executes the call request using the given environment against the state provider
///
/// Does not commit any changes to the database
fn call_with<S>(
&self,
mut cfg: CfgEnv,
block: BlockEnv,
request: CallRequest,
state: S,
state_overrides: Option<StateOverride>,
) -> EthResult<(ResultAndState, Env)>
where
S: StateProvider,
{
// we want to disable this in eth_call, since this is common practice used by other node
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
cfg.disable_block_gas_limit = true;
// Disabled because eth_call is sometimes used with eoa senders
// See <htps://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
let request_gas = request.gas;
let mut env = build_call_evm_env(cfg, block, request)?;
let mut db = SubState::new(State::new(state));
// apply state overrides
if let Some(state_overrides) = state_overrides {
apply_state_overrides(state_overrides, &mut db)?;
}
// The basefee should be ignored for eth_call
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
env.block.basefee = U256::ZERO;
if request_gas.is_none() && env.tx.gas_price > U256::ZERO {
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap");
// no gas limit was provided in the request, so we need to cap the request's gas limit
cap_tx_gas_limit_with_caller_allowance(&mut db, &mut env.tx)?;
}
let env = prepare_call_env(cfg, block_env, request, &mut db, state_overrides)?;
trace!(target: "rpc::eth::call", ?env, "Executing call");
transact(&mut db, env)
}
@@ -335,72 +285,3 @@ where
Ok(inspector.into_access_list())
}
}
/// Applies the given state overrides (a set of [AccountOverride]) to the [CacheDB].
fn apply_state_overrides<DB>(overrides: StateOverride, db: &mut CacheDB<DB>) -> EthResult<()>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
for (account, account_overrides) in overrides {
apply_account_override(account, account_overrides, db)?;
}
Ok(())
}
/// Applies a single [AccountOverride] to the [CacheDB].
fn apply_account_override<DB>(
account: Address,
account_override: AccountOverride,
db: &mut CacheDB<DB>,
) -> EthResult<()>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
let mut account_info = db.basic(account)?.unwrap_or_default();
if let Some(nonce) = account_override.nonce {
account_info.nonce = nonce.as_u64();
}
if let Some(code) = account_override.code {
account_info.code = Some(Bytecode::new_raw(code.0));
}
if let Some(balance) = account_override.balance {
account_info.balance = balance;
}
db.insert_account_info(account, account_info);
// We ensure that not both state and state_diff are set.
// If state is set, we must mark the account as "NewlyCreated", so that the old storage
// isn't read from
match (account_override.state, account_override.state_diff) {
(Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)),
(None, None) => {
// nothing to do
}
(Some(new_account_state), None) => {
db.replace_account_storage(
account,
new_account_state
.into_iter()
.map(|(slot, value)| {
(U256::from_be_bytes(slot.0), U256::from_be_bytes(value.0))
})
.collect(),
)?;
}
(None, Some(account_state_diff)) => {
for (slot, value) in account_state_diff {
db.insert_account_storage(
account,
U256::from_be_bytes(slot.0),
U256::from_be_bytes(value.0),
)?;
}
}
};
Ok(())
}

View File

@@ -208,7 +208,7 @@ where
) -> Result<Bytes> {
trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, "Serving eth_call");
let (res, _env) = self
.execute_call_at(
.transact_call_at(
request,
block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)),
state_overrides,

View File

@@ -2,12 +2,18 @@
use crate::eth::error::{EthApiError, EthResult, InvalidTransactionError};
use reth_primitives::{AccessList, Address, U256};
use reth_rpc_types::CallRequest;
use reth_rpc_types::{
state::{AccountOverride, StateOverride},
CallRequest,
};
use revm::{
db::CacheDB,
precompile::{Precompiles, SpecId as PrecompilesSpecId},
primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv},
Database, Inspector,
};
use revm_primitives::{db::DatabaseRef, Bytecode};
use tracing::trace;
/// Returns the addresses of the precompiles corresponding to the SpecId.
pub(crate) fn get_precompiles(spec_id: &SpecId) -> Vec<reth_primitives::H160> {
@@ -57,6 +63,51 @@ where
Ok((res, evm.env))
}
/// Prepares the [Env] for execution.
///
/// Does not commit any changes to the underlying database.
pub(crate) fn prepare_call_env<DB>(
mut cfg: CfgEnv,
block: BlockEnv,
request: CallRequest,
db: &mut CacheDB<DB>,
state_overrides: Option<StateOverride>,
) -> EthResult<Env>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
// we want to disable this in eth_call, since this is common practice used by other node
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
cfg.disable_block_gas_limit = true;
// Disabled because eth_call is sometimes used with eoa senders
// See <htps://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
let request_gas = request.gas;
let mut env = build_call_evm_env(cfg, block, request)?;
// apply state overrides
if let Some(state_overrides) = state_overrides {
apply_state_overrides(state_overrides, db)?;
}
// The basefee should be ignored for eth_call
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
env.block.basefee = U256::ZERO;
if request_gas.is_none() && env.tx.gas_price > U256::ZERO {
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap");
// no gas limit was provided in the request, so we need to cap the request's gas limit
cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?;
}
Ok(env)
}
/// Creates a new [Env] to be used for executing the [CallRequest] in `eth_call`.
///
/// Note: this does _not_ access the Database to check the sender.
@@ -197,3 +248,72 @@ impl CallFees {
}
}
}
/// Applies the given state overrides (a set of [AccountOverride]) to the [CacheDB].
fn apply_state_overrides<DB>(overrides: StateOverride, db: &mut CacheDB<DB>) -> EthResult<()>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
for (account, account_overrides) in overrides {
apply_account_override(account, account_overrides, db)?;
}
Ok(())
}
/// Applies a single [AccountOverride] to the [CacheDB].
fn apply_account_override<DB>(
account: Address,
account_override: AccountOverride,
db: &mut CacheDB<DB>,
) -> EthResult<()>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
let mut account_info = db.basic(account)?.unwrap_or_default();
if let Some(nonce) = account_override.nonce {
account_info.nonce = nonce.as_u64();
}
if let Some(code) = account_override.code {
account_info.code = Some(Bytecode::new_raw(code.0));
}
if let Some(balance) = account_override.balance {
account_info.balance = balance;
}
db.insert_account_info(account, account_info);
// We ensure that not both state and state_diff are set.
// If state is set, we must mark the account as "NewlyCreated", so that the old storage
// isn't read from
match (account_override.state, account_override.state_diff) {
(Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)),
(None, None) => {
// nothing to do
}
(Some(new_account_state), None) => {
db.replace_account_storage(
account,
new_account_state
.into_iter()
.map(|(slot, value)| {
(U256::from_be_bytes(slot.0), U256::from_be_bytes(value.0))
})
.collect(),
)?;
}
(None, Some(account_state_diff)) => {
for (slot, value) in account_state_diff {
db.insert_account_storage(
account,
U256::from_be_bytes(slot.0),
U256::from_be_bytes(value.0),
)?;
}
}
};
Ok(())
}