Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: impl eip-2935
  • Loading branch information
onbjerg committed Apr 29, 2024
commit 680ded77312a5a5e5bd869c48a04334a9b5a0f50
7 changes: 6 additions & 1 deletion crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ use reth_revm::{
eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
processor::verify_receipt,
stack::InspectorStack,
state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
state_change::{
apply_beacon_root_contract_call, apply_blockhashes_update, post_block_balance_increments,
},
Evm, State,
};
use revm_primitives::{
Expand Down Expand Up @@ -152,6 +154,9 @@ where
block.parent_beacon_block_root,
&mut evm,
)?;
// todo: the parent timestamp stuff is really annoying -.- how do we not make this pollute
// the api
apply_blockhashes_update(&self.chain_spec, block.timestamp, block.number, 0, &mut evm)?;

// execute transactions
let mut cumulative_gas_used = 0;
Expand Down
13 changes: 12 additions & 1 deletion crates/interfaces/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,25 @@ pub enum BlockValidationError {
/// The beacon block root
parent_beacon_block_root: B256,
},
/// EVM error during beacon root contract call
/// EVM error during [EIP-4788] beacon root contract call.
///
/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788
#[error("failed to apply beacon root contract call at {parent_beacon_block_root}: {message}")]
BeaconRootContractCall {
/// The beacon block root
parent_beacon_block_root: Box<B256>,
/// The error message.
message: String,
},
/// EVM error during [EIP-2935] pre-block state transition.
///
/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935
#[error("failed to apply EIP-2935 pre-block state transition: {message}")]
// todo: better variant name
Eip2935StateTransition {
/// The error message.
message: String,
},
}

/// BlockExecutor Errors
Expand Down
13 changes: 12 additions & 1 deletion crates/payload/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use reth_provider::{
BlockReaderIdExt, BlockSource, CanonStateNotification, ProviderError, StateProviderFactory,
};
use reth_revm::state_change::{
apply_beacon_root_contract_call, post_block_withdrawals_balance_increments,
apply_beacon_root_contract_call, apply_blockhashes_update,
post_block_withdrawals_balance_increments,
};
use reth_tasks::TaskSpawner;
use reth_transaction_pool::TransactionPool;
Expand Down Expand Up @@ -888,6 +889,16 @@ where
attributes.parent_beacon_block_root(),
&mut evm_pre_block,
)
.map_err(|err| PayloadBuilderError::Internal(err.into()))?;
// todo: the parent timestamp stuff is really annoying -.- how do we not make this pollute
// the api
apply_blockhashes_update(
chain_spec,
attributes.timestamp(),
block_number,
0,
&mut evm_pre_block,
)
.map_err(|err| PayloadBuilderError::Internal(err.into()))
}

Expand Down
24 changes: 22 additions & 2 deletions crates/revm/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use crate::{
database::StateProviderDatabase,
eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
stack::{InspectorStack, InspectorStackConfig},
state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
state_change::{
apply_beacon_root_contract_call, apply_blockhashes_update, post_block_balance_increments,
},
};

/// EVMProcessor is a block executor that uses revm to execute blocks or multiple blocks.
Expand Down Expand Up @@ -152,7 +154,7 @@ where

/// Applies the pre-block call to the EIP-4788 beacon block root contract.
///
/// If cancun is not activated or the block is the genesis block, then this is a no-op, and no
/// If Cancun is not activated or the block is the genesis block, then this is a no-op, and no
/// state changes are made.
fn apply_beacon_root_contract_call(
&mut self,
Expand All @@ -168,6 +170,23 @@ where
Ok(())
}

/// Applies the pre-block state transition to the EIP-2935 blockhash history contract.
///
/// If Prague is not activated or the block is the genesis block, then this is a no-op, and no
/// state changes are made.
fn apply_blockhashes_update(&mut self, block: &Block) -> Result<(), BlockExecutionError> {
// todo: the parent timestamp stuff is really annoying -.- how do we not make this pollute
// the api
apply_blockhashes_update(
&self.chain_spec,
block.timestamp,
block.number,
0,
&mut self.evm,
)?;
Ok(())
}

/// Apply post execution state changes, including block rewards, withdrawals, and irregular DAO
/// hardfork state change.
pub fn apply_post_execution_state_change(
Expand Down Expand Up @@ -261,6 +280,7 @@ where
) -> Result<Vec<Receipt>, BlockExecutionError> {
self.init_env(&block.header, total_difficulty);
self.apply_beacon_root_contract_call(block)?;
self.apply_blockhashes_update(block)?;
let (receipts, cumulative_gas_used) = self.execute_transactions(block, total_difficulty)?;

// Check if gas used matches the value set in header.
Expand Down
105 changes: 99 additions & 6 deletions crates/revm/src/state_change.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use reth_consensus_common::calc;
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
constants::SYSTEM_ADDRESS, revm::env::fill_tx_env_with_beacon_root_contract_call, Address,
ChainSpec, Header, Withdrawal, B256, U256,
address, constants::SYSTEM_ADDRESS, revm::env::fill_tx_env_with_beacon_root_contract_call,
Address, ChainSpec, Header, Withdrawal, B256, U256,
};
use revm::{interpreter::Host, Database, DatabaseCommit, Evm};
use std::collections::HashMap;
use revm::{
interpreter::Host,
primitives::{Account, AccountInfo, StorageSlot},
Database, DatabaseCommit, Evm,
};
use std::{collections::HashMap, ops::Rem};

