,
) -> Result<(BundleStateWithReceipts, u64), BlockExecutionError> {
trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions");
+ // TODO: there isn't really a parent beacon block root here, so not sure whether or not to
+ // call the 4788 beacon contract
let (receipts, gas_used) =
executor.execute_transactions(block, U256::ZERO, Some(senders))?;
diff --git a/crates/interfaces/src/executor.rs b/crates/interfaces/src/executor.rs
index cbddf4017..fbde59bf0 100644
--- a/crates/interfaces/src/executor.rs
+++ b/crates/interfaces/src/executor.rs
@@ -26,6 +26,10 @@ pub enum BlockValidationError {
BlockPreMerge { hash: H256 },
#[error("Missing total difficulty")]
MissingTotalDifficulty { hash: H256 },
+ #[error("EIP-4788 Parent beacon block root missing for active Cancun block")]
+ MissingParentBeaconBlockRoot,
+ #[error("The parent beacon block root is not zero for Cancun genesis block")]
+ CancunGenesisParentBeaconBlockRootNotZero,
}
/// BlockExecutor Errors
diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs
index c209e9858..4dd68d849 100644
--- a/crates/payload/basic/src/lib.rs
+++ b/crates/payload/basic/src/lib.rs
@@ -33,8 +33,10 @@ use reth_primitives::{
};
use reth_provider::{BlockReaderIdExt, BlockSource, BundleStateWithReceipts, StateProviderFactory};
use reth_revm::{
- database::StateProviderDatabase, env::tx_env_with_recovered, into_reth_log,
- state_change::post_block_withdrawals_balance_increments,
+ database::StateProviderDatabase,
+ env::tx_env_with_recovered,
+ into_reth_log,
+ state_change::{apply_beacon_root_contract_call, post_block_withdrawals_balance_increments},
};
use reth_rlp::Encodable;
use reth_tasks::TaskSpawner;
@@ -45,6 +47,7 @@ use revm::{
Database, DatabaseCommit, State,
};
use std::{
+ fmt::Debug,
future::Future,
pin::Pin,
sync::{atomic::AtomicBool, Arc},
@@ -664,6 +667,16 @@ where
let block_number = initialized_block_env.number.to::();
+ // apply eip-4788 pre block contract call
+ pre_block_beacon_root_contract_call(
+ &mut db,
+ &chain_spec,
+ block_number,
+ &initialized_cfg,
+ &initialized_block_env,
+ &attributes,
+ )?;
+
let mut receipts = Vec::new();
while let Some(pool_tx) = best_txs.next() {
// ensure we still have capacity for this transaction
@@ -771,7 +784,8 @@ where
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;
- // merge all transitions into bundle state.
+ // merge all transitions into bundle state, this would apply the withdrawal balance changes and
+ // 4788 contract call
db.merge_transitions(BundleRetention::PlainState);
let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number);
@@ -861,7 +875,7 @@ where
extra_data,
attributes,
chain_spec,
- ..
+ initialized_cfg,
} = config;
debug!(parent_hash=?parent_block.hash, parent_number=parent_block.number, "building empty payload");
@@ -876,10 +890,21 @@ where
let block_number = initialized_block_env.number.to::();
let block_gas_limit: u64 = initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX);
+ // apply eip-4788 pre block contract call
+ pre_block_beacon_root_contract_call(
+ &mut db,
+ &chain_spec,
+ block_number,
+ &initialized_cfg,
+ &initialized_block_env,
+ &attributes,
+ )?;
+
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;
- // merge transition, this will apply the withdrawal balance changes.
+ // merge all transitions into bundle state, this would apply the withdrawal balance changes and
+ // 4788 contract call
db.merge_transitions(BundleRetention::PlainState);
// calculate the state root
@@ -967,6 +992,50 @@ fn commit_withdrawals>(
})
}
+/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call.
+///
+/// This constructs a new [EVM](revm::EVM) with the given DB, and environment ([CfgEnv] and
+/// [BlockEnv]) to execute the pre block contract call.
+///
+/// The parent beacon block root used for the call is gathered from the given
+/// [PayloadBuilderAttributes].
+///
+/// This uses [apply_beacon_root_contract_call] to ultimately apply the beacon root contract state
+/// change.
+fn pre_block_beacon_root_contract_call(
+ db: &mut DB,
+ chain_spec: &ChainSpec,
+ block_number: u64,
+ initialized_cfg: &CfgEnv,
+ initialized_block_env: &BlockEnv,
+ attributes: &PayloadBuilderAttributes,
+) -> Result<(), PayloadBuilderError>
+where
+ DB: Database + DatabaseCommit,
+ ::Error: Debug,
+{
+ // Configure the environment for the block.
+ let env = Env {
+ cfg: initialized_cfg.clone(),
+ block: initialized_block_env.clone(),
+ ..Default::default()
+ };
+
+ // apply pre-block EIP-4788 contract call
+ let mut evm_pre_block = revm::EVM::with_env(env);
+ evm_pre_block.database(db);
+
+ // initialize a block from the env, because the pre block call needs the block itself
+ apply_beacon_root_contract_call(
+ chain_spec,
+ attributes.timestamp,
+ block_number,
+ attributes.parent_beacon_block_root,
+ &mut evm_pre_block,
+ )
+ .map_err(|err| PayloadBuilderError::Internal(err.into()))
+}
+
/// Checks if the new payload is better than the current best.
///
/// This compares the total fees of the blocks, higher is better.
diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs
index 7955a6e47..2fcdf09b3 100644
--- a/crates/primitives/src/constants/mod.rs
+++ b/crates/primitives/src/constants/mod.rs
@@ -1,6 +1,6 @@
//! Ethereum protocol-related constants
-use crate::{H256, U256};
+use crate::{H160, H256, U256};
use hex_literal::hex;
use std::time::Duration;
@@ -132,6 +132,13 @@ pub const BEACON_CONSENSUS_REORG_UNWIND_DEPTH: u64 = 3;
///
pub const ALLOWED_FUTURE_BLOCK_TIME_SECONDS: u64 = 15;
+/// The address for the beacon roots contract defined in EIP-4788.
+pub const BEACON_ROOTS_ADDRESS: H160 = H160(hex!("bEac00dDB15f3B6d645C48263dC93862413A222D"));
+
+/// The caller to be used when calling the EIP-4788 beacon roots contract at the beginning of the
+/// block.
+pub const SYSTEM_ADDRESS: H160 = H160(hex!("fffffffffffffffffffffffffffffffffffffffe"));
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml
index c6032f436..faf692c7c 100644
--- a/crates/revm/Cargo.toml
+++ b/crates/revm/Cargo.toml
@@ -9,7 +9,7 @@ repository.workspace = true
description = "reth specific revm utilities"
[dependencies]
-# reth
+# reth
reth-primitives.workspace = true
reth-interfaces.workspace = true
reth-provider.workspace = true
@@ -21,4 +21,4 @@ reth-consensus-common = { path = "../consensus/common" }
revm.workspace = true
# common
-tracing.workspace = true
\ No newline at end of file
+tracing.workspace = true
diff --git a/crates/revm/revm-primitives/src/config.rs b/crates/revm/revm-primitives/src/config.rs
index ec7188007..22d5a15cf 100644
--- a/crates/revm/revm-primitives/src/config.rs
+++ b/crates/revm/revm-primitives/src/config.rs
@@ -19,7 +19,9 @@ pub fn revm_spec_by_timestamp_after_merge(
/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecId {
- if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
+ if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
+ revm::primitives::CANCUN
+ } else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
revm::primitives::SHANGHAI
} else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) {
revm::primitives::MERGE
diff --git a/crates/revm/revm-primitives/src/env.rs b/crates/revm/revm-primitives/src/env.rs
index 3efa2b19c..721a46e2d 100644
--- a/crates/revm/revm-primitives/src/env.rs
+++ b/crates/revm/revm-primitives/src/env.rs
@@ -1,9 +1,10 @@
use crate::config::revm_spec;
use reth_primitives::{
+ constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS},
recover_signer, Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
- TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, U256,
+ TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, H256, U256,
};
-use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, SpecId, TransactTo, TxEnv};
+use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv};
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
@@ -106,6 +107,51 @@ pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEn
tx_env
}
+/// Fill transaction environment with the EIP-4788 system contract message data.
+///
+/// This requirements for the beacon root contract call defined by
+/// [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) are:
+///
+/// At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e.
+/// before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the
+/// 32-byte input of `header.parent_beacon_block_root`, a gas limit of `30_000_000`, and `0` value.
+/// This will trigger the `set()` routine of the beacon roots contract. This is a system operation
+/// and therefore:
+/// * the call must execute to completion
+/// * the call does not count against the block’s gas limit
+/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as
+/// part of the call
+/// * if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently
+pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_block_root: H256) {
+ env.tx = TxEnv {
+ caller: SYSTEM_ADDRESS,
+ transact_to: TransactTo::Call(BEACON_ROOTS_ADDRESS),
+ // Explicitly set nonce to None so revm does not do any nonce checks
+ nonce: None,
+ gas_limit: 30_000_000,
+ value: U256::ZERO,
+ data: parent_beacon_block_root.to_fixed_bytes().to_vec().into(),
+ // Setting the gas price to zero enforces that no value is transferred as part of the call,
+ // and that the call will not count against the block's gas limit
+ gas_price: U256::ZERO,
+ // The chain ID check is not relevant here and is disabled if set to None
+ chain_id: None,
+ // Setting the gas priority fee to None ensures the effective gas price is derived from the
+ // `gas_price` field, which we need to be zero
+ gas_priority_fee: None,
+ access_list: Vec::new(),
+ // blob fields can be None for this tx
+ blob_hashes: Vec::new(),
+ max_fee_per_blob_gas: None,
+ };
+
+ // ensure the block gas limit is >= the tx
+ env.block.gas_limit = U256::from(env.tx.gas_limit);
+
+ // disable the base fee check for this call by setting the base fee to zero
+ env.block.basefee = U256::ZERO;
+}
+
/// Fill transaction environment from [TransactionSignedEcRecovered].
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
diff --git a/crates/revm/src/processor.rs b/crates/revm/src/processor.rs
index 9ded17dd1..183e603d6 100644
--- a/crates/revm/src/processor.rs
+++ b/crates/revm/src/processor.rs
@@ -4,7 +4,7 @@ use crate::{
eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
into_reth_log,
stack::{InspectorStack, InspectorStackConfig},
- state_change::post_block_balance_increments,
+ state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
};
use reth_interfaces::{
executor::{BlockExecutionError, BlockValidationError},
@@ -53,7 +53,7 @@ pub struct EVMProcessor<'a> {
/// Outer vector stores receipts for each block sequentially.
/// The inner vector stores receipts ordered by transaction number.
///
- /// If receipt is None it means it is pruned.
+ /// If receipt is None it means it is pruned.
receipts: Vec>>,
/// First block will be initialized to `None`
/// and be set to the block number of first block executed.
@@ -172,6 +172,24 @@ impl<'a> EVMProcessor<'a> {
);
}
+ /// 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
+ /// state changes are made.
+ pub fn apply_beacon_root_contract_call(
+ &mut self,
+ block: &Block,
+ ) -> Result<(), BlockExecutionError> {
+ apply_beacon_root_contract_call(
+ &self.chain_spec,
+ block.timestamp,
+ block.number,
+ block.parent_beacon_block_root,
+ &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(
@@ -256,6 +274,8 @@ impl<'a> EVMProcessor<'a> {
total_difficulty: U256,
senders: Option>,
) -> Result<(Vec, u64), BlockExecutionError> {
+ self.init_env(&block.header, total_difficulty);
+
// perf: do not execute empty blocks
if block.body.is_empty() {
return Ok((Vec::new(), 0))
@@ -263,8 +283,6 @@ impl<'a> EVMProcessor<'a> {
let senders = self.recover_senders(&block.body, senders)?;
- self.init_env(&block.header, total_difficulty);
-
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.len());
for (transaction, sender) in block.body.iter().zip(senders) {
@@ -318,6 +336,8 @@ impl<'a> EVMProcessor<'a> {
total_difficulty: U256,
senders: Option>,
) -> Result, BlockExecutionError> {
+ self.init_env(&block.header, total_difficulty);
+ self.apply_beacon_root_contract_call(block)?;
let (receipts, cumulative_gas_used) =
self.execute_transactions(block, total_difficulty, senders)?;
@@ -529,3 +549,433 @@ pub fn verify_receipt<'a>(
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use reth_primitives::{
+ constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS},
+ keccak256, Account, Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, MAINNET,
+ };
+ use reth_provider::{AccountReader, BlockHashReader, StateRootProvider};
+ use reth_revm_primitives::TransitionState;
+ use revm::Database;
+ use std::{collections::HashMap, str::FromStr};
+
+ use super::*;
+
+ /// Returns the beacon root contract code
+ fn beacon_root_contract_code() -> Bytes {
+ Bytes::from_str("0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500").unwrap()
+ }
+
+ #[derive(Debug, Default, Clone, Eq, PartialEq)]
+ struct StateProviderTest {
+ accounts: HashMap, Account)>,
+ contracts: HashMap,
+ block_hash: HashMap,
+ }
+
+ impl StateProviderTest {
+ /// Insert account.
+ fn insert_account(
+ &mut self,
+ address: Address,
+ mut account: Account,
+ bytecode: Option,
+ storage: HashMap,
+ ) {
+ if let Some(bytecode) = bytecode {
+ let hash = keccak256(&bytecode);
+ account.bytecode_hash = Some(hash);
+ self.contracts.insert(hash, Bytecode::new_raw(bytecode.into()));
+ }
+ self.accounts.insert(address, (storage, account));
+ }
+ }
+
+ impl AccountReader for StateProviderTest {
+ fn basic_account(&self, address: Address) -> reth_interfaces::Result