feat(rpc): support eth_call state overrides (#1634)

This commit is contained in:
Matthias Seitz
2023-03-04 21:44:33 +01:00
committed by GitHub
parent 91652a0b0c
commit 0e9d18d4e9
6 changed files with 109 additions and 13 deletions

View File

@@ -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.
///

View File

@@ -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()

View File

@@ -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>,

View File

@@ -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(())
}

View File

@@ -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"))
}

View File

@@ -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),