Skip to main content

reth_bb/
evm_config.rs

1//! Big-block EVM configuration.
2//!
3//! Wraps [`EthEvmConfig`] to create executors that handle multi-segment
4//! big-block execution internally. At transaction boundaries defined by
5//! [`BigBlockData`], the executor swaps the EVM environment (block env,
6//! cfg env) and applies pre/post execution changes for each segment.
7
8pub(crate) use reth_engine_primitives::BigBlockData;
9use reth_engine_primitives::ExecutionPayload as _;
10use reth_storage_errors::any::AnyError;
11use revm_primitives::Bytes;
12
13use crate::evm::{BalIndexReader, BbBlockExecutorFactory, BbEvmPlan};
14use alloy_consensus::Header;
15use alloy_eips::Decodable2718;
16use alloy_evm::{
17    eth::{spec::EthExecutorSpec, EthBlockExecutionCtx},
18    EthEvmFactory,
19};
20use alloy_primitives::B256;
21use alloy_rpc_types::engine::ExecutionData;
22use core::convert::Infallible;
23use reth_chainspec::{ChainSpec, EthChainSpec};
24use reth_ethereum_forks::Hardforks;
25use reth_ethereum_primitives::{Block, EthPrimitives};
26use reth_evm::{
27    execute::BlockAssembler, ConfigureEngineEvm, ConfigureEvm, Database, EvmEnv, EvmEnvFor,
28    ExecutableTxIterator, ExecutionCtxFor, NextBlockEnvAttributes,
29};
30use reth_evm_ethereum::{EthEvmConfig, RethReceiptBuilder};
31use reth_primitives_traits::{SealedBlock, SealedHeader, SignedTransaction, TxTy};
32use revm::primitives::hardfork::SpecId;
33use std::sync::Arc;
34
35// ---------------------------------------------------------------------------
36// Execution plan types
37// ---------------------------------------------------------------------------
38
39/// A single execution segment within a big block.
40#[derive(Debug, Clone)]
41pub(crate) struct BigBlockSegment<'a> {
42    /// Transaction index at which this segment starts.
43    pub start_tx: usize,
44    /// The EVM environment for this segment.
45    pub evm_env: EvmEnv,
46    /// The execution context for this segment.
47    pub ctx: EthBlockExecutionCtx<'a>,
48}
49
50// ---------------------------------------------------------------------------
51// BbEvmConfig
52// ---------------------------------------------------------------------------
53
54/// EVM configuration for big-block execution.
55///
56/// Wraps [`EthEvmConfig`]. When a big-block payload is received, the plan is
57/// staged on the [`BbBlockExecutorFactory`]
58/// and consumed when the executor is created. Block hashes for inter-segment
59/// BLOCKHASH resolution are reseeded into `State::block_hashes` at each
60/// segment boundary via a [`BlockHashSeeder`](crate::evm::BlockHashSeeder)
61/// callback injected in [`ConfigureEvm::create_executor`].
62#[derive(Debug, Clone)]
63pub struct BbEvmConfig<C = ChainSpec> {
64    /// The inner Ethereum EVM configuration (used for env computation).
65    pub inner: EthEvmConfig<C>,
66    /// Block executor factory for big-block execution.
67    executor_factory: BbBlockExecutorFactory<Arc<C>>,
68    /// Block assembler.
69    block_assembler: BbBlockAssembler,
70}
71
72impl<C> BbEvmConfig<C> {
73    /// Creates a new big-block EVM configuration.
74    pub fn new(inner: EthEvmConfig<C>) -> Self
75    where
76        C: Clone,
77    {
78        let chain_spec = inner.chain_spec().clone();
79        let executor_factory = BbBlockExecutorFactory::new(
80            RethReceiptBuilder::default(),
81            chain_spec,
82            EthEvmFactory::default(),
83        );
84
85        Self { inner, executor_factory, block_assembler: Default::default() }
86    }
87}
88
89// ---------------------------------------------------------------------------
90// Block hash seeder for State<DB>
91// ---------------------------------------------------------------------------
92
93/// Reseeds `State::block_hashes` with the given hashes.
94///
95/// This is used as a [`BlockHashSeeder`](crate::evm::BlockHashSeeder) callback,
96/// injected into [`BbBlockExecutor`](crate::evm::BbBlockExecutor) from
97/// `ConfigureEvm::create_executor` where the concrete `State<DB>` type is known.
98/// At each segment boundary the executor calls this to populate the ring buffer
99/// with the 256 block hashes relevant to the new segment's block number window.
100fn seed_state_block_hashes<DB>(state: &mut &mut revm::database::State<DB>, hashes: &[(u64, B256)]) {
101    for &(number, hash) in hashes {
102        state.block_hashes.insert(number, hash);
103    }
104}
105
106/// Reads the BAL index from a `&mut State<DB>`.
107///
108/// Used as a [`BalIndexReader`] callback so the
109/// generic [`BbBlockExecutor`](crate::evm::BbBlockExecutor) can pick its
110/// starting segment without a trait bound on `DB`.
111fn read_bal_index<DB>(state: &&mut revm::database::State<DB>) -> u64 {
112    state.bal_state.bal_index()
113}
114
115/// Bumps the BAL index on a `&mut State<DB>`.
116///
117/// Used as a [`BalIndexBumper`](crate::evm::BalIndexBumper) callback so the
118/// generic [`BbBlockExecutor`](crate::evm::BbBlockExecutor) can advance
119/// `bal_index` between sub-events of a segment boundary (post-N's `finish()`
120/// and pre-N+1's `apply_pre_execution_changes()`) without a trait bound on
121/// `DB`.
122fn bump_bal_index<DB: revm::Database>(state: &mut &mut revm::database::State<DB>) {
123    state.bump_bal_index();
124}
125
126/// Sets the BAL index on a `&mut State<DB>`.
127///
128/// Used as a [`BalIndexSetter`](crate::evm::BalIndexSetter) callback so
129/// [`BbBlockExecutor::initialize`](crate::evm::BbBlockExecutor) can renumber
130/// a worker's incoming `bal_index = i + 1` into the boundary-padded space
131/// `i + 1 + 2*k` (where `k` is the worker's segment index).
132fn set_bal_index<DB: revm::Database>(state: &mut &mut revm::database::State<DB>, index: u64) {
133    state.set_bal_index(index);
134}
135
136// ---------------------------------------------------------------------------
137// ConfigureEvm
138// ---------------------------------------------------------------------------
139
140impl<C> ConfigureEvm for BbEvmConfig<C>
141where
142    C: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
143{
144    type Primitives = EthPrimitives;
145    type Error = Infallible;
146    type NextBlockEnvCtx = NextBlockEnvAttributes;
147    type BlockExecutorFactory = BbBlockExecutorFactory<Arc<C>>;
148    type BlockAssembler = BbBlockAssembler;
149
150    fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
151        &self.executor_factory
152    }
153
154    fn block_assembler(&self) -> &Self::BlockAssembler {
155        &self.block_assembler
156    }
157
158    fn evm_env(&self, header: &Header) -> Result<EvmEnv<SpecId>, Self::Error> {
159        self.inner.evm_env(header)
160    }
161
162    fn next_evm_env(
163        &self,
164        parent: &Header,
165        attributes: &NextBlockEnvAttributes,
166    ) -> Result<EvmEnv, Self::Error> {
167        self.inner.next_evm_env(parent, attributes)
168    }
169
170    fn context_for_block<'a>(
171        &self,
172        _block: &'a SealedBlock<reth_ethereum_primitives::Block>,
173    ) -> Result<BbEvmPlan<'a>, Self::Error> {
174        unreachable!("big blocks EVM should only be used from within the engine pipeline")
175    }
176
177    fn context_for_next_block(
178        &self,
179        _parent: &SealedHeader,
180        _attributes: Self::NextBlockEnvCtx,
181    ) -> Result<BbEvmPlan<'_>, Self::Error> {
182        unreachable!("big blocks EVM should only be used from within the engine pipeline")
183    }
184
185    fn create_executor<'a, DB, I>(
186        &'a self,
187        evm: reth_evm::EvmFor<Self, &'a mut revm::database::State<DB>, I>,
188        ctx: BbEvmPlan<'a>,
189    ) -> alloy_evm::block::BlockExecutorFor<
190        'a,
191        Self::BlockExecutorFactory,
192        &'a mut revm::database::State<DB>,
193        I,
194    >
195    where
196        DB: Database,
197        I: reth_evm::InspectorFor<Self, &'a mut revm::database::State<DB>> + 'a,
198    {
199        let bal_index_reader: Option<BalIndexReader<&'a mut revm::database::State<DB>>> =
200            Some(read_bal_index::<DB>);
201
202        // Inject concrete function pointers that know the `State<DB>` type so
203        // the generic executor can manipulate `bal_index` and reseed block
204        // hashes without a trait bound on `DB`.
205        self.executor_factory.create_executor_with_seeder(
206            evm,
207            ctx,
208            Some(seed_state_block_hashes::<DB>),
209            bal_index_reader,
210            Some(bump_bal_index::<DB>),
211            Some(set_bal_index::<DB>),
212        )
213    }
214
215    fn create_executor_with_state<'ctx, 'db, DB, I>(
216        &'ctx self,
217        evm: reth_evm::EvmFor<Self, &'db mut revm::database::State<DB>, I>,
218        ctx: BbEvmPlan<'ctx>,
219    ) -> alloy_evm::block::BlockExecutorFor<
220        'ctx,
221        Self::BlockExecutorFactory,
222        &'db mut revm::database::State<DB>,
223        I,
224    >
225    where
226        DB: Database,
227        I: reth_evm::InspectorFor<Self, &'db mut revm::database::State<DB>>,
228    {
229        let bal_index_reader: Option<BalIndexReader<&'db mut revm::database::State<DB>>> =
230            Some(read_bal_index::<DB>);
231
232        self.executor_factory.create_executor_with_seeder(
233            evm,
234            ctx,
235            Some(seed_state_block_hashes::<DB>),
236            bal_index_reader,
237            Some(bump_bal_index::<DB>),
238            Some(set_bal_index::<DB>),
239        )
240    }
241}
242
243// ---------------------------------------------------------------------------
244// ConfigureEngineEvm — intercepts payload methods for big blocks
245// ---------------------------------------------------------------------------
246
247impl<C> ConfigureEngineEvm<BigBlockData<ExecutionData>> for BbEvmConfig<C>
248where
249    C: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
250{
251    fn evm_env_for_payload(
252        &self,
253        payload: &BigBlockData<ExecutionData>,
254    ) -> Result<EvmEnvFor<Self>, Self::Error> {
255        // Compute the env from the first segment BEFORE removing the
256        // entry (stage_plan_for_payload removes it).
257        let first_exec_data = &payload.env_switches[0];
258        let mut env = self.inner.evm_env_for_payload(first_exec_data)?;
259
260        // Disable basefee validation: transactions from different
261        // original blocks may have gas prices below the big block's
262        // effective basefee.
263        env.cfg_env.disable_base_fee = true;
264        env.block_env.gas_limit = payload.gas_limit();
265
266        Ok(env)
267    }
268
269    fn context_for_payload<'a>(
270        &self,
271        payload: &'a BigBlockData<ExecutionData>,
272    ) -> Result<ExecutionCtxFor<'a, Self>, Self::Error> {
273        let mut current_tx = 0;
274        let segments: Vec<_> = payload
275            .env_switches
276            .iter()
277            .map(|exec_data| {
278                let start_tx = current_tx;
279                let mut evm_env = self.inner.evm_env_for_payload(exec_data)?;
280                evm_env.cfg_env.disable_base_fee = true;
281                let ctx = self.inner.context_for_payload(exec_data)?;
282                current_tx += exec_data.payload.transactions().len();
283                Ok(BigBlockSegment { start_tx, evm_env, ctx })
284            })
285            .collect::<Result<_, Self::Error>>()?;
286
287        let mut plan = BbEvmPlan::new(segments);
288
289        // Add prior block hashes to the seeding list.
290        plan.block_hashes_to_seed.extend(payload.prior_block_hashes.clone());
291        plan.block_hashes_to_seed.sort_unstable_by_key(|(n, _)| *n);
292
293        Ok(plan)
294    }
295
296    fn tx_iterator_for_payload(
297        &self,
298        payload: &BigBlockData<ExecutionData>,
299    ) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
300        let transactions = payload
301            .env_switches
302            .iter()
303            .flat_map(|exec_data| exec_data.payload.transactions().clone())
304            .collect::<Vec<_>>();
305
306        let convert = |tx: Bytes| {
307            let tx =
308                TxTy::<Self::Primitives>::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;
309            let signer = tx.try_recover().map_err(AnyError::new)?;
310            Ok::<_, AnyError>(tx.with_signer(signer))
311        };
312
313        Ok((transactions, convert))
314    }
315}
316
317#[derive(Debug, Default, Clone)]
318pub struct BbBlockAssembler;
319
320impl<Spec: EthExecutorSpec + 'static> BlockAssembler<BbBlockExecutorFactory<Spec>>
321    for BbBlockAssembler
322{
323    type Block = Block;
324
325    fn assemble_block(
326        &self,
327        _input: reth_evm::execute::BlockAssemblerInput<
328            '_,
329            '_,
330            BbBlockExecutorFactory<Spec>,
331            <Self::Block as reth_node_api::Block>::Header,
332        >,
333    ) -> Result<Self::Block, reth_errors::BlockExecutionError> {
334        unreachable!("block building is not supported for big blocks EVM")
335    }
336}