mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 00:58:11 -05:00
feat(rpc): support eth_call state overrides (#1634)
This commit is contained in:
@@ -4,8 +4,8 @@ use reth_primitives::{
|
||||
H256, H64, U256, U64,
|
||||
};
|
||||
use reth_rpc_types::{
|
||||
CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock, SyncStatus,
|
||||
Transaction, TransactionReceipt, TransactionRequest, Work,
|
||||
state::StateOverride, CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock,
|
||||
SyncStatus, Transaction, TransactionReceipt, TransactionRequest, Work,
|
||||
};
|
||||
|
||||
/// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
|
||||
@@ -135,7 +135,12 @@ pub trait EthApi {
|
||||
|
||||
/// Executes a new message call immediately without creating a transaction on the block chain.
|
||||
#[method(name = "eth_call")]
|
||||
async fn call(&self, request: CallRequest, block_number: Option<BlockId>) -> Result<Bytes>;
|
||||
async fn call(
|
||||
&self,
|
||||
request: CallRequest,
|
||||
block_number: Option<BlockId>,
|
||||
state_overrides: Option<StateOverride>,
|
||||
) -> Result<Bytes>;
|
||||
|
||||
/// Generates an access list for a transaction.
|
||||
///
|
||||
|
||||
@@ -98,7 +98,7 @@ where
|
||||
));
|
||||
assert!(is_unimplemented(EthApiClient::transaction_receipt(client, hash).await.err().unwrap()));
|
||||
assert!(is_unimplemented(
|
||||
EthApiClient::call(client, call_request.clone(), None).await.err().unwrap()
|
||||
EthApiClient::call(client, call_request.clone(), None, None).await.err().unwrap()
|
||||
));
|
||||
assert!(is_unimplemented(
|
||||
EthApiClient::create_access_list(client, call_request.clone(), None).await.err().unwrap()
|
||||
|
||||
@@ -9,7 +9,7 @@ pub type StateOverride = HashMap<Address, AccountOverride>;
|
||||
|
||||
/// Custom account override used in call
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct AccountOverride {
|
||||
pub nonce: Option<u64>,
|
||||
|
||||
@@ -11,11 +11,15 @@ use reth_primitives::{
|
||||
};
|
||||
use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory};
|
||||
use reth_revm::database::{State, SubState};
|
||||
use reth_rpc_types::CallRequest;
|
||||
use reth_rpc_types::{
|
||||
state::{AccountOverride, StateOverride},
|
||||
CallRequest,
|
||||
};
|
||||
use revm::{
|
||||
db::{CacheDB, DatabaseRef},
|
||||
primitives::{
|
||||
ruint::Uint, BlockEnv, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo,
|
||||
TxEnv,
|
||||
ruint::Uint, BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState,
|
||||
TransactTo, TxEnv,
|
||||
},
|
||||
Database,
|
||||
};
|
||||
@@ -55,10 +59,11 @@ where
|
||||
&self,
|
||||
request: CallRequest,
|
||||
at: BlockId,
|
||||
state_overrides: Option<StateOverride>,
|
||||
) -> EthResult<(ResultAndState, Env)> {
|
||||
let (cfg, block_env, at) = self.evm_env_at(at).await?;
|
||||
let state = self.state_at_block_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
|
||||
self.call_with(cfg, block_env, request, state)
|
||||
self.call_with(cfg, block_env, request, state, state_overrides)
|
||||
}
|
||||
|
||||
/// Executes the call request using the given environment against the state provider
|
||||
@@ -70,6 +75,7 @@ where
|
||||
block: BlockEnv,
|
||||
mut request: CallRequest,
|
||||
state: S,
|
||||
state_overrides: Option<StateOverride>,
|
||||
) -> EthResult<(ResultAndState, Env)>
|
||||
where
|
||||
S: StateProvider,
|
||||
@@ -80,6 +86,12 @@ where
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
transact(&mut db, env)
|
||||
}
|
||||
|
||||
@@ -391,3 +403,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;
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ use reth_primitives::{
|
||||
use reth_provider::{BlockProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory};
|
||||
use reth_rpc_api::EthApiServer;
|
||||
use reth_rpc_types::{
|
||||
CallRequest, EIP1186AccountProofResponse, FeeHistory, FeeHistoryCacheItem, Index, RichBlock,
|
||||
SyncStatus, TransactionReceipt, TransactionRequest, Work,
|
||||
state::StateOverride, CallRequest, EIP1186AccountProofResponse, FeeHistory,
|
||||
FeeHistoryCacheItem, Index, RichBlock, SyncStatus, TransactionReceipt, TransactionRequest,
|
||||
Work,
|
||||
};
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
use serde_json::Value;
|
||||
@@ -177,7 +178,12 @@ where
|
||||
}
|
||||
|
||||
/// Handler for: `eth_call`
|
||||
async fn call(&self, _request: CallRequest, _block_number: Option<BlockId>) -> Result<Bytes> {
|
||||
async fn call(
|
||||
&self,
|
||||
_request: CallRequest,
|
||||
_block_number: Option<BlockId>,
|
||||
_state_overrides: Option<StateOverride>,
|
||||
) -> Result<Bytes> {
|
||||
Err(internal_rpc_err("unimplemented"))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::result::{internal_rpc_err, rpc_err};
|
||||
use jsonrpsee::{core::Error as RpcError, types::error::INVALID_PARAMS_CODE};
|
||||
use reth_primitives::{constants::SELECTOR_LEN, U128, U256};
|
||||
use reth_primitives::{constants::SELECTOR_LEN, Address, U128, U256};
|
||||
use reth_rpc_types::BlockError;
|
||||
use reth_transaction_pool::error::PoolError;
|
||||
use revm::primitives::{EVMError, Halt};
|
||||
@@ -67,6 +67,10 @@ pub(crate) enum EthApiError {
|
||||
/// Thrown when constructing an RPC block from a primitive block data failed.
|
||||
#[error(transparent)]
|
||||
InvalidBlockData(#[from] BlockError),
|
||||
/// Thrown when a [AccountOverride](reth_rpc_types::state::AccountOverride) contains
|
||||
/// conflicting `state` and `stateDiff` fields
|
||||
#[error("account {0:?} has both 'state' and 'stateDiff'")]
|
||||
BothStateAndStateDiffInOverride(Address),
|
||||
/// Other internal error
|
||||
#[error(transparent)]
|
||||
Internal(#[from] reth_interfaces::Error),
|
||||
|
||||
Reference in New Issue
Block a user