diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index d641ad7c8..a8c4560b8 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -1,7 +1,7 @@ use reth_consensus_common::calc; use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ - constants::SYSTEM_ADDRESS, Address, ChainSpec, Hardfork, Header, Withdrawal, H256, U256, + constants::SYSTEM_ADDRESS, Address, ChainSpec, Header, Withdrawal, H256, U256, }; use reth_revm_primitives::{env::fill_tx_env_with_beacon_root_contract_call, Database}; use revm::{primitives::ResultAndState, DatabaseCommit, EVM}; @@ -109,8 +109,8 @@ where Ok(()) } -/// Returns a map of addresses to their balance increments if shanghai is active at the given -/// timestamp. +/// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the +/// given timestamp. #[inline] pub fn post_block_withdrawals_balance_increments( chain_spec: &ChainSpec, @@ -137,7 +137,7 @@ pub fn insert_post_block_withdrawals_balance_increments( balance_increments: &mut HashMap, ) { // Process withdrawals - if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block_timestamp) { + if chain_spec.is_shanghai_activated_at_timestamp(block_timestamp) { if let Some(withdrawals) = withdrawals { for withdrawal in withdrawals { *balance_increments.entry(withdrawal.address).or_default() += diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc/src/eth/api/pending_block.rs index 0cf7c36e1..cf73307ca 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc/src/eth/api/pending_block.rs @@ -1,16 +1,24 @@ //! Support for building a pending block via local txpool. -use crate::eth::error::EthResult; +use crate::eth::error::{EthApiError, EthResult}; +use core::fmt::Debug; use reth_primitives::{ - constants::{BEACON_NONCE, EMPTY_WITHDRAWALS}, - proofs, Block, Header, IntoRecoveredTransaction, Receipt, SealedBlock, SealedHeader, + constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE}, + proofs, Block, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT, H256, U256, }; -use reth_provider::{BundleStateWithReceipts, StateProviderFactory}; -use reth_revm::{database::StateProviderDatabase, env::tx_env_with_recovered, into_reth_log}; +use reth_provider::{BundleStateWithReceipts, ChainSpecProvider, StateProviderFactory}; +use reth_revm::{ + database::StateProviderDatabase, + env::tx_env_with_recovered, + into_reth_log, + state_change::{apply_beacon_root_contract_call, post_block_withdrawals_balance_increments}, +}; use reth_transaction_pool::TransactionPool; -use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; -use revm_primitives::{BlockEnv, CfgEnv, EVMError, Env, InvalidTransaction, ResultAndState}; +use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State}; +use revm_primitives::{ + BlockEnv, CfgEnv, EVMError, Env, InvalidTransaction, ResultAndState, SpecId, +}; use std::time::Instant; /// Configured [BlockEnv] and [CfgEnv] for a pending block @@ -25,14 +33,19 @@ pub(crate) struct PendingBlockEnv { } impl PendingBlockEnv { - /// Builds a pending block from the given client and pool. + /// Builds a pending block using the given client and pool. + /// + /// If the origin is the actual pending block, the block is built with withdrawals. + /// + /// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre + /// block contract call using the parent beacon block root received from the CL. pub(crate) fn build_block( self, client: &Client, pool: &Pool, ) -> EthResult where - Client: StateProviderFactory, + Client: StateProviderFactory + ChainSpecProvider, Pool: TransactionPool, { let Self { cfg, block_env, origin } = self; @@ -43,13 +56,39 @@ impl PendingBlockEnv { let mut db = State::builder().with_database(Box::new(state)).with_bundle_update().build(); let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = block_env.gas_limit.try_into().unwrap_or(u64::MAX); + let mut sum_blob_gas_used = 0; + let block_gas_limit: u64 = block_env.gas_limit.to::(); let base_fee = block_env.basefee.to::(); let block_number = block_env.number.to::(); let mut executed_txs = Vec::new(); let mut best_txs = pool.best_transactions_with_base_fee(base_fee); + let (withdrawals, withdrawals_root) = match origin { + PendingBlockEnvOrigin::ActualPending(ref block) => { + (block.withdrawals.clone(), block.withdrawals_root) + } + PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None), + }; + + let chain_spec = client.chain_spec(); + + let parent_beacon_block_root = if origin.is_actual_pending() { + // apply eip-4788 pre block contract call if we got the block from the CL with the real + // parent beacon block root + pre_block_beacon_root_contract_call( + &mut db, + chain_spec.as_ref(), + block_number, + &cfg, + &block_env, + origin.header().parent_beacon_block_root, + )?; + origin.header().parent_beacon_block_root + } else { + None + }; + let mut receipts = Vec::new(); while let Some(pool_tx) = best_txs.next() { @@ -65,6 +104,28 @@ impl PendingBlockEnv { // convert tx to a signed transaction let tx = pool_tx.to_recovered_transaction(); + // There's only limited amount of blob space available per block, so we need to check if + // the EIP-4844 can still fit in the block + if let Some(blob_tx) = tx.transaction.as_eip4844() { + let tx_blob_gas = blob_tx.blob_gas(); + if sum_blob_gas_used + tx_blob_gas > MAX_DATA_GAS_PER_BLOCK { + // we can't fit this _blob_ transaction into the block, so we mark it as + // invalid, which removes its dependent transactions from + // the iterator. This is similar to the gas limit condition + // for regular transactions above. + best_txs.mark_invalid(&pool_tx); + continue + } else { + // add to the data gas if we're going to execute the transaction + sum_blob_gas_used += tx_blob_gas; + + // if we've reached the max data gas per block, we can skip blob txs entirely + if sum_blob_gas_used == MAX_DATA_GAS_PER_BLOCK { + best_txs.skip_blobs(); + } + } + } + // Configure the environment for the block. let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx: tx_env_with_recovered(&tx) }; @@ -93,10 +154,10 @@ impl PendingBlockEnv { } } }; - // commit changes - db.commit(state); let gas_used = result.gas_used(); + // commit changes + db.commit(state); // add gas used by the transaction to cumulative gas used, before creating the receipt cumulative_gas_used += gas_used; @@ -112,6 +173,17 @@ impl PendingBlockEnv { // append transaction to the list of executed transactions executed_txs.push(tx.into_signed()); } + + // executes the withdrawals and commits them to the Database and BundleState. + let balance_increments = post_block_withdrawals_balance_increments( + &chain_spec, + block_env.timestamp.try_into().unwrap_or(u64::MAX), + withdrawals.clone().unwrap_or_default().as_ref(), + ); + + // increment account balances for withdrawals + db.increment_balances(balance_increments)?; + // merge all transitions into bundle state. db.merge_transitions(BundleRetention::PlainState); @@ -126,6 +198,10 @@ impl PendingBlockEnv { // create the block header let transactions_root = proofs::calculate_transaction_root(&executed_txs); + // check if cancun is activated to set eip4844 header fields correctly + let blob_gas_used = + if cfg.spec_id >= SpecId::CANCUN { Some(sum_blob_gas_used) } else { None }; + let header = Header { parent_hash, ommers_hash: EMPTY_OMMER_ROOT, @@ -133,7 +209,7 @@ impl PendingBlockEnv { state_root, transactions_root, receipts_root, - withdrawals_root: Some(EMPTY_WITHDRAWALS), + withdrawals_root, logs_bloom, timestamp: block_env.timestamp.to::(), mix_hash: block_env.prevrandao.unwrap_or_default(), @@ -143,20 +219,61 @@ impl PendingBlockEnv { gas_limit: block_gas_limit, difficulty: U256::ZERO, gas_used: cumulative_gas_used, - blob_gas_used: None, - excess_blob_gas: None, + blob_gas_used, + excess_blob_gas: block_env.get_blob_excess_gas(), extra_data: Default::default(), - parent_beacon_block_root: None, + parent_beacon_block_root, }; // seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals: Some(vec![]) }; + let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; let sealed_block = block.seal_slow(); Ok(sealed_block) } } +/// 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. +/// +/// This uses [apply_beacon_root_contract_call] to ultimately apply the beacon root contract state +/// change. +fn pre_block_beacon_root_contract_call( + db: &mut DB, + chain_spec: &ChainSpec, + block_number: u64, + initialized_cfg: &CfgEnv, + initialized_block_env: &BlockEnv, + parent_beacon_block_root: Option, +) -> EthResult<()> +where + DB: Database + DatabaseCommit, + ::Error: Debug, +{ + // Configure the environment for the block. + let env = Env { + cfg: initialized_cfg.clone(), + block: initialized_block_env.clone(), + ..Default::default() + }; + + // apply pre-block EIP-4788 contract call + let mut evm_pre_block = revm::EVM::with_env(env); + evm_pre_block.database(db); + + // initialize a block from the env, because the pre block call needs the block itself + apply_beacon_root_contract_call( + chain_spec, + initialized_block_env.timestamp.to::(), + block_number, + parent_beacon_block_root, + &mut evm_pre_block, + ) + .map_err(|err| EthApiError::Internal(err.into())) +} + /// The origin for a configured [PendingBlockEnv] #[derive(Clone, Debug)] pub(crate) enum PendingBlockEnvOrigin {