/// Collect all balance changes at the end of the block.
///
Expand Down Expand Up @@ -51,11 +55,100 @@ pub fn post_block_balance_increments(
balance_increments
}

/// Applies the pre-block call to the EIP-4788 beacon block root contract, using the given block,
// todo: temporary move over of constants from revm until we've migrated to the latest version
const HISTORY_SERVE_WINDOW: usize = 8192;
const HISTORY_STORAGE_ADDRESS: Address = address!("25a219378dad9b3503c8268c9ca836a52427a4fb");

/// Applies the pre-block state change outlined in [EIP-2935] to store historical blockhashes in a
/// system contract.
///
/// If Prague is not activated, or the block is the genesis block, then this is a no-op, and no
/// state changes are made.
///
/// If the provided block is the fork activation block, this will generate multiple state changes,
/// as it inserts multiple historical blocks, as outlined in the EIP.
///
/// If the provided block is after Prague has been activated, this will only insert a single block
/// hash.
///
/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935
#[inline]
pub fn apply_blockhashes_update<EXT, DB: Database + DatabaseCommit>(
chain_spec: &ChainSpec,
block_timestamp: u64,
block_number: u64,
parent_timestamp: u64,
evm: &mut Evm<'_, EXT, DB>,
) -> Result<(), BlockExecutionError>
where
DB::Error: std::fmt::Display,
{
// If Prague is not activated or this is the genesis block, no hashes are added.
if !chain_spec.is_prague_active_at_timestamp(block_timestamp) || block_number == 0 {
return Ok(())
}
assert!(block_number > 0);

// Create an empty account using the `From<AccountInfo>` impl of `Account`. This marks the
// account internally as `Loaded`, which is required, since we want the EVM to retrieve storage
// values from the DB when `BLOCKHASH` is invoked.
let mut account = Account::from(AccountInfo::default());

// Insert the state change for the slot
let (slot, value) = eip2935_block_hash_slot(block_number - 1, evm)
.map_err(|err| BlockValidationError::Eip2935StateTransition { message: err.to_string() })?;
account.storage.insert(slot, value);

// If Prague was not activated at the parent block, then this is the fork activation block, and
// we add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well.
//
// Note: The -1 is because the ancestor itself was already inserted up above.
if !chain_spec.is_prague_active_at_timestamp(parent_timestamp) {
let mut ancestor_block_number = block_number - 1;
for _ in 0..HISTORY_SERVE_WINDOW - 1 {
// Stop at genesis
if ancestor_block_number == 0 {
break
}
ancestor_block_number = ancestor_block_number - 1;

let (slot, value) =
eip2935_block_hash_slot(ancestor_block_number, evm).map_err(|err| {
BlockValidationError::Eip2935StateTransition { message: err.to_string() }
})?;
account.storage.insert(slot, value);
}
}

// Commit the state change
evm.context.evm.db.commit(HashMap::from([(HISTORY_STORAGE_ADDRESS, account)]));

Ok(())
}

/// Helper function to create a [`StorageSlot`] for [EIP-2935] state transitions for a given block
/// number.
///
/// This calculates the correct storage slot in the `BLOCKHASH` history storage address, fetches the
/// blockhash and creates a [`StorageSlot`] with appropriate previous and new values.
fn eip2935_block_hash_slot<EXT, DB: Database>(
block_number: u64,
evm: &mut Evm<'_, EXT, DB>,
) -> Result<(U256, StorageSlot), DB::Error> {
let slot = U256::from(block_number).rem(U256::from(HISTORY_SERVE_WINDOW));
let current_hash = evm.db_mut().storage(HISTORY_STORAGE_ADDRESS, slot)?;
let ancestor_hash = evm.db_mut().block_hash(U256::from(block_number))?;

Ok((slot, StorageSlot::new_changed(current_hash, ancestor_hash.into())))
}

/// Applies the pre-block call to the [EIP-4788] beacon block root contract, using the given block,
/// [ChainSpec], EVM.
///
/// If cancun is not activated or the block is the genesis block, then this is a no-op, and no
/// If Cancun is not activated or the block is the genesis block, then this is a no-op, and no
/// state changes are made.
///
/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788
#[inline]
pub fn apply_beacon_root_contract_call<EXT, DB: Database + DatabaseCommit>(
chain_spec: &ChainSpec,
Expand Down
1 change: 1 addition & 0 deletions crates/rpc/rpc/src/eth/api/pending_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl PendingBlockEnv {

let chain_spec = client.chain_spec();

// todo: apply blockhash history state change
let parent_beacon_block_root = if origin.is_actual_pending() {
// apply eip-4788 pre block contract call if we got the block from the CL with the real
// parent beacon block root
Expand Down