Skip to content

Commit

Permalink
Implement state and block overrides (#1270)
Browse files Browse the repository at this point in the history
* Implement state and block overrides

* Update comment

* apply_block_overrides func

* apply_state_overrides func

* Use a single func for replacing account storage

* Clippy

* Address feedback

Use Reth's BlockOverrides with saturating_to to convert back to u64

* Test for block overrides

* Test for state overrides

* Comment tests

* Create a fresh working set

* Remove box

* remove alloy-serde

---------

Co-authored-by: eyusufatik <[email protected]>
  • Loading branch information
rakanalh and eyusufatik authored Oct 9, 2024
1 parent 31a2b52 commit 1f5782f
Show file tree
Hide file tree
Showing 9 changed files with 422 additions and 39 deletions.
54 changes: 42 additions & 12 deletions crates/evm/src/evm/db.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "native")]
use std::collections::HashMap;

use reth_primitives::{Address, B256};
use revm::primitives::{AccountInfo as ReVmAccountInfo, Bytecode, U256};
use revm::Database;
Expand All @@ -6,6 +9,23 @@ use sov_state::codec::BcsCodec;

use super::{AccountInfo, DbAccount};

// infallible
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DBError {}

impl std::fmt::Display for DBError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EVM Infallible DBError")
}
}

// impl stdError for dberror
impl std::error::Error for DBError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

pub(crate) struct EvmDb<'a, C: sov_modules_api::Context> {
pub(crate) accounts: sov_modules_api::StateMap<Address, AccountInfo, BcsCodec>,
pub(crate) code: sov_modules_api::StateMap<B256, Bytecode, BcsCodec>,
Expand All @@ -27,22 +47,32 @@ impl<'a, C: sov_modules_api::Context> EvmDb<'a, C> {
working_set,
}
}
}

// infallible
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DBError {}
#[cfg(feature = "native")]
pub(crate) fn override_block_hash(&mut self, number: u64, hash: B256) {
self.last_block_hashes
.set(&U256::from(number), &hash, self.working_set);
}

impl std::fmt::Display for DBError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EVM Infallible DBError")
#[cfg(feature = "native")]
pub(crate) fn override_account(&mut self, account: &Address, info: AccountInfo) {
self.accounts.set(account, &info, self.working_set);
}
}

// impl stdError for dberror
impl std::error::Error for DBError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
#[cfg(feature = "native")]
pub(crate) fn override_set_account_storage(
&mut self,
account: &Address,
state_diff: HashMap<B256, B256>,
) {
let db_account = DbAccount::new(*account);
for (slot, value) in state_diff {
db_account.storage.set(
&U256::from_be_bytes(slot.0),
&U256::from_be_bytes(value.0),
self.working_set,
);
}
}
}

