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 {