feat: add pre-block EIP-4788 beacon root contract call (#4457)

This commit is contained in:
Dan Cline
2023-09-19 11:16:48 -04:00
committed by GitHub
parent a96dbb476c
commit a80b72041b
9 changed files with 660 additions and 17 deletions

View File

@ -305,6 +305,8 @@ impl StorageInner {
senders: Vec<Address>,
) -> 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))?;

View File

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

View File

@ -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::<u64>();
// 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::<u64>();
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<DB: Database<Error = Error>>(
})
}
/// 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>(
db: &mut DB,
chain_spec: &ChainSpec,
block_number: u64,
initialized_cfg: &CfgEnv,
initialized_block_env: &BlockEnv,
attributes: &PayloadBuilderAttributes,
) -> Result<(), PayloadBuilderError>
where
DB: Database + DatabaseCommit,
<DB as Database>::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.

View File

@ -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;
/// <https://github.com/ethereum/go-ethereum/blob/a196f3e8a22b6ad22ced5c2e3baf32bc3ebd4ec9/consensus/ethash/consensus.go#L227-L229>
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::*;

View File

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

View File

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

View File

@ -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},
@ -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<Vec<Address>>,
) -> Result<(Vec<Receipt>, 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<Vec<Address>>,
) -> Result<Vec<Receipt>, 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<Address, (HashMap<StorageKey, U256>, Account)>,
contracts: HashMap<H256, Bytecode>,
block_hash: HashMap<u64, H256>,
}
impl StateProviderTest {
/// Insert account.
fn insert_account(
&mut self,
address: Address,
mut account: Account,
bytecode: Option<Bytes>,
storage: HashMap<StorageKey, U256>,
) {
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<Option<Account>> {
let ret = Ok(self.accounts.get(&address).map(|(_, acc)| *acc));
ret
}
}
impl BlockHashReader for StateProviderTest {
fn block_hash(&self, number: u64) -> reth_interfaces::Result<Option<H256>> {
Ok(self.block_hash.get(&number).cloned())
}
fn canonical_hashes_range(
&self,
start: BlockNumber,
end: BlockNumber,
) -> reth_interfaces::Result<Vec<H256>> {
let range = start..end;
Ok(self
.block_hash
.iter()
.filter_map(|(block, hash)| range.contains(block).then_some(*hash))
.collect())
}
}
impl StateRootProvider for StateProviderTest {
fn state_root(
&self,
_bundle_state: BundleStateWithReceipts,
) -> reth_interfaces::Result<H256> {
todo!()
}
}
impl StateProvider for StateProviderTest {
fn storage(
&self,
account: Address,
storage_key: reth_primitives::StorageKey,
) -> reth_interfaces::Result<Option<reth_primitives::StorageValue>> {
Ok(self
.accounts
.get(&account)
.and_then(|(storage, _)| storage.get(&storage_key).cloned()))
}
fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytecode>> {
Ok(self.contracts.get(&code_hash).cloned())
}
fn proof(
&self,
_address: Address,
_keys: &[H256],
) -> reth_interfaces::Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
todo!()
}
}
#[test]
fn eip_4788_non_genesis_call() {
let mut header = Header { timestamp: 1, number: 1, ..Header::default() };
let mut db = StateProviderTest::default();
let beacon_root_contract_code = beacon_root_contract_code();
let beacon_root_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(beacon_root_contract_code.clone())),
nonce: 1,
};
db.insert_account(
BEACON_ROOTS_ADDRESS,
beacon_root_contract_account,
Some(beacon_root_contract_code),
HashMap::new(),
);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
// execute invalid header (no parent beacon block root)
let mut executor = EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db));
// attempt to execute a block without parent beacon block root, expect err
let err = executor
.execute_and_verify_receipt(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.expect_err(
"Executing cancun block without parent beacon block root field should fail",
);
assert_eq!(
err,
BlockExecutionError::Validation(BlockValidationError::MissingParentBeaconBlockRoot)
);
// fix header, set a gas limit
header.parent_beacon_block_root = Some(H256::from_low_u64_be(0x1337));
// Now execute a block with the fixed header, ensure that it does not fail
executor
.execute(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.unwrap();
// check the actual storage of the contract - it should be:
// * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
// header.timestamp
// * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
// should be parent_beacon_block_root
let history_buffer_length = 98304u64;
let timestamp_index = header.timestamp % history_buffer_length;
let parent_beacon_block_root_index =
timestamp_index % history_buffer_length + history_buffer_length;
// get timestamp storage and compare
let timestamp_storage =
executor.db_mut().storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap();
assert_eq!(timestamp_storage, U256::from(header.timestamp));
// get parent beacon block root storage and compare
let parent_beacon_block_root_storage = executor
.db_mut()
.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
.expect("storage value should exist");
assert_eq!(parent_beacon_block_root_storage, U256::from(0x1337));
}
#[test]
fn eip_4788_no_code_cancun() {
// This test ensures that we "silently fail" when cancun is active and there is no code at
// BEACON_ROOTS_ADDRESS
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(H256::from_low_u64_be(0x1337)),
..Header::default()
};
let db = StateProviderTest::default();
// DON'T deploy the contract at genesis
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let mut executor = EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db));
executor.init_env(&header, U256::ZERO);
// get the env
let previous_env = executor.evm.env.clone();
// attempt to execute an empty block with parent beacon block root, this should not fail
executor
.execute_and_verify_receipt(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.expect(
"Executing a block with no transactions while cancun is active should not fail",
);
// ensure that the env has not changed
assert_eq!(executor.evm.env, previous_env);
}
#[test]
fn eip_4788_empty_account_call() {
// This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account
// during the pre-block call
let mut db = StateProviderTest::default();
let beacon_root_contract_code = beacon_root_contract_code();
let beacon_root_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(beacon_root_contract_code.clone())),
nonce: 1,
};
db.insert_account(
BEACON_ROOTS_ADDRESS,
beacon_root_contract_account,
Some(beacon_root_contract_code),
HashMap::new(),
);
// insert an empty SYSTEM_ADDRESS
db.insert_account(SYSTEM_ADDRESS, Account::default(), None, HashMap::new());
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let mut executor = EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db));
// construct the header for block one
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(H256::from_low_u64_be(0x1337)),
..Header::default()
};
executor.init_env(&header, U256::ZERO);
// attempt to execute an empty block with parent beacon block root, this should not fail
executor
.execute_and_verify_receipt(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.expect(
"Executing a block with no transactions while cancun is active should not fail",
);
// ensure that the nonce of the system address account has not changed
let nonce = executor.db_mut().basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce;
assert_eq!(nonce, 0);
}
#[test]
fn eip_4788_genesis_call() {
let mut db = StateProviderTest::default();
let beacon_root_contract_code = beacon_root_contract_code();
let beacon_root_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(beacon_root_contract_code.clone())),
nonce: 1,
};
db.insert_account(
BEACON_ROOTS_ADDRESS,
beacon_root_contract_account,
Some(beacon_root_contract_code),
HashMap::new(),
);
// activate cancun at genesis
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Cancun, ForkCondition::Timestamp(0))
.build(),
);
let mut header = chain_spec.genesis_header();
let mut executor = EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db));
executor.init_env(&header, U256::ZERO);
// attempt to execute the genesis block with non-zero parent beacon block root, expect err
header.parent_beacon_block_root = Some(H256::from_low_u64_be(0x1337));
let _err = executor
.execute_and_verify_receipt(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.expect_err(
"Executing genesis cancun block with non-zero parent beacon block root field should fail",
);
// fix header
header.parent_beacon_block_root = Some(H256::zero());
// now try to process the genesis block again, this time ensuring that a system contract
// call does not occur
executor
.execute(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.unwrap();
// there is no system contract call so there should be NO STORAGE CHANGES
// this means we'll check the transition state
let state = executor.evm.db().unwrap();
let transition_state = state
.transition_state
.clone()
.expect("the evm should be initialized with bundle updates");
// assert that it is the default (empty) transition state
assert_eq!(transition_state, TransitionState::default());
}
#[test]
fn eip_4788_high_base_fee() {
// This test ensures that if we have a base fee, then we don't return an error when the
// system contract is called, due to the gas price being less than the base fee.
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(H256::from_low_u64_be(0x1337)),
base_fee_per_gas: Some(u64::MAX),
..Header::default()
};
let mut db = StateProviderTest::default();
let beacon_root_contract_code = beacon_root_contract_code();
let beacon_root_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(beacon_root_contract_code.clone())),
nonce: 1,
};
db.insert_account(
BEACON_ROOTS_ADDRESS,
beacon_root_contract_account,
Some(beacon_root_contract_code),
HashMap::new(),
);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
// execute header
let mut executor = EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db));
executor.init_env(&header, U256::ZERO);
// ensure that the env is configured with a base fee
assert_eq!(executor.evm.env.block.basefee, U256::from(u64::MAX));
// Now execute a block with the fixed header, ensure that it does not fail
executor
.execute(
&Block { header: header.clone(), body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
)
.unwrap();
// check the actual storage of the contract - it should be:
// * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
// header.timestamp
// * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
// should be parent_beacon_block_root
let history_buffer_length = 98304u64;
let timestamp_index = header.timestamp % history_buffer_length;
let parent_beacon_block_root_index =
timestamp_index % history_buffer_length + history_buffer_length;
// get timestamp storage and compare
let timestamp_storage =
executor.db_mut().storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap();
assert_eq!(timestamp_storage, U256::from(header.timestamp));
// get parent beacon block root storage and compare
let parent_beacon_block_root_storage = executor
.db_mut()
.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
.unwrap();
assert_eq!(parent_beacon_block_root_storage, U256::from(0x1337));
}
}

