1use crate::evm_config::BigBlockSegment;
10use alloy_consensus::TransactionEnvelope;
11use alloy_eips::eip7685::Requests;
12use alloy_evm::{
13 block::{
14 BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
15 ExecutableTx, GasOutput, StateDB,
16 },
17 eth::{EthBlockExecutionCtx, EthBlockExecutor, EthEvmContext, EthTxResult},
18 precompiles::PrecompilesMap,
19 Database, EthEvm, EthEvmFactory, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded,
20};
21use alloy_primitives::B256;
22use reth_ethereum_primitives::{Receipt, TransactionSigned};
23use reth_evm_ethereum::RethReceiptBuilder;
24use revm::{
25 context::{BlockEnv, TxEnv},
26 context_interface::result::{EVMError, HaltReason},
27 handler::PrecompileProvider,
28 interpreter::InterpreterResult,
29 primitives::hardfork::SpecId,
30 Inspector,
31};
32use tracing::{debug, trace};
33
34#[derive(Debug, Clone)]
40pub struct BbEvmPlan<'a> {
41 pub(crate) segments: Vec<BigBlockSegment<'a>>,
43 pub(crate) next_segment: usize,
45 pub(crate) tx_counter: usize,
47 pub(crate) block_hashes_to_seed: Vec<(u64, B256)>,
50}
51
52impl<'a> BbEvmPlan<'a> {
53 pub(crate) fn new(segments: Vec<BigBlockSegment<'a>>) -> Self {
55 let mut block_hashes_to_seed = Vec::new();
57 for seg in segments.iter().skip(1) {
58 let finished_block_number = seg.evm_env.block_env.number.saturating_to::<u64>() - 1;
59 let finished_block_hash = seg.ctx.parent_hash;
60 block_hashes_to_seed.push((finished_block_number, finished_block_hash));
61 }
62
63 Self { segments, next_segment: 1, tx_counter: 0, block_hashes_to_seed }
64 }
65
66 pub(crate) fn hashes_for_block(&self, block_number: u64) -> Vec<(u64, B256)> {
70 let min = block_number.saturating_sub(256);
71 self.block_hashes_to_seed
72 .iter()
73 .copied()
74 .filter(|(n, _)| *n >= min && *n < block_number)
75 .collect()
76 }
77
78 pub(crate) fn segment_index_for_tx(&self, tx_index: usize) -> usize {
80 self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
81 }
82}
83
84pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
94
95pub(crate) type BalIndexReader<DB> = fn(&DB) -> u64;
103
104pub(crate) type BalIndexBumper<DB> = fn(&mut DB);
114
115pub(crate) type BalIndexSetter<DB> = fn(&mut DB, u64);
125
126#[expect(missing_debug_implementations)]
138pub struct BbBlockExecutor<'a, DB, I, P, Spec>
139where
140 DB: Database,
141{
142 inner: Option<EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder>>,
144 plan: BbEvmPlan<'a>,
145 accumulated_requests: Requests,
148 gas_used_offset: u64,
151 blob_gas_used_offset: u64,
154 block_hash_seeder: Option<BlockHashSeeder<DB>>,
157 bal_index_reader: Option<BalIndexReader<DB>>,
159 bal_index_bumper: Option<BalIndexBumper<DB>>,
163 bal_index_setter: Option<BalIndexSetter<DB>>,
167 initialized: bool,
169}
170
171impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
172where
173 DB: StateDB,
174 I: Inspector<EthEvmContext<DB>>,
175 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
176 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
177 EthEvm<DB, I, P>: Evm<
178 DB = DB,
179 Tx = TxEnv,
180 HaltReason = HaltReason,
181 Error = EVMError<DB::Error>,
182 Spec = SpecId,
183 BlockEnv = BlockEnv,
184 >,
185 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
186{
187 #[expect(clippy::too_many_arguments)]
188 pub(crate) fn new(
189 evm: EthEvm<DB, I, P>,
190 plan: BbEvmPlan<'a>,
191 spec: Spec,
192 receipt_builder: RethReceiptBuilder,
193 block_hash_seeder: Option<BlockHashSeeder<DB>>,
194 bal_index_reader: Option<BalIndexReader<DB>>,
195 bal_index_bumper: Option<BalIndexBumper<DB>>,
196 bal_index_setter: Option<BalIndexSetter<DB>>,
197 ) -> Self {
198 let inner = EthBlockExecutor::new(evm, plan.segments[0].ctx.clone(), spec, receipt_builder);
199 Self {
200 inner: Some(inner),
201 plan,
202 accumulated_requests: Requests::default(),
203 gas_used_offset: 0,
204 blob_gas_used_offset: 0,
205 block_hash_seeder,
206 bal_index_reader,
207 bal_index_bumper,
208 bal_index_setter,
209 initialized: false,
210 }
211 }
212
213 fn initialize(&mut self) -> Result<(), BlockExecutionError> {
223 if self.initialized {
224 return Ok(());
225 }
226
227 let segment_idx = if let Some(bal_index) = self
228 .bal_index_reader
229 .map(|reader| reader(self.inner().evm().db()))
230 .filter(|bal_index| *bal_index > 0)
231 {
232 let segment_idx = self.plan.segment_index_for_tx((bal_index - 1) as usize);
233
234 if let Some(setter) = self.bal_index_setter {
242 let renumbered = bal_index + 2 * segment_idx as u64;
243 setter(self.inner_mut().evm_mut().db_mut(), renumbered);
244 }
245
246 segment_idx
247 } else {
248 if self.initialized {
249 return Ok(());
250 }
251
252 self.initialized = true;
253 0
254 };
255 let segment = &self.plan.segments[segment_idx];
256 let block_env = segment.evm_env.block_env.clone();
257 let block_number = block_env.number.saturating_to::<u64>();
258 let mut cfg_env = segment.evm_env.cfg_env.clone();
259 cfg_env.disable_base_fee = true;
260
261 let inner = self.inner.as_mut().expect("inner executor must exist");
262 let evm_ctx = inner.evm.ctx_mut();
263 evm_ctx.block = block_env;
264 evm_ctx.cfg = cfg_env;
265 inner.ctx = segment.ctx.clone();
266
267 self.reseed_block_hashes_for(block_number);
268
269 Ok(())
270 }
271
272 const fn inner(&self) -> &EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
273 self.inner.as_ref().expect("inner executor must exist")
274 }
275
276 const fn inner_mut(
277 &mut self,
278 ) -> &mut EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
279 self.inner.as_mut().expect("inner executor must exist")
280 }
281
282 fn reseed_block_hashes_for(&mut self, block_number: u64) {
283 let Some(seeder) = self.block_hash_seeder else { return };
284 let hashes = self.plan.hashes_for_block(block_number);
285 seeder(self.inner_mut().evm_mut().db_mut(), &hashes);
286 }
287
288 fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
289 let plan = &mut self.plan;
290 let seg_idx = plan.next_segment;
291 let prev_seg_idx = seg_idx - 1;
292
293 debug!(
294 target: "engine::bb::evm",
295 seg_idx,
296 tx_counter = plan.tx_counter,
297 "Applying segment boundary"
298 );
299
300 let prev_segment = &plan.segments[prev_seg_idx];
304
305 let new_segment = &plan.segments[seg_idx];
307 let new_block_env = new_segment.evm_env.block_env.clone();
308 let mut new_cfg_env = new_segment.evm_env.cfg_env.clone();
309 new_cfg_env.disable_base_fee = true;
310
311 plan.next_segment += 1;
312
313 let mut inner = self.inner.take().expect("inner executor must exist");
318 inner.ctx = prev_segment.ctx.clone();
319 let spec = inner.spec.clone();
320 let receipt_builder = inner.receipt_builder;
321
322 if let Some(bumper) = self.bal_index_bumper {
323 bumper(inner.evm_mut().db_mut());
324 }
325
326 let (mut evm, result) = inner.finish()?;
327
328 if let Some(bumper) = self.bal_index_bumper {
333 bumper(evm.db_mut());
334 }
335
336 self.gas_used_offset += result.gas_used;
340 self.blob_gas_used_offset += result.blob_gas_used;
341 self.accumulated_requests.extend(result.requests);
342
343 let last_receipt_cumulative =
344 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
345 let seg_block_number = prev_segment.evm_env.block_env.number.saturating_to::<u64>();
346 debug!(
347 target: "engine::bb::evm",
348 prev_seg_idx,
349 seg_block_number,
350 segment_gas_used = result.gas_used,
351 gas_used_offset = self.gas_used_offset,
352 last_receipt_cumulative,
353 receipt_count = result.receipts.len(),
354 "Finished segment"
355 );
356
357 let ctx = evm.ctx_mut();
359 ctx.block = new_block_env;
360 ctx.cfg = new_cfg_env;
361
362 let mut new_inner =
366 EthBlockExecutor::new(evm, new_segment.ctx.clone(), spec, receipt_builder);
367
368 new_inner.receipts = result.receipts;
370
371 self.inner = Some(new_inner);
372
373 let new_block_number =
376 self.plan.segments[seg_idx].evm_env.block_env.number.saturating_to::<u64>();
377 self.reseed_block_hashes_for(new_block_number);
378
379 self.inner_mut().apply_pre_execution_changes()?;
382
383 trace!(target: "engine::bb::evm", "Started segment {seg_idx}");
384
385 Ok(())
386 }
387}
388
389impl<'a, DB, I, P, Spec> BlockExecutor for BbBlockExecutor<'a, DB, I, P, Spec>
390where
391 DB: StateDB,
392 I: Inspector<EthEvmContext<DB>>,
393 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
394 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
395 EthEvm<DB, I, P>: Evm<
396 DB = DB,
397 Tx = TxEnv,
398 HaltReason = HaltReason,
399 Error = EVMError<DB::Error>,
400 Spec = SpecId,
401 BlockEnv = BlockEnv,
402 >,
403 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
404{
405 type Transaction = TransactionSigned;
406 type Receipt = Receipt;
407 type Evm = EthEvm<DB, I, P>;
408 type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
409
410 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
411 self.initialize()?;
417 self.inner_mut().apply_pre_execution_changes()
418 }
419
420 fn execute_transaction_without_commit(
421 &mut self,
422 tx: impl ExecutableTx<Self>,
423 ) -> Result<Self::Result, BlockExecutionError> {
424 self.initialize()?;
425 self.inner_mut().execute_transaction_without_commit(tx)
426 }
427
428 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
429 let gas_used = self.inner_mut().commit_transaction(output);
430
431 let offset = self.gas_used_offset;
435 if offset > 0 &&
436 let Some(receipt) = self.inner_mut().receipts.last_mut()
437 {
438 receipt.cumulative_gas_used += offset;
439 }
440
441 self.plan.tx_counter += 1;
442
443 while self.plan.next_segment < self.plan.segments.len() &&
444 self.plan.tx_counter == self.plan.segments[self.plan.next_segment].start_tx
445 {
446 self.apply_segment_boundary().expect("must succeed");
447 }
448
449 gas_used
450 }
451
452 fn finish(
453 mut self,
454 ) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
455 let last_seg = self.plan.segments.last().unwrap();
459 let last_ctx = EthBlockExecutionCtx {
460 parent_hash: last_seg.ctx.parent_hash,
461 parent_beacon_block_root: last_seg.ctx.parent_beacon_block_root,
462 ommers: last_seg.ctx.ommers,
463 withdrawals: last_seg.ctx.withdrawals.clone(),
464 extra_data: last_seg.ctx.extra_data.clone(),
465 tx_count_hint: last_seg.ctx.tx_count_hint,
466 slot_number: last_seg.ctx.slot_number,
467 };
468 self.inner_mut().ctx = last_ctx;
469 let inner = self.inner.take().expect("inner executor must exist");
470 let (evm, mut result) = inner.finish()?;
471
472 let last_segment_gas = result.gas_used;
476 result.gas_used += self.gas_used_offset;
477 result.blob_gas_used += self.blob_gas_used_offset;
478
479 let last_receipt_cumulative =
480 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
481 debug!(
482 target: "engine::bb::evm",
483 last_segment_gas,
484 gas_used_offset = self.gas_used_offset,
485 total_gas_used = result.gas_used,
486 last_receipt_cumulative,
487 receipt_count = result.receipts.len(),
488 "Finished final segment"
489 );
490
491 if !self.accumulated_requests.is_empty() {
494 let mut merged = self.accumulated_requests;
495 merged.extend(result.requests);
496 result.requests = merged;
497 }
498
499 Ok((evm, result))
500 }
501
502 fn evm_mut(&mut self) -> &mut Self::Evm {
503 self.inner_mut().evm_mut()
504 }
505
506 fn evm(&self) -> &Self::Evm {
507 self.inner().evm()
508 }
509
510 fn receipts(&self) -> &[Self::Receipt] {
511 self.inner().receipts()
512 }
513}
514
515#[derive(Debug, Clone)]
522pub struct BbBlockExecutorFactory<Spec> {
523 receipt_builder: RethReceiptBuilder,
524 spec: Spec,
525 evm_factory: EthEvmFactory,
526}
527
528impl<Spec> BbBlockExecutorFactory<Spec> {
529 pub const fn new(
530 receipt_builder: RethReceiptBuilder,
531 spec: Spec,
532 evm_factory: EthEvmFactory,
533 ) -> Self {
534 Self { receipt_builder, spec, evm_factory }
535 }
536
537 pub const fn evm_factory(&self) -> &EthEvmFactory {
538 &self.evm_factory
539 }
540
541 pub const fn spec(&self) -> &Spec {
542 &self.spec
543 }
544
545 pub const fn receipt_builder(&self) -> &RethReceiptBuilder {
546 &self.receipt_builder
547 }
548
549 pub(crate) fn create_executor_with_seeder<'a, DB, I>(
550 &'a self,
551 evm: EthEvm<DB, I, PrecompilesMap>,
552 plan: BbEvmPlan<'a>,
553 block_hash_seeder: Option<BlockHashSeeder<DB>>,
554 bal_index_reader: Option<BalIndexReader<DB>>,
555 bal_index_bumper: Option<BalIndexBumper<DB>>,
556 bal_index_setter: Option<BalIndexSetter<DB>>,
557 ) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
558 where
559 Spec: alloy_evm::eth::spec::EthExecutorSpec,
560 DB: StateDB,
561 I: Inspector<EthEvmContext<DB>>,
562 {
563 BbBlockExecutor::new(
564 evm,
565 plan,
566 &self.spec,
567 self.receipt_builder,
568 block_hash_seeder,
569 bal_index_reader,
570 bal_index_bumper,
571 bal_index_setter,
572 )
573 }
574}
575
576impl<Spec> BlockExecutorFactory for BbBlockExecutorFactory<Spec>
577where
578 Spec: alloy_evm::eth::spec::EthExecutorSpec + 'static,
579 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
580{
581 type EvmFactory = EthEvmFactory;
582 type ExecutionCtx<'a> = BbEvmPlan<'a>;
583 type Transaction = TransactionSigned;
584 type Receipt = Receipt;
585 type TxExecutionResult = EthTxResult<
586 <EthEvmFactory as EvmFactory>::HaltReason,
587 <TransactionSigned as TransactionEnvelope>::TxType,
588 >;
589 type Executor<'a, DB: StateDB, I: Inspector<EthEvmContext<DB>>> =
590 BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>;
591
592 fn evm_factory(&self) -> &Self::EvmFactory {
593 &self.evm_factory
594 }
595
596 fn create_executor<'a, DB, I>(
597 &'a self,
598 evm: EthEvm<DB, I, PrecompilesMap>,
599 ctx: BbEvmPlan<'a>,
600 ) -> Self::Executor<'a, DB, I>
601 where
602 DB: StateDB,
603 I: Inspector<EthEvmContext<DB>>,
604 {
605 BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, None, None, None, None)
606 }
607}