mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add pre-block EIP-4788 beacon root contract call (#4457)
This commit is contained in:
@ -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))?;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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
|
||||
tracing.workspace = true
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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<Vec<Option<Receipt>>>,
|
||||
/// 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<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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
Reference in New Issue
Block a user