View File

@ -1,6 +1,11 @@
use reth_consensus_common::calc;
use reth_primitives::{Address, ChainSpec, Hardfork, Header, Withdrawal, U256};
use std::collections::HashMap;
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
constants::SYSTEM_ADDRESS, Address, ChainSpec, Hardfork, Header, Withdrawal, H256, U256,
};
use reth_revm_primitives::{env::fill_tx_env_with_beacon_root_contract_call, Database};
use revm::{primitives::ResultAndState, DatabaseCommit, EVM};
use std::{collections::HashMap, fmt::Debug};
/// Collect all balance changes at the end of the block.
///
@ -46,6 +51,64 @@ 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,
/// [ChainSpec], EVM.
///
/// If cancun is not activated or the block is the genesis block, then this is a no-op, and no
/// state changes are made.
#[inline]
pub fn apply_beacon_root_contract_call<DB: Database + DatabaseCommit>(
chain_spec: &ChainSpec,
block_timestamp: u64,
block_number: u64,
block_parent_beacon_block_root: Option<H256>,
evm: &mut EVM<DB>,
) -> Result<(), BlockExecutionError>
where
<DB as Database>::Error: Debug,
{
if chain_spec.is_cancun_activated_at_timestamp(block_timestamp) {
// if the block number is zero (genesis block) then the parent beacon block root must
// be 0x0 and no system transaction may occur as per EIP-4788
if block_number == 0 {
if block_parent_beacon_block_root != Some(H256::zero()) {
return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero.into())
}
} else {
let parent_beacon_block_root = block_parent_beacon_block_root.ok_or(
BlockExecutionError::from(BlockValidationError::MissingParentBeaconBlockRoot),
)?;
// get previous env
let previous_env = evm.env.clone();
// modify env for pre block call
fill_tx_env_with_beacon_root_contract_call(&mut evm.env, parent_beacon_block_root);
let ResultAndState { mut state, .. } = match evm.transact() {
Ok(res) => res,
Err(e) => {
evm.env = previous_env;
return Err(BlockExecutionError::from(BlockValidationError::EVM {
hash: Default::default(),
message: format!("{e:?}"),
}))
}
};
state.remove(&SYSTEM_ADDRESS);
state.remove(&evm.env.block.coinbase);
let db = evm.db().expect("db to not be moved");
db.commit(state);
// re-set the previous env
evm.env = previous_env;
}
}
Ok(())
}
/// Returns a map of addresses to their balance increments if shanghai is active at the given
/// timestamp.
#[inline]