Compare commits

...

11 Commits

Author SHA1 Message Date
Dan Cline
a979a0a42c fix(tree): prioritize BAL prewarm for state root task (#23839) 2026-04-29 16:42:02 +02:00
Dan Cline
73fd1b6a72 fix(engine): do not install state hook if BAL is disabled (#23835) (#23836)
Co-authored-by: Alexey Shekhirin <github@shekhirin.com>
2026-04-29 16:00:22 +02:00
Ishika Choudhury
ada5c64864 chore: remaining Devnet3 fixes (#23826)
Co-authored-by: Soubhik Singha Mahapatra <soubhiksmp2004@gmail.com>
Co-authored-by: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com>
Co-authored-by: Brian Picciano <me@mediocregopher.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-29 14:16:02 +02:00
Alexey Shekhirin
4cc0a8fb24 ci(hive): use bal-devnet-3 fixtures and branch 2026-04-29 10:13:38 +02:00
Matthias Seitz
253d53e94a chore(hive): remove duplicate expected failures 2026-04-29 10:07:26 +02:00
Matthias Seitz
bf349a3cb6 fix(bal): propagate built block access lists 2026-04-29 10:04:50 +02:00
Matthias Seitz
f17ff3fa5f Merge remote-tracking branch 'origin/main' into bal-devnet-3
# Conflicts:
#	.github/scripts/hive/expected_failures.yaml
2026-04-29 09:48:58 +02:00
Emma Jamieson-Hoare
b598d64ebd feat(net): enable ETH70 by default (#23771) (#23772)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-28 13:13:19 +01:00
Emma Jamieson-Hoare
e56fb8f22b chore(hive): expect Amsterdam EIP-7610 collision failures
Adds Amsterdam fork variants of the EIP-7610 create-collision test
failures already tracked for prior forks in eels/consume-engine and
eels/consume-rlp.

Amp-Thread-ID: https://ampcode.com/threads/T-019dcdeb-2f75-71d7-a319-e88f3677046a
Co-authored-by: Amp <amp@ampcode.com>
2026-04-27 11:13:44 +02:00
Emma Jamieson-Hoare
18edd918c8 feat(engine): validate BAL post-execution (EIP-7928)
Pre-execution: reject blocks whose declared BAL exceeds the gas-limit
budget (total_bal_items > gas_limit / ITEM_COST).

Post-execution: enable the BAL builder on the State DB when a BAL is
present, extract the produced BAL via take_built_alloy_bal, and verify
its hash matches block_access_list_hash committed in the header.

Amp-Thread-ID: https://ampcode.com/threads/T-019dcdeb-2f75-71d7-a319-e88f3677046a
Co-authored-by: Amp <amp@ampcode.com>
2026-04-27 11:13:44 +02:00
Emma Jamieson-Hoare
2771424cc9 feat(consensus): add BAL error variants
Adds BlockAccessListHashMismatch and BlockAccessListCostMoreThanGasLimit
ConsensusError variants for EIP-7928 (Amsterdam) BAL validation.

Amp-Thread-ID: https://ampcode.com/threads/T-019dcdeb-2f75-71d7-a319-e88f3677046a
Co-authored-by: Amp <amp@ampcode.com>
2026-04-27 11:13:44 +02:00
19 changed files with 239 additions and 45 deletions

View File

@@ -5,8 +5,8 @@ fixture_variant="${1:-osaka}"
case "${fixture_variant}" in
amsterdam)
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/bal@v6.0.0/fixtures_bal.tar.gz"
eels_branch="devnets/snøbal/4"
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/bal@v5.6.1/fixtures_bal.tar.gz"
eels_branch="devnets/bal/3"
;;
osaka)
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz"

1
Cargo.lock generated
View File

@@ -7765,6 +7765,7 @@ name = "reth-consensus"
version = "2.1.0"
dependencies = [
"alloy-consensus",
"alloy-eip7928",
"alloy-primitives",
"auto_impl",
"reth-execution-types",

View File

@@ -191,8 +191,10 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
}
};
let bal= executor.take_bal();
if let Err(err) = consensus
.validate_block_post_execution(&block, &result, None)
.validate_block_post_execution(&block, &result, None,bal)
.wrap_err_with(|| {
format!(
"Failed to validate block {} {}",

View File

@@ -18,6 +18,7 @@ reth-primitives-traits.workspace = true
# ethereum
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-eip7928.workspace = true
# misc
auto_impl.workspace = true
@@ -29,10 +30,9 @@ std = [
"reth-primitives-traits/std",
"alloy-primitives/std",
"alloy-consensus/std",
"reth-primitives-traits/std",
"alloy-eip7928/std",
"reth-execution-types/std",
"thiserror/std",
"alloy-eip7928/std",
]
test-utils = [
"reth-primitives-traits/test-utils",
]
test-utils = ["reth-primitives-traits/test-utils"]

View File

@@ -38,6 +38,7 @@ use alloc::{
vec::Vec,
};
use alloy_consensus::Header;
use alloy_eip7928::BlockAccessList;
use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
use core::{error::Error, fmt::Display};
@@ -85,6 +86,7 @@ pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
block: &RecoveredBlock<N::Block>,
result: &BlockExecutionResult<N::Receipt>,
receipt_root_bloom: Option<ReceiptRootBloom>,
block_access_list: Option<BlockAccessList>,
) -> Result<(), ConsensusError>;
}
@@ -474,6 +476,12 @@ pub enum ConsensusError {
/// EIP-7825: Transaction gas limit exceeds maximum allowed
#[error(transparent)]
TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
/// Error when an unexpected block access list cost is encountered.
#[error("block access list cost exceeds gas limit")]
BlockAccessListCostMoreThanGasLimit,
/// Error when the block access list hash doesn't match the expected value.
#[error("block access list hash mismatch: {0}")]
BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
/// Any additional consensus error, for example L2-specific errors.
#[error(transparent)]
Other(#[from] Arc<dyn Error + Send + Sync>),
@@ -519,6 +527,23 @@ impl ConsensusError {
}
}
/// Validates the block access list against the gas limit.
///
/// EIP-7925 specifies that the total cost of the block access list items must not exceed
/// the gas limit. Each item costs `ITEM_COST` gas.
pub fn validate_block_access_list_gas(
block_access_list: Option<&alloy_eip7928::BlockAccessList>,
gas_limit: u64,
) -> Result<(), ConsensusError> {
if let Some(bal) = block_access_list {
let bal_items = alloy_eip7928::total_bal_items(bal);
if bal_items > gas_limit / alloy_eip7928::ITEM_COST as u64 {
return Err(ConsensusError::BlockAccessListCostMoreThanGasLimit)
}
}
Ok(())
}
impl From<InvalidTransactionError> for ConsensusError {
fn from(value: InvalidTransactionError) -> Self {
Self::InvalidTransaction(value)

View File

@@ -20,6 +20,7 @@
use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
use alloc::sync::Arc;
use alloy_eip7928::BlockAccessList;
use reth_execution_types::BlockExecutionResult;
use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
@@ -77,6 +78,7 @@ impl<N: NodePrimitives> FullConsensus<N> for NoopConsensus {
_block: &RecoveredBlock<N::Block>,
_result: &BlockExecutionResult<N::Receipt>,
_receipt_root_bloom: Option<ReceiptRootBloom>,
_block_access_list: Option<BlockAccessList>,
) -> Result<(), ConsensusError> {
Ok(())
}

View File

@@ -1,4 +1,5 @@
use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
use alloy_eip7928::BlockAccessList;
use core::sync::atomic::{AtomicBool, Ordering};
use reth_execution_types::BlockExecutionResult;
use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
@@ -52,6 +53,7 @@ impl<N: NodePrimitives> FullConsensus<N> for TestConsensus {
_block: &RecoveredBlock<N::Block>,
_result: &BlockExecutionResult<N::Receipt>,
_receipt_root_bloom: Option<ReceiptRootBloom>,
_block_access_list: Option<BlockAccessList>,
) -> Result<(), ConsensusError> {
if self.fail_validation() {
Err(ConsensusError::BaseFeeMissing)

View File

@@ -123,6 +123,7 @@ where
/// Whether sparse trie cache pruning is fully disabled.
disable_sparse_trie_cache_pruning: bool,
/// Whether to disable BAL-based parallel execution (falls back to tx-based prewarming).
#[allow(unused)]
disable_bal_parallel_execution: bool,
/// Whether to disable BAL-driven parallel state root computation.
disable_bal_parallel_state_root: bool,
@@ -272,7 +273,9 @@ where
halve_workers,
config,
);
let install_state_hook = env.decoded_bal.is_none();
// If no BALs are present or we have them explicitly disabled, we use sparse trie task and
// need to send the updates to it via state hook
let install_state_hook = env.decoded_bal.is_none() || self.disable_bal_parallel_state_root;
let prewarm_handle = self.spawn_caching_with(
env,
prewarm_rx,
@@ -505,14 +508,14 @@ where
);
{
let to_prewarm_task = to_prewarm_task.clone();
let disable_bal_parallel_execution = self.disable_bal_parallel_execution;
let disable_bal_parallel_state_root = self.disable_bal_parallel_state_root;
self.executor.spawn_blocking_named("prewarm", move || {
let mode = if skip_prewarm {
PrewarmMode::Skipped
} else if let Some(decoded_bal) =
maybe_decoded_bal.filter(|_| !disable_bal_parallel_execution)
let mode = if let Some(decoded_bal) =
maybe_decoded_bal.filter(|_| !disable_bal_parallel_state_root)
{
PrewarmMode::BlockAccessList(decoded_bal)
} else if skip_prewarm {
PrewarmMode::Skipped
} else {
PrewarmMode::Transactions(transactions)
};
@@ -798,7 +801,7 @@ impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
/// Returns a state hook to stream execution state updates to the sparse trie cache task.
///
/// Returns `None` when execution should not send state updates, such as BAL-driven execution.
/// Returns `None` when BAL-driven hashed state streaming feeds the sparse trie task.
pub fn state_hook(&self) -> Option<impl OnStateHook> {
self.install_state_hook
.then(|| self.state_root_handle.as_ref().map(|handle| handle.state_hook()))

View File

@@ -62,7 +62,9 @@ use crate::tree::payload_processor::receipt_root_task::{IndexedReceipt, ReceiptR
use reth_chain_state::{
CanonicalInMemoryState, DeferredTrieData, ExecutedBlock, ExecutionTimingStats, LazyOverlay,
};
use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
use reth_consensus::{
validate_block_access_list_gas, ConsensusError, FullConsensus, ReceiptRootBloom,
};
use reth_engine_primitives::{
ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
};
@@ -568,7 +570,7 @@ where
// The receipt root task is spawned before execution and receives receipts incrementally
// as transactions complete, allowing parallel computation during execution.
let execute_block_start = Instant::now();
let (output, senders, receipt_root_rx) =
let (output, senders, receipt_root_rx, built_bal) =
match self.execute_block(state_provider, env, &input, &mut handle) {
Ok(output) => output,
Err(err) => return self.handle_execution_error(input, err, &parent_block),
@@ -650,6 +652,7 @@ where
transaction_root,
receipt_root_bloom,
hashed_state,
built_bal
),
block
);
@@ -906,6 +909,7 @@ where
BlockExecutionOutput<N::Receipt>,
Vec<Address>,
tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>,
Option<BlockAccessList>,
),
InsertBlockErrorKind,
>
@@ -913,15 +917,29 @@ where
S: StateProvider + Send,
Err: core::error::Error + Send + Sync + 'static,
V: PayloadValidator<T, Block = N::Block>,
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
T: PayloadTypes<
BuiltPayload: BuiltPayload<Primitives = N>,
ExecutionData: ExecutionPayload,
>,
Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
{
debug!(target: "engine::tree::payload_validator", "Executing block");
if let Some(bal_opt) = input.block_access_list() {
let bal = bal_opt.map_err(BlockExecutionError::other)?;
validate_block_access_list_gas(Some(&bal), input.gas_limit())
.map_err(|e| {
debug!(target: "engine::tree::payload_validator", "BAL is invalid since it contains more items than the gas limit allows");
InsertBlockErrorKind::Consensus(e)
})?
}
let has_bal = input.block_access_list().is_some();
let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
State::builder()
.with_database(StateProviderDatabase::new(state_provider))
.with_bundle_update()
.with_bal_builder_if(has_bal)
.build()
});
@@ -980,6 +998,7 @@ where
handle.iter_transactions(),
&receipt_tx,
&executed_tx_index,
has_bal,
)?;
drop(receipt_tx);
@@ -994,6 +1013,11 @@ where
debug_span!(target: "engine::tree", "merge_transitions")
.in_scope(|| db.merge_transitions(BundleRetention::Reverts));
// Extract the built bal if payload has bal
let built_bal = if has_bal { db.take_built_alloy_bal() } else { None };
tracing::info!("Built Bal is {:?}", built_bal);
let output = BlockExecutionOutput { result, state: db.take_bundle() };
let execution_duration = execution_start.elapsed();
@@ -1001,7 +1025,7 @@ where
self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
Ok((output, senders, result_rx))
Ok((output, senders, result_rx, built_bal))
}
/// Executes transactions and collects senders, streaming receipts to a background task.
@@ -1013,18 +1037,20 @@ where
/// - Collecting transaction senders for later use
///
/// Returns the executor (for finalization) and the collected senders.
fn execute_transactions<E, Tx, InnerTx, Err>(
fn execute_transactions<'a, E, Tx, InnerTx, Err, DB>(
&self,
mut executor: E,
transaction_count: usize,
transactions: impl Iterator<Item = Result<Tx, Err>>,
receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
executed_tx_index: &AtomicUsize,
has_bal: bool,
) -> Result<(E, Vec<Address>), BlockExecutionError>
where
E: BlockExecutor<Receipt = N::Receipt>,
E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
InnerTx: TxHashRef,
DB: revm::Database + 'a,
Err: core::error::Error + Send + Sync + 'static,
{
let mut senders = Vec::with_capacity(transaction_count);
@@ -1035,6 +1061,11 @@ where
.in_scope(|| executor.apply_pre_execution_changes())?;
self.metrics.record_pre_execution(pre_exec_start.elapsed());
// Bump BAL index after pre-execution changes (EIP-7928: index 0 is pre-execution)
if has_bal {
executor.evm_mut().db_mut().bump_bal_index();
}
// Execute transactions
let exec_span = debug_span!(target: "engine::tree", "execution").entered();
let mut transactions = transactions.into_iter();
@@ -1079,7 +1110,12 @@ where
let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
}
}
// Bump BAL index after each transaction (EIP-7928)
if has_bal {
executor.evm_mut().db_mut().bump_bal_index();
}
}
drop(exec_span);
Ok((executor, senders))
@@ -1362,6 +1398,7 @@ where
transaction_root: Option<B256>,
receipt_root_bloom: Option<ReceiptRootBloom>,
hashed_state: LazyHashedPostState,
built_bal: Option<BlockAccessList>,
) -> Result<LazyHashedPostState, InsertBlockErrorKind>
where
V: PayloadValidator<T, Block = N::Block>,
@@ -1388,9 +1425,13 @@ where
let _enter =
debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
.entered();
if let Err(err) =
self.consensus.validate_block_post_execution(block, output, receipt_root_bloom)
{
if let Err(err) = self.consensus.validate_block_post_execution(
block,
output,
receipt_root_bloom,
built_bal,
) {
// call post-block hook
self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
return Err(err.into())

View File

@@ -13,7 +13,7 @@ extern crate alloc;
use alloc::{fmt::Debug, sync::Arc};
use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, EMPTY_OMMER_ROOT_HASH};
use alloy_eips::eip7840::BlobParams;
use alloy_eips::{eip7840::BlobParams, eip7928::BlockAccessList};
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_consensus::{
Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom, TransactionRoot,
@@ -108,9 +108,15 @@ where
block: &RecoveredBlock<N::Block>,
result: &BlockExecutionResult<N::Receipt>,
receipt_root_bloom: Option<ReceiptRootBloom>,
block_access_list: Option<BlockAccessList>,
) -> Result<(), ConsensusError> {
let res =
validate_block_post_execution(block, &self.chain_spec, result, receipt_root_bloom);
let res = validate_block_post_execution(
block,
&self.chain_spec,
result,
receipt_root_bloom,
block_access_list,
);
if self.skip_requests_hash_check &&
let Err(ConsensusError::BodyRequestsHashDiff(_)) = &res

View File

@@ -1,6 +1,9 @@
use alloc::vec::Vec;
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
use alloy_eips::Encodable2718;
use alloy_eips::{
eip7928::{compute_block_access_list_hash, BlockAccessList},
Encodable2718,
};
use alloy_primitives::{Bloom, Bytes, B256};
use reth_chainspec::EthereumHardforks;
use reth_consensus::ConsensusError;
@@ -21,6 +24,7 @@ pub fn validate_block_post_execution<B, R, ChainSpec>(
chain_spec: &ChainSpec,
result: &BlockExecutionResult<R>,
receipt_root_bloom: Option<(B256, Bloom)>,
block_access_list: Option<BlockAccessList>,
) -> Result<(), ConsensusError>
where
B: Block,
@@ -79,6 +83,21 @@ where
}
}
// Validate that the block access list hash matches the calculated block access list hash
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) &&
block_access_list.is_some()
{
let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
let default_bal = BlockAccessList::default();
let block_access_list_hash =
compute_block_access_list_hash(block_access_list.as_ref().unwrap_or(&default_bal));
if block_access_list_hash != block_bal_hash {
return Err(ConsensusError::BlockAccessListHashMismatch(
(block_access_list_hash, block_bal_hash).into(),
))
}
}
Ok(())
}

View File

@@ -47,6 +47,7 @@ where
transactions,
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
state_root,
block_access_list_hash,
..
} = input;
@@ -90,6 +91,12 @@ where
};
}
let bal_hash = if self.chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
block_access_list_hash
} else {
None
};
let header = Header {
parent_hash: ctx.parent_hash,
ommers_hash: EMPTY_OMMER_ROOT_HASH,
@@ -112,8 +119,8 @@ where
blob_gas_used: block_blob_gas_used,
excess_blob_gas,
requests_hash,
block_access_list_hash: None,
slot_number: None,
block_access_list_hash: bal_hash,
slot_number: ctx.slot_number,
};
Ok(Block {

View File

@@ -9,7 +9,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
use alloy_consensus::Transaction;
use alloy_primitives::U256;
use alloy_primitives::{Bytes, U256};
use alloy_rlp::Encodable;
use alloy_rpc_types_engine::PayloadAttributes as EthPayloadAttributes;
use reth_basic_payload_builder::{
@@ -446,7 +446,9 @@ where
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
}
let BlockBuilderOutcome { execution_result, block, .. } = if let Some(mut handle) = trie_handle
let BlockBuilderOutcome { execution_result, block, block_access_list, .. } = if let Some(
mut handle,
) = trie_handle
{
// Drop the state hook, which drops the StateHookSender and triggers
// FinishedStateUpdates via its Drop impl, signaling the trie task to finalize.
@@ -486,7 +488,9 @@ where
}));
}
let payload = EthBuiltPayload::new(sealed_block, total_fees, requests, None)
let block_access_list: Option<Bytes> =
block_access_list.map(|block_access_list| alloy_rlp::encode(&block_access_list).into());
let payload = EthBuiltPayload::new(sealed_block, total_fees, requests, block_access_list)
// add blob sidecars from the executed txs
.with_sidecars(blob_sidecars);