Expand Down
18 changes: 14 additions & 4 deletions crates/evm/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use reth_primitives::{
};
use reth_provider::ProviderError;
use reth_rpc_eth_types::error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError};
use reth_rpc_types::state::StateOverride;
use reth_rpc_types::trace::geth::{GethDebugTracingOptions, GethTrace};
use reth_rpc_types::{
AnyReceiptEnvelope, AnyTransactionReceipt, Log, OtherFields, ReceiptWithBloom,
AnyReceiptEnvelope, AnyTransactionReceipt, BlockOverrides, Log, OtherFields, ReceiptWithBloom,
TransactionReceipt,
};
use reth_rpc_types_compat::block::from_primitive_with_hash;
Expand Down Expand Up @@ -494,8 +495,8 @@ impl<C: sov_modules_api::Context> Evm<C> {
&self,
request: reth_rpc_types::TransactionRequest,
block_id: Option<BlockId>,
_state_overrides: Option<reth_rpc_types::state::StateOverride>,
_block_overrides: Option<Box<reth_rpc_types::BlockOverrides>>,
state_overrides: Option<StateOverride>,
block_overrides: Option<BlockOverrides>,
working_set: &mut WorkingSet<C>,
) -> RpcResult<reth_primitives::Bytes> {
let block_number = match block_id {
Expand All @@ -509,7 +510,7 @@ impl<C: sov_modules_api::Context> Evm<C> {
None => BlockNumberOrTag::Latest,
};

let (block_env, mut cfg_env) = {
let (mut block_env, mut cfg_env) = {
let block_env = match block_number {
BlockNumberOrTag::Pending => get_pending_block_env(self, working_set),
_ => {
Expand All @@ -519,6 +520,7 @@ impl<C: sov_modules_api::Context> Evm<C> {
BlockEnv::from(&block)
}
};

// Set evm state to block if needed
match block_number {
BlockNumberOrTag::Pending | BlockNumberOrTag::Latest => {}
Expand All @@ -536,6 +538,14 @@ impl<C: sov_modules_api::Context> Evm<C> {

let mut evm_db = self.get_db(working_set);

if let Some(mut block_overrides) = block_overrides {
apply_block_overrides(&mut block_env, &mut block_overrides, &mut evm_db);
}

if let Some(state_overrides) = state_overrides {
apply_state_overrides(state_overrides, &mut evm_db)?;
}

let cap_to_balance = evm_db
.basic(request.from.unwrap_or_default())
.map_err(EthApiError::from)?
Expand Down
117 changes: 114 additions & 3 deletions crates/evm/src/rpc_helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,120 @@
use std::collections::HashMap;

pub use filter::*;
pub use log_utils::*;
pub use responses::*;
use reth_primitives::{keccak256, Address};
use reth_rpc_eth_types::{EthApiError, EthResult};
use reth_rpc_types::state::AccountOverride;
use reth_rpc_types::BlockOverrides;
use revm::Database;

mod filter;
mod log_utils;
mod responses;
mod tracing_utils;

pub use filter::*;
pub use log_utils::*;
pub use responses::*;
pub(crate) use tracing_utils::*;

use crate::db::EvmDb;
#[cfg(feature = "native")]
use crate::primitive_types::BlockEnv;

#[cfg(feature = "native")]
/// Applies all instances [`AccountOverride`] to the [`EvmDb`].
pub(crate) fn apply_state_overrides<C: sov_modules_api::Context>(
state_overrides: HashMap<Address, AccountOverride>,
db: &mut EvmDb<C>,
) -> EthResult<()> {
for (address, account_overrides) in state_overrides {
apply_account_override(address, account_overrides, db)?;
}

Ok(())
}

#[cfg(feature = "native")]
/// Applies a single [`AccountOverride`] to the [`EvmDb`].
pub(crate) fn apply_account_override<C: sov_modules_api::Context>(
account: Address,
account_override: AccountOverride,
db: &mut EvmDb<C>,
) -> EthResult<()> {
// we need to fetch the account via the `DatabaseRef` to not update the state of the account,
// which is modified via `Database::basic_ref`
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_hash = keccak256(code);
}
if let Some(balance) = account_override.balance {
account_info.balance = balance;
}

db.override_account(&account, account_info.into());

// 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.override_set_account_storage(&account, new_account_state);
}
(None, Some(account_state_diff)) => {
db.override_set_account_storage(&account, account_state_diff);
}
};

Ok(())
}

#[cfg(feature = "native")]
/// Applies all instances of [`BlockOverride`] to the [`EvmDb`].
pub(crate) fn apply_block_overrides<C: sov_modules_api::Context>(
block_env: &mut BlockEnv,
block_overrides: &mut BlockOverrides,
db: &mut EvmDb<C>,
) {
if let Some(block_hashes) = block_overrides.block_hash.take() {
// override block hashes
for (num, hash) in block_hashes {
db.override_block_hash(num, hash);
}
}

let BlockOverrides {
number,
time,
gas_limit,
coinbase,
random,
base_fee,
block_hash: _,
difficulty: _,
} = *block_overrides;
if let Some(number) = number {
block_env.number = number.saturating_to();
}
if let Some(time) = time {
block_env.timestamp = time;
}
if let Some(gas_limit) = gas_limit {
block_env.gas_limit = gas_limit;
}
if let Some(coinbase) = coinbase {
block_env.coinbase = coinbase;
}
if let Some(random) = random {
block_env.prevrandao = random;
}
if let Some(base_fee) = base_fee {
block_env.basefee = base_fee.saturating_to();
}
}
Loading

0 comments on commit 1f5782f

Please sign in to comment.