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>,
|
senders: Vec<Address>,
|
||||||
) -> Result<(BundleStateWithReceipts, u64), BlockExecutionError> {
|
) -> Result<(BundleStateWithReceipts, u64), BlockExecutionError> {
|
||||||
trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions");
|
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) =
|
let (receipts, gas_used) =
|
||||||
executor.execute_transactions(block, U256::ZERO, Some(senders))?;
|
executor.execute_transactions(block, U256::ZERO, Some(senders))?;
|
||||||
|
|||||||
@ -26,6 +26,10 @@ pub enum BlockValidationError {
|
|||||||
BlockPreMerge { hash: H256 },
|
BlockPreMerge { hash: H256 },
|
||||||
#[error("Missing total difficulty")]
|
#[error("Missing total difficulty")]
|
||||||
MissingTotalDifficulty { hash: H256 },
|
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
|
/// BlockExecutor Errors
|
||||||
|
|||||||
@ -33,8 +33,10 @@ use reth_primitives::{
|
|||||||
};
|
};
|
||||||
use reth_provider::{BlockReaderIdExt, BlockSource, BundleStateWithReceipts, StateProviderFactory};
|
use reth_provider::{BlockReaderIdExt, BlockSource, BundleStateWithReceipts, StateProviderFactory};
|
||||||
use reth_revm::{
|
use reth_revm::{
|
||||||
database::StateProviderDatabase, env::tx_env_with_recovered, into_reth_log,
|
database::StateProviderDatabase,
|
||||||
state_change::post_block_withdrawals_balance_increments,
|
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_rlp::Encodable;
|
||||||
use reth_tasks::TaskSpawner;
|
use reth_tasks::TaskSpawner;
|
||||||
@ -45,6 +47,7 @@ use revm::{
|
|||||||
Database, DatabaseCommit, State,
|
Database, DatabaseCommit, State,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
@ -664,6 +667,16 @@ where
|
|||||||
|
|
||||||
let block_number = initialized_block_env.number.to::<u64>();
|
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();
|
let mut receipts = Vec::new();
|
||||||
while let Some(pool_tx) = best_txs.next() {
|
while let Some(pool_tx) = best_txs.next() {
|
||||||
// ensure we still have capacity for this transaction
|
// ensure we still have capacity for this transaction
|
||||||
@ -771,7 +784,8 @@ where
|
|||||||
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
|
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
|
||||||
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.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);
|
db.merge_transitions(BundleRetention::PlainState);
|
||||||
|
|
||||||
let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number);
|
let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number);
|
||||||
@ -861,7 +875,7 @@ where
|
|||||||
extra_data,
|
extra_data,
|
||||||
attributes,
|
attributes,
|
||||||
chain_spec,
|
chain_spec,
|
||||||
..
|
initialized_cfg,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
debug!(parent_hash=?parent_block.hash, parent_number=parent_block.number, "building empty payload");
|
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_number = initialized_block_env.number.to::<u64>();
|
||||||
let block_gas_limit: u64 = initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX);
|
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 } =
|
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
|
||||||
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.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);
|
db.merge_transitions(BundleRetention::PlainState);
|
||||||
|
|
||||||
// calculate the state root
|
// 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.
|
/// Checks if the new payload is better than the current best.
|
||||||
///
|
///
|
||||||
/// This compares the total fees of the blocks, higher is better.
|
/// This compares the total fees of the blocks, higher is better.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Ethereum protocol-related constants
|
//! Ethereum protocol-related constants
|
||||||
|
|
||||||
use crate::{H256, U256};
|
use crate::{H160, H256, U256};
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use std::time::Duration;
|
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>
|
/// <https://github.com/ethereum/go-ethereum/blob/a196f3e8a22b6ad22ced5c2e3baf32bc3ebd4ec9/consensus/ethash/consensus.go#L227-L229>
|
||||||
pub const ALLOWED_FUTURE_BLOCK_TIME_SECONDS: u64 = 15;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -19,7 +19,9 @@ pub fn revm_spec_by_timestamp_after_merge(
|
|||||||
|
|
||||||
/// return revm_spec from spec configuration.
|
/// return revm_spec from spec configuration.
|
||||||
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecId {
|
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
|
revm::primitives::SHANGHAI
|
||||||
} else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) {
|
} else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) {
|
||||||
revm::primitives::MERGE
|
revm::primitives::MERGE
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
use crate::config::revm_spec;
|
use crate::config::revm_spec;
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
|
constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS},
|
||||||
recover_signer, Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
|
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]
|
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
|
||||||
pub fn fill_cfg_and_block_env(
|
pub fn fill_cfg_and_block_env(
|
||||||
@ -106,6 +107,51 @@ pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEn
|
|||||||
tx_env
|
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].
|
/// Fill transaction environment from [TransactionSignedEcRecovered].
|
||||||
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
|
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
|
||||||
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
|
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},
|
eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
|
||||||
into_reth_log,
|
into_reth_log,
|
||||||
stack::{InspectorStack, InspectorStackConfig},
|
stack::{InspectorStack, InspectorStackConfig},
|
||||||
state_change::post_block_balance_increments,
|
state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
|
||||||
};
|
};
|
||||||
use reth_interfaces::{
|
use reth_interfaces::{
|
||||||
executor::{BlockExecutionError, BlockValidationError},
|
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
|
/// Apply post execution state changes, including block rewards, withdrawals, and irregular DAO
|
||||||
/// hardfork state change.
|
/// hardfork state change.
|
||||||
pub fn apply_post_execution_state_change(
|
pub fn apply_post_execution_state_change(
|
||||||
@ -256,6 +274,8 @@ impl<'a> EVMProcessor<'a> {
|
|||||||
total_difficulty: U256,
|
total_difficulty: U256,
|
||||||
senders: Option<Vec<Address>>,
|
senders: Option<Vec<Address>>,
|
||||||
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
|
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
|
||||||
|
self.init_env(&block.header, total_difficulty);
|
||||||
|
|
||||||
// perf: do not execute empty blocks
|
// perf: do not execute empty blocks
|
||||||
if block.body.is_empty() {
|
if block.body.is_empty() {
|
||||||
return Ok((Vec::new(), 0))
|
return Ok((Vec::new(), 0))
|
||||||
@ -263,8 +283,6 @@ impl<'a> EVMProcessor<'a> {
|
|||||||
|
|
||||||
let senders = self.recover_senders(&block.body, senders)?;
|
let senders = self.recover_senders(&block.body, senders)?;
|
||||||
|
|
||||||
self.init_env(&block.header, total_difficulty);
|
|
||||||
|
|
||||||
let mut cumulative_gas_used = 0;
|
let mut cumulative_gas_used = 0;
|
||||||
let mut receipts = Vec::with_capacity(block.body.len());
|
let mut receipts = Vec::with_capacity(block.body.len());
|
||||||
for (transaction, sender) in block.body.iter().zip(senders) {
|
for (transaction, sender) in block.body.iter().zip(senders) {
|
||||||
@ -318,6 +336,8 @@ impl<'a> EVMProcessor<'a> {
|
|||||||
total_difficulty: U256,
|
total_difficulty: U256,
|
||||||
senders: Option<Vec<Address>>,
|
senders: Option<Vec<Address>>,
|
||||||
) -> Result<Vec<Receipt>, BlockExecutionError> {
|
) -> Result<Vec<Receipt>, BlockExecutionError> {
|
||||||
|
self.init_env(&block.header, total_difficulty);
|
||||||
|
self.apply_beacon_root_contract_call(block)?;
|
||||||
let (receipts, cumulative_gas_used) =
|
let (receipts, cumulative_gas_used) =
|
||||||
self.execute_transactions(block, total_difficulty, senders)?;
|
self.execute_transactions(block, total_difficulty, senders)?;
|
||||||
|
|
||||||
@ -529,3 +549,433 @@ pub fn verify_receipt<'a>(
|
|||||||
|
|
||||||
Ok(())
|
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_consensus_common::calc;
|
||||||
use reth_primitives::{Address, ChainSpec, Hardfork, Header, Withdrawal, U256};
|
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
|
||||||
use std::collections::HashMap;
|
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.
|
/// Collect all balance changes at the end of the block.
|
||||||
///
|
///
|
||||||
@ -46,6 +51,64 @@ pub fn post_block_balance_increments(
|
|||||||
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
|
/// Returns a map of addresses to their balance increments if shanghai is active at the given
|
||||||
/// timestamp.
|
/// timestamp.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user