View File

@@ -79,4 +79,11 @@ where
Self::Right(b) => b.size_hint(),
}
}
fn take_bal(&mut self) -> Option<alloy_eips::eip7928::BlockAccessList> {
match self {
Self::Left(a) => a.take_bal(),
Self::Right(b) => b.take_bal(),
}
}
}

View File

@@ -3,7 +3,10 @@
use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{BlockHeader, Header};
use alloy_eips::eip2718::WithEncoded;
use alloy_eips::{
eip2718::WithEncoded,
eip7928::{compute_block_access_list_hash, BlockAccessList},
};
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory, GasOutput};
use alloy_evm::{
block::{CommitChanges, ExecutableTxParts},
@@ -21,7 +24,10 @@ use reth_primitives_traits::{
use reth_storage_api::StateProvider;
pub use reth_storage_errors::provider::ProviderError;
use reth_trie_common::{updates::TrieUpdates, HashedPostState};
use revm::database::{states::bundle_state::BundleRetention, BundleState, State};
use revm::{
database::{states::bundle_state::BundleRetention, BundleState, State},
state::bal::Bal,
};
/// A type that knows how to execute a block. It is assumed to operate on a
/// [`crate::Evm`] internally and use [`State`] as database.
@@ -145,6 +151,9 @@ pub trait Executor<DB: Database>: Sized {
///
/// This is used to optimize DB commits depending on the size of the state.
fn size_hint(&self) -> usize;
/// Takes built [`BlockAccessList`] from executor.
fn take_bal(&mut self) -> Option<BlockAccessList>;
}
/// Input for block building. Consumed by [`BlockAssembler`].
@@ -162,6 +171,7 @@ pub trait Executor<DB: Database>: Sized {
/// - `bundle_state`: Accumulated state changes from all transactions
/// - `state_provider`: Access to the current state for additional lookups
/// - `state_root`: The calculated state root after all changes
/// - `block_access_list_hash`: Block access list hash (EIP-7928, Amsterdam)
///
/// # Usage
///
@@ -178,6 +188,7 @@ pub trait Executor<DB: Database>: Sized {
/// bundle_state: &state_changes,
/// state_provider: &state,
/// state_root: calculated_root,
/// block_access_list_hash: Some(calculated_bal_hash),
/// };
///
/// let block = assembler.assemble_block(input)?;
@@ -205,6 +216,8 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
pub state_provider: &'b dyn StateProvider,
/// State root for this block.
pub state_root: B256,
/// Block access list hash (EIP-7928, Amsterdam).
pub block_access_list_hash: Option<B256>,
}
impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
@@ -222,6 +235,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
bundle_state: &'a BundleState,
state_provider: &'b dyn StateProvider,
state_root: B256,
block_access_list_hash: Option<B256>,
) -> Self {
Self {
evm_env,
@@ -232,6 +246,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
bundle_state,
state_provider,
state_root,
block_access_list_hash,
}
}
}
@@ -301,6 +316,8 @@ pub struct BlockBuilderOutcome<N: NodePrimitives> {
pub trie_updates: TrieUpdates,
/// The built block.
pub block: RecoveredBlock<N::Block>,
/// Block access list built during execution (EIP-7928, Amsterdam).
pub block_access_list: Option<BlockAccessList>,
}
/// A type that knows how to execute and build a block.
@@ -453,7 +470,10 @@ where
type Executor = Executor;
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
self.executor.apply_pre_execution_changes()
self.executor.apply_pre_execution_changes()?;
self.executor.evm_mut().db_mut().bump_bal_index();
Ok(())
}
fn execute_transaction_with_commit_condition(
@@ -466,6 +486,7 @@ where
self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
{
self.transactions.push(tx);
self.executor.evm_mut().db_mut().bump_bal_index();
Ok(Some(gas_used))
} else {
Ok(None)
@@ -483,6 +504,10 @@ where
// merge all transitions into bundle state
db.merge_transitions(BundleRetention::Reverts);
let block_access_list = db.take_built_alloy_bal();
let block_access_list_hash =
block_access_list.as_ref().map(|bal| compute_block_access_list_hash(bal));
let hashed_state = state.hashed_post_state(&db.bundle_state);
let (state_root, trie_updates) = match state_root_precomputed {
Some(precomputed) => precomputed,
@@ -503,11 +528,18 @@ where
bundle_state: &db.bundle_state,
state_provider: &state,
state_root,
block_access_list_hash,
})?;
let block = RecoveredBlock::new_unhashed(block, senders);
Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block })
Ok(BlockBuilderOutcome {
execution_result: result,
hashed_state,
trie_updates,
block,
block_access_list,
})
}
fn executor_mut(&mut self) -> &mut Self::Executor {
@@ -554,11 +586,33 @@ where
block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
{
let result = self
let mut executor = self
.strategy_factory
.executor_for_block(&mut self.db, block)
.map_err(BlockExecutionError::other)?
.execute_block(block.transactions_recovered())?;
.map_err(BlockExecutionError::other)?;
let has_bal = block.header().block_access_list_hash().is_some();
if has_bal {
executor.evm_mut().db_mut().bal_state.bal_builder = Some(Bal::new());
} else {
executor.evm_mut().db_mut().bal_state.bal_builder = None;
}
executor.apply_pre_execution_changes()?;
if has_bal {
executor.evm_mut().db_mut().bump_bal_index();
}
for tx in block.transactions_recovered() {
executor.execute_transaction(tx)?;
if has_bal {
executor.evm_mut().db_mut().bump_bal_index();
}
}
let result = executor.apply_post_execution_changes()?;
self.db.merge_transitions(BundleRetention::Reverts);
@@ -592,6 +646,10 @@ where
fn size_hint(&self) -> usize {
self.db.bundle_state.size_hint()
}
fn take_bal(&mut self) -> Option<BlockAccessList> {
self.db.take_built_alloy_bal()
}
}
/// A helper trait marking a 'static type that can be converted into an [`ExecutableTxParts`] for
@@ -697,6 +755,10 @@ mod tests {
fn size_hint(&self) -> usize {
0
}
fn take_bal(&mut self) -> Option<BlockAccessList> {
None
}
}
#[test]

View File

@@ -35,10 +35,11 @@ pub enum EthVersion {
impl EthVersion {
/// The latest known eth version
pub const LATEST: Self = Self::Eth69;
pub const LATEST: Self = Self::Eth70;
/// All known eth versions
pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
pub const ALL_VERSIONS: &'static [Self] =
&[Self::Eth70, Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
/// Returns true if the version is eth/66
pub const fn is_eth66(&self) -> bool {

View File

@@ -202,7 +202,7 @@ where
// update the cached reads
self.update_cached_reads(parent_header_hash, request_cache).await;
self.consensus.validate_block_post_execution(&block, &output, None)?;
self.consensus.validate_block_post_execution(&block, &output, None, None)?;
self.ensure_payment(&block, &output, &message)?;

View File

@@ -4,7 +4,7 @@ use alloy_primitives::BlockNumber;
use num_traits::Zero;
use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
use reth_config::config::ExecutionConfig;
use reth_consensus::FullConsensus;
use reth_consensus::{validate_block_access_list_gas, FullConsensus};
use reth_db::{static_file::HeaderMask, tables};
use reth_evm::{execute::Executor, metrics::ExecutorMetrics, ConfigureEvm};
use reth_execution_types::Chain;
@@ -357,7 +357,19 @@ where
})
})?;
if let Err(err) = self.consensus.validate_block_post_execution(&block, &result, None) {
let bal = executor.take_bal().unwrap_or_default();
if block.header().block_access_list_hash().is_some() &&
let Err(err) = validate_block_access_list_gas(Some(&bal), block.gas_limit())
{
return Err(StageError::Block {
block: Box::new(block.block_with_parent()),
error: BlockErrorKind::Validation(err),
})
}
if let Err(err) =
self.consensus.validate_block_post_execution(&block, &result, None, Some(bal))
{
return Err(StageError::Block {
block: Box::new(block.block_with_parent()),
error: BlockErrorKind::Validation(err),

View File

@@ -252,7 +252,7 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> {
.map_err(|err| Error::block_failed(block_number, err))?;
// Consensus checks after block execution
validate_block_post_execution(block, &chain_spec, &output, None)
validate_block_post_execution(block, &chain_spec, &output, None, None)
.map_err(|err| Error::block_failed(block_number, err))?;
// Compute and check the post state root