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, OnStateHook, StateChangeSource, 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 std::sync::{Arc, Mutex};
33use tracing::{debug, trace};
34
35#[derive(Debug, Clone)]
41pub struct BbEvmPlan<'a> {
42 pub(crate) segments: Vec<BigBlockSegment<'a>>,
44 pub(crate) next_segment: usize,
46 pub(crate) tx_counter: usize,
48 pub(crate) block_hashes_to_seed: Vec<(u64, B256)>,
51}
52
53impl<'a> BbEvmPlan<'a> {
54 pub(crate) fn new(segments: Vec<BigBlockSegment<'a>>) -> Self {
56 let mut block_hashes_to_seed = Vec::new();
58 for seg in segments.iter().skip(1) {
59 let finished_block_number = seg.evm_env.block_env.number.saturating_to::<u64>() - 1;
60 let finished_block_hash = seg.ctx.parent_hash;
61 block_hashes_to_seed.push((finished_block_number, finished_block_hash));
62 }
63
64 Self { segments, next_segment: 1, tx_counter: 0, block_hashes_to_seed }
65 }
66
67 pub(crate) fn hashes_for_block(&self, block_number: u64) -> Vec<(u64, B256)> {
71 let min = block_number.saturating_sub(256);
72 self.block_hashes_to_seed
73 .iter()
74 .copied()
75 .filter(|(n, _)| *n >= min && *n < block_number)
76 .collect()
77 }
78
79 pub(crate) fn segment_index_for_tx(&self, tx_index: usize) -> usize {
81 self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
82 }
83}
84
85pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
95
96pub(crate) type BalIndexReader<DB> = fn(&DB) -> u64;
104
105pub(crate) type BalIndexBumper<DB> = fn(&mut DB);
115
116pub(crate) type BalIndexSetter<DB> = fn(&mut DB, u64);
126
127#[expect(missing_debug_implementations)]
139pub struct BbBlockExecutor<'a, DB, I, P, Spec>
140where
141 DB: Database,
142{
143 inner: Option<EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder>>,
145 plan: BbEvmPlan<'a>,
146 accumulated_requests: Requests,
149 gas_used_offset: u64,
152 blob_gas_used_offset: u64,
155 shared_hook: Arc<Mutex<Option<Box<dyn OnStateHook>>>>,
159 block_hash_seeder: Option<BlockHashSeeder<DB>>,
162 bal_index_reader: Option<BalIndexReader<DB>>,
164 bal_index_bumper: Option<BalIndexBumper<DB>>,
168 bal_index_setter: Option<BalIndexSetter<DB>>,
172 initialized: bool,
174}
175
176impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
177where
178 DB: StateDB,
179 I: Inspector<EthEvmContext<DB>>,
180 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
181 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
182 EthEvm<DB, I, P>: Evm<
183 DB = DB,
184 Tx = TxEnv,
185 HaltReason = HaltReason,
186 Error = EVMError<DB::Error>,
187 Spec = SpecId,
188 BlockEnv = BlockEnv,
189 >,
190 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
191{
192 #[expect(clippy::too_many_arguments)]
193 pub(crate) fn new(
194 evm: EthEvm<DB, I, P>,
195 plan: BbEvmPlan<'a>,
196 spec: Spec,
197 receipt_builder: RethReceiptBuilder,
198 block_hash_seeder: Option<BlockHashSeeder<DB>>,
199 bal_index_reader: Option<BalIndexReader<DB>>,
200 bal_index_bumper: Option<BalIndexBumper<DB>>,
201 bal_index_setter: Option<BalIndexSetter<DB>>,
202 ) -> Self {
203 let inner = EthBlockExecutor::new(evm, plan.segments[0].ctx.clone(), spec, receipt_builder);
204 Self {
205 inner: Some(inner),
206 plan,
207 accumulated_requests: Requests::default(),
208 gas_used_offset: 0,
209 blob_gas_used_offset: 0,
210 shared_hook: Arc::new(Mutex::new(None)),
211 block_hash_seeder,
212 bal_index_reader,
213 bal_index_bumper,
214 bal_index_setter,
215 initialized: false,
216 }
217 }
218
219 fn initialize(&mut self) -> Result<(), BlockExecutionError> {
229 if self.initialized {
230 return Ok(());
231 }
232
233 let segment_idx = if let Some(bal_index) = self
234 .bal_index_reader
235 .map(|reader| reader(self.inner().evm().db()))
236 .filter(|bal_index| *bal_index > 0)
237 {
238 let segment_idx = self.plan.segment_index_for_tx((bal_index - 1) as usize);
239
240 if let Some(setter) = self.bal_index_setter {
248 let renumbered = bal_index + 2 * segment_idx as u64;
249 setter(self.inner_mut().evm_mut().db_mut(), renumbered);
250 }
251
252 segment_idx
253 } else {
254 if self.initialized {
255 return Ok(());
256 }
257
258 self.initialized = true;
259 0
260 };
261 let segment = &self.plan.segments[segment_idx];
262 let block_env = segment.evm_env.block_env.clone();
263 let block_number = block_env.number.saturating_to::<u64>();
264 let mut cfg_env = segment.evm_env.cfg_env.clone();
265 cfg_env.disable_base_fee = true;
266
267 let inner = self.inner.as_mut().expect("inner executor must exist");
268 let evm_ctx = inner.evm.ctx_mut();
269 evm_ctx.block = block_env;
270 evm_ctx.cfg = cfg_env;
271 inner.ctx = segment.ctx.clone();
272
273 self.reseed_block_hashes_for(block_number);
274
275 Ok(())
276 }
277
278 fn forwarding_hook(&self) -> Option<Box<dyn OnStateHook>> {
280 let shared = self.shared_hook.clone();
281 Some(Box::new(move |source: StateChangeSource, state: &revm::state::EvmState| {
282 if let Some(hook) = shared.lock().unwrap().as_mut() {
283 hook.on_state(source, state);
284 }
285 }))
286 }
287
288 const fn inner(&self) -> &EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
289 self.inner.as_ref().expect("inner executor must exist")
290 }
291
292 const fn inner_mut(
293 &mut self,
294 ) -> &mut EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
295 self.inner.as_mut().expect("inner executor must exist")
296 }
297
298 fn reseed_block_hashes_for(&mut self, block_number: u64) {
299 let Some(seeder) = self.block_hash_seeder else { return };
300 let hashes = self.plan.hashes_for_block(block_number);
301 seeder(self.inner_mut().evm_mut().db_mut(), &hashes);
302 }
303
304 fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
305 let plan = &mut self.plan;
306 let seg_idx = plan.next_segment;
307 let prev_seg_idx = seg_idx - 1;
308
309 debug!(
310 target: "engine::bb::evm",
311 seg_idx,
312 tx_counter = plan.tx_counter,
313 "Applying segment boundary"
314 );
315
316 let prev_segment = &plan.segments[prev_seg_idx];
320
321 let new_segment = &plan.segments[seg_idx];
323 let new_block_env = new_segment.evm_env.block_env.clone();
324 let mut new_cfg_env = new_segment.evm_env.cfg_env.clone();
325 new_cfg_env.disable_base_fee = true;
326
327 plan.next_segment += 1;
328
329 let mut inner = self.inner.take().expect("inner executor must exist");
334 inner.ctx = prev_segment.ctx.clone();
335 let spec = inner.spec.clone();
336 let receipt_builder = inner.receipt_builder;
337
338 if let Some(bumper) = self.bal_index_bumper {
339 bumper(inner.evm_mut().db_mut());
340 }
341
342 let (mut evm, result) = inner.finish()?;
343
344 if let Some(bumper) = self.bal_index_bumper {
349 bumper(evm.db_mut());
350 }
351
352 self.gas_used_offset += result.gas_used;
356 self.blob_gas_used_offset += result.blob_gas_used;
357 self.accumulated_requests.extend(result.requests);
358
359 let last_receipt_cumulative =
360 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
361 let seg_block_number = prev_segment.evm_env.block_env.number.saturating_to::<u64>();
362 debug!(
363 target: "engine::bb::evm",
364 prev_seg_idx,
365 seg_block_number,
366 segment_gas_used = result.gas_used,
367 gas_used_offset = self.gas_used_offset,
368 last_receipt_cumulative,
369 receipt_count = result.receipts.len(),
370 "Finished segment"
371 );
372
373 let ctx = evm.ctx_mut();
375 ctx.block = new_block_env;
376 ctx.cfg = new_cfg_env;
377
378 let mut new_inner =
382 EthBlockExecutor::new(evm, new_segment.ctx.clone(), spec, receipt_builder);
383
384 new_inner.receipts = result.receipts;
386
387 if self.shared_hook.lock().unwrap().is_some() {
390 new_inner.set_state_hook(self.forwarding_hook());
391 }
392
393 self.inner = Some(new_inner);
394
395 let new_block_number =
398 self.plan.segments[seg_idx].evm_env.block_env.number.saturating_to::<u64>();
399 self.reseed_block_hashes_for(new_block_number);
400
401 self.inner_mut().apply_pre_execution_changes()?;
404
405 trace!(target: "engine::bb::evm", "Started segment {seg_idx}");
406
407 Ok(())
408 }
409}
410
411impl<'a, DB, I, P, Spec> BlockExecutor for BbBlockExecutor<'a, DB, I, P, Spec>
412where
413 DB: StateDB,
414 I: Inspector<EthEvmContext<DB>>,
415 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
416 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
417 EthEvm<DB, I, P>: Evm<
418 DB = DB,
419 Tx = TxEnv,
420 HaltReason = HaltReason,
421 Error = EVMError<DB::Error>,
422 Spec = SpecId,
423 BlockEnv = BlockEnv,
424 >,
425 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
426{
427 type Transaction = TransactionSigned;
428 type Receipt = Receipt;
429 type Evm = EthEvm<DB, I, P>;
430 type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
431
432 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
433 self.initialize()?;
439 self.inner_mut().apply_pre_execution_changes()
440 }
441
442 fn execute_transaction_without_commit(
443 &mut self,
444 tx: impl ExecutableTx<Self>,
445 ) -> Result<Self::Result, BlockExecutionError> {
446 self.initialize()?;
447 self.inner_mut().execute_transaction_without_commit(tx)
448 }
449
450 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
451 let gas_used = self.inner_mut().commit_transaction(output);
452
453 let offset = self.gas_used_offset;
457 if offset > 0 &&
458 let Some(receipt) = self.inner_mut().receipts.last_mut()
459 {
460 receipt.cumulative_gas_used += offset;
461 }
462
463 self.plan.tx_counter += 1;
464
465 while self.plan.next_segment < self.plan.segments.len() &&
466 self.plan.tx_counter == self.plan.segments[self.plan.next_segment].start_tx
467 {
468 self.apply_segment_boundary().expect("must succeed");
469 }
470
471 gas_used
472 }
473
474 fn finish(
475 mut self,
476 ) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
477 let last_seg = self.plan.segments.last().unwrap();
481 let last_ctx = EthBlockExecutionCtx {
482 parent_hash: last_seg.ctx.parent_hash,
483 parent_beacon_block_root: last_seg.ctx.parent_beacon_block_root,
484 ommers: last_seg.ctx.ommers,
485 withdrawals: last_seg.ctx.withdrawals.clone(),
486 extra_data: last_seg.ctx.extra_data.clone(),
487 tx_count_hint: last_seg.ctx.tx_count_hint,
488 slot_number: last_seg.ctx.slot_number,
489 };
490 self.inner_mut().ctx = last_ctx;
491 let inner = self.inner.take().expect("inner executor must exist");
492 let (evm, mut result) = inner.finish()?;
493
494 let last_segment_gas = result.gas_used;
498 result.gas_used += self.gas_used_offset;
499 result.blob_gas_used += self.blob_gas_used_offset;
500
501 let last_receipt_cumulative =
502 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
503 debug!(
504 target: "engine::bb::evm",
505 last_segment_gas,
506 gas_used_offset = self.gas_used_offset,
507 total_gas_used = result.gas_used,
508 last_receipt_cumulative,
509 receipt_count = result.receipts.len(),
510 "Finished final segment"
511 );
512
513 if !self.accumulated_requests.is_empty() {
516 let mut merged = self.accumulated_requests;
517 merged.extend(result.requests);
518 result.requests = merged;
519 }
520
521 Ok((evm, result))
522 }
523
524 fn set_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) {
525 *self.shared_hook.lock().unwrap() = hook;
529 let fwd = self.forwarding_hook();
530 self.inner_mut().set_state_hook(fwd);
531 }
532
533 fn evm_mut(&mut self) -> &mut Self::Evm {
534 self.inner_mut().evm_mut()
535 }
536
537 fn evm(&self) -> &Self::Evm {
538 self.inner().evm()
539 }
540
541 fn receipts(&self) -> &[Self::Receipt] {
542 self.inner().receipts()
543 }
544}
545
546#[derive(Debug, Clone)]
553pub struct BbBlockExecutorFactory<Spec> {
554 receipt_builder: RethReceiptBuilder,
555 spec: Spec,
556 evm_factory: EthEvmFactory,
557}
558
559impl<Spec> BbBlockExecutorFactory<Spec> {
560 pub const fn new(
561 receipt_builder: RethReceiptBuilder,
562 spec: Spec,
563 evm_factory: EthEvmFactory,
564 ) -> Self {
565 Self { receipt_builder, spec, evm_factory }
566 }
567
568 pub const fn evm_factory(&self) -> &EthEvmFactory {
569 &self.evm_factory
570 }
571
572 pub const fn spec(&self) -> &Spec {
573 &self.spec
574 }
575
576 pub const fn receipt_builder(&self) -> &RethReceiptBuilder {
577 &self.receipt_builder
578 }
579
580 pub(crate) fn create_executor_with_seeder<'a, DB, I>(
581 &'a self,
582 evm: EthEvm<DB, I, PrecompilesMap>,
583 plan: BbEvmPlan<'a>,
584 block_hash_seeder: Option<BlockHashSeeder<DB>>,
585 bal_index_reader: Option<BalIndexReader<DB>>,
586 bal_index_bumper: Option<BalIndexBumper<DB>>,
587 bal_index_setter: Option<BalIndexSetter<DB>>,
588 ) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
589 where
590 Spec: alloy_evm::eth::spec::EthExecutorSpec,
591 DB: StateDB,
592 I: Inspector<EthEvmContext<DB>>,
593 {
594 BbBlockExecutor::new(
595 evm,
596 plan,
597 &self.spec,
598 self.receipt_builder,
599 block_hash_seeder,
600 bal_index_reader,
601 bal_index_bumper,
602 bal_index_setter,
603 )
604 }
605}
606
607impl<Spec> BlockExecutorFactory for BbBlockExecutorFactory<Spec>
608where
609 Spec: alloy_evm::eth::spec::EthExecutorSpec + 'static,
610 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
611{
612 type EvmFactory = EthEvmFactory;
613 type ExecutionCtx<'a> = BbEvmPlan<'a>;
614 type Transaction = TransactionSigned;
615 type Receipt = Receipt;
616 type TxExecutionResult = EthTxResult<
617 <EthEvmFactory as EvmFactory>::HaltReason,
618 <TransactionSigned as TransactionEnvelope>::TxType,
619 >;
620 type Executor<'a, DB: StateDB, I: Inspector<EthEvmContext<DB>>> =
621 BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>;
622
623 fn evm_factory(&self) -> &Self::EvmFactory {
624 &self.evm_factory
625 }
626
627 fn create_executor<'a, DB, I>(
628 &'a self,
629 evm: EthEvm<DB, I, PrecompilesMap>,
630 ctx: BbEvmPlan<'a>,
631 ) -> Self::Executor<'a, DB, I>
632 where
633 DB: StateDB,
634 I: Inspector<EthEvmContext<DB>>,
635 {
636 BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, None, None, None, None)
637 }
638}