1use crate::evm_config::BigBlockSegment;
10use alloy_eips::eip7685::Requests;
11use alloy_evm::{
12 block::{
13 BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
14 BlockExecutorFor, ExecutableTx, GasOutput, OnStateHook, StateChangeSource, StateDB,
15 },
16 eth::{EthBlockExecutionCtx, EthBlockExecutor, EthEvmContext, EthTxResult},
17 precompiles::PrecompilesMap,
18 Database, EthEvm, EthEvmFactory, Evm, FromRecoveredTx, FromTxWithEncoded,
19};
20use alloy_primitives::B256;
21use reth_ethereum_primitives::{Receipt, TransactionSigned};
22use reth_evm_ethereum::RethReceiptBuilder;
23use revm::{
24 context::{BlockEnv, TxEnv},
25 context_interface::result::{EVMError, HaltReason},
26 handler::PrecompileProvider,
27 interpreter::InterpreterResult,
28 primitives::hardfork::SpecId,
29 Inspector,
30};
31use std::sync::{Arc, Mutex};
32use tracing::{debug, trace};
33
34pub(crate) struct BbEvmPlan {
40 pub(crate) segments: Vec<BigBlockSegment>,
42 pub(crate) next_segment: usize,
44 pub(crate) tx_counter: usize,
46 pub(crate) block_hashes_to_seed: Vec<(u64, B256)>,
49}
50
51impl BbEvmPlan {
52 pub(crate) fn new(segments: Vec<BigBlockSegment>) -> Self {
54 let mut block_hashes_to_seed = Vec::new();
56 for seg in segments.iter().skip(1) {
57 let finished_block_number = seg.evm_env.block_env.number.saturating_to::<u64>() - 1;
58 let finished_block_hash = seg.ctx.parent_hash;
59 block_hashes_to_seed.push((finished_block_number, finished_block_hash));
60 }
61
62 Self { segments, next_segment: 1, tx_counter: 0, block_hashes_to_seed }
63 }
64
65 pub(crate) fn hashes_for_block(&self, block_number: u64) -> Vec<(u64, B256)> {
69 let min = block_number.saturating_sub(256);
70 self.block_hashes_to_seed
71 .iter()
72 .copied()
73 .filter(|(n, _)| *n >= min && *n < block_number)
74 .collect()
75 }
76}
77
78impl std::fmt::Debug for BbEvmPlan {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("BbEvmPlan")
81 .field("segments", &self.segments)
82 .field("next_segment", &self.next_segment)
83 .field("tx_counter", &self.tx_counter)
84 .field("block_hashes_to_seed", &self.block_hashes_to_seed)
85 .finish()
86 }
87}
88
89pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
99
100pub(crate) struct BbBlockExecutor<'a, DB, I, P, Spec>
112where
113 DB: Database,
114{
115 inner: Option<EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder>>,
117 plan: Option<BbEvmPlan>,
118 accumulated_requests: Requests,
121 gas_used_offset: u64,
124 blob_gas_used_offset: u64,
127 shared_hook: Arc<Mutex<Option<Box<dyn OnStateHook>>>>,
131 block_hash_seeder: Option<BlockHashSeeder<DB>>,
134}
135
136impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
137where
138 DB: StateDB,
139 I: Inspector<EthEvmContext<DB>>,
140 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
141 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
142 EthEvm<DB, I, P>: Evm<
143 DB = DB,
144 Tx = TxEnv,
145 HaltReason = HaltReason,
146 Error = EVMError<DB::Error>,
147 Spec = SpecId,
148 BlockEnv = BlockEnv,
149 >,
150 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
151{
152 pub(crate) fn new(
153 evm: EthEvm<DB, I, P>,
154 ctx: EthBlockExecutionCtx<'a>,
155 spec: Spec,
156 receipt_builder: RethReceiptBuilder,
157 plan: Option<BbEvmPlan>,
158 block_hash_seeder: Option<BlockHashSeeder<DB>>,
159 ) -> Self {
160 let inner = EthBlockExecutor::new(evm, ctx, spec, receipt_builder);
161 Self {
162 inner: Some(inner),
163 plan,
164 accumulated_requests: Requests::default(),
165 gas_used_offset: 0,
166 blob_gas_used_offset: 0,
167 shared_hook: Arc::new(Mutex::new(None)),
168 block_hash_seeder,
169 }
170 }
171
172 fn forwarding_hook(&self) -> Option<Box<dyn OnStateHook>> {
174 let shared = self.shared_hook.clone();
175 Some(Box::new(move |source: StateChangeSource, state: &revm::state::EvmState| {
176 if let Some(hook) = shared.lock().unwrap().as_mut() {
177 hook.on_state(source, state);
178 }
179 }))
180 }
181
182 const fn inner(&self) -> &EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
183 self.inner.as_ref().expect("inner executor must exist")
184 }
185
186 const fn inner_mut(
187 &mut self,
188 ) -> &mut EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
189 self.inner.as_mut().expect("inner executor must exist")
190 }
191
192 fn reseed_block_hashes_for(&mut self, block_number: u64) {
193 let Some(seeder) = self.block_hash_seeder else { return };
194 let hashes = match &self.plan {
195 Some(plan) => plan.hashes_for_block(block_number),
196 None => return,
197 };
198 seeder(self.inner_mut().evm_mut().db_mut(), &hashes);
199 }
200
201 fn maybe_apply_boundary(&mut self) -> Result<(), BlockExecutionError> {
202 loop {
203 let plan = match &self.plan {
204 Some(p) => p,
205 None => return Ok(()),
206 };
207
208 if plan.next_segment >= plan.segments.len() ||
209 plan.tx_counter != plan.segments[plan.next_segment].start_tx
210 {
211 return Ok(());
212 }
213
214 self.apply_segment_boundary()?;
215 }
216 }
217
218 fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
219 let plan = self.plan.as_mut().expect("plan must exist");
220 let seg_idx = plan.next_segment;
221 let prev_seg_idx = seg_idx - 1;
222
223 debug!(
224 target: "engine::bb::evm",
225 seg_idx,
226 tx_counter = plan.tx_counter,
227 "Applying segment boundary"
228 );
229
230 let prev_segment = &plan.segments[prev_seg_idx];
234 let prev_ctx = EthBlockExecutionCtx {
235 parent_hash: prev_segment.ctx.parent_hash,
236 parent_beacon_block_root: prev_segment.ctx.parent_beacon_block_root,
237 ommers: prev_segment.ctx.ommers,
238 withdrawals: prev_segment.ctx.withdrawals.clone(),
239 extra_data: prev_segment.ctx.extra_data.clone(),
240 tx_count_hint: prev_segment.ctx.tx_count_hint,
241 };
242
243 let new_segment = &plan.segments[seg_idx];
245 let new_block_env = new_segment.evm_env.block_env.clone();
246 let mut new_cfg_env = new_segment.evm_env.cfg_env.clone();
247 new_cfg_env.disable_base_fee = true;
248 let new_ctx = EthBlockExecutionCtx {
249 parent_hash: new_segment.ctx.parent_hash,
250 parent_beacon_block_root: new_segment.ctx.parent_beacon_block_root,
251 ommers: new_segment.ctx.ommers,
252 withdrawals: new_segment.ctx.withdrawals.clone(),
253 extra_data: new_segment.ctx.extra_data.clone(),
254 tx_count_hint: new_segment.ctx.tx_count_hint,
255 };
256
257 plan.next_segment += 1;
258
259 let mut inner = self.inner.take().expect("inner executor must exist");
263 inner.ctx = prev_ctx;
264 let spec = inner.spec.clone();
265 let receipt_builder = inner.receipt_builder;
266 let (mut evm, result) = inner.finish()?;
267
268 self.gas_used_offset += result.gas_used;
272 self.blob_gas_used_offset += result.blob_gas_used;
273 self.accumulated_requests.extend(result.requests);
274
275 let last_receipt_cumulative =
276 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
277 let seg_block_number = prev_segment.evm_env.block_env.number.saturating_to::<u64>();
278 debug!(
279 target: "engine::bb::evm",
280 prev_seg_idx,
281 seg_block_number,
282 segment_gas_used = result.gas_used,
283 gas_used_offset = self.gas_used_offset,
284 last_receipt_cumulative,
285 receipt_count = result.receipts.len(),
286 "Finished segment"
287 );
288
289 let ctx = evm.ctx_mut();
291 ctx.block = new_block_env;
292 ctx.cfg = new_cfg_env;
293
294 let mut new_inner = EthBlockExecutor::new(evm, new_ctx, spec, receipt_builder);
298
299 new_inner.receipts = result.receipts;
301
302 if self.shared_hook.lock().unwrap().is_some() {
305 new_inner.set_state_hook(self.forwarding_hook());
306 }
307
308 self.inner = Some(new_inner);
309
310 let new_block_number = self.plan.as_ref().unwrap().segments[seg_idx]
313 .evm_env
314 .block_env
315 .number
316 .saturating_to::<u64>();
317 self.reseed_block_hashes_for(new_block_number);
318
319 self.inner_mut().apply_pre_execution_changes()?;
321
322 trace!(target: "engine::bb::evm", "Started segment {seg_idx}");
323
324 Ok(())
325 }
326}
327
328impl<'a, DB, I, P, Spec> BlockExecutor for BbBlockExecutor<'a, DB, I, P, Spec>
329where
330 DB: StateDB,
331 I: Inspector<EthEvmContext<DB>>,
332 P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
333 Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
334 EthEvm<DB, I, P>: Evm<
335 DB = DB,
336 Tx = TxEnv,
337 HaltReason = HaltReason,
338 Error = EVMError<DB::Error>,
339 Spec = SpecId,
340 BlockEnv = BlockEnv,
341 >,
342 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
343{
344 type Transaction = TransactionSigned;
345 type Receipt = Receipt;
346 type Evm = EthEvm<DB, I, P>;
347 type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
348
349 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
350 if let Some(seg0) = self.plan.as_ref().map(|p| &p.segments[0]) {
356 let block_env = seg0.evm_env.block_env.clone();
357 let block_number = block_env.number.saturating_to::<u64>();
358 let mut cfg_env = seg0.evm_env.cfg_env.clone();
359 cfg_env.disable_base_fee = true;
360 let seg0_ctx = EthBlockExecutionCtx {
361 parent_hash: seg0.ctx.parent_hash,
362 parent_beacon_block_root: seg0.ctx.parent_beacon_block_root,
363 ommers: seg0.ctx.ommers,
364 withdrawals: seg0.ctx.withdrawals.clone(),
365 extra_data: seg0.ctx.extra_data.clone(),
366 tx_count_hint: seg0.ctx.tx_count_hint,
367 };
368
369 let inner = self.inner_mut();
370 let evm_ctx = inner.evm.ctx_mut();
371 evm_ctx.block = block_env;
372 evm_ctx.cfg = cfg_env;
373 inner.ctx = seg0_ctx;
374
375 self.reseed_block_hashes_for(block_number);
376 }
377
378 self.inner_mut().apply_pre_execution_changes()
379 }
380
381 fn execute_transaction_without_commit(
382 &mut self,
383 tx: impl ExecutableTx<Self>,
384 ) -> Result<Self::Result, BlockExecutionError> {
385 self.maybe_apply_boundary()?;
386 self.inner_mut().execute_transaction_without_commit(tx)
387 }
388
389 fn commit_transaction(
390 &mut self,
391 output: Self::Result,
392 ) -> Result<GasOutput, BlockExecutionError> {
393 let gas_used = self.inner_mut().commit_transaction(output)?;
394
395 let offset = self.gas_used_offset;
399 if offset > 0 &&
400 let Some(receipt) = self.inner_mut().receipts.last_mut()
401 {
402 receipt.cumulative_gas_used += offset;
403 }
404
405 if let Some(plan) = &mut self.plan {
406 plan.tx_counter += 1;
407 }
408 Ok(gas_used)
409 }
410
411 fn finish(
412 mut self,
413 ) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
414 if let Some(last_seg) = self.plan.as_ref().map(|p| p.segments.last().unwrap()) {
418 let last_ctx = EthBlockExecutionCtx {
419 parent_hash: last_seg.ctx.parent_hash,
420 parent_beacon_block_root: last_seg.ctx.parent_beacon_block_root,
421 ommers: last_seg.ctx.ommers,
422 withdrawals: last_seg.ctx.withdrawals.clone(),
423 extra_data: last_seg.ctx.extra_data.clone(),
424 tx_count_hint: last_seg.ctx.tx_count_hint,
425 };
426 self.inner_mut().ctx = last_ctx;
427 }
428 let inner = self.inner.take().expect("inner executor must exist");
429 let (evm, mut result) = inner.finish()?;
430
431 let last_segment_gas = result.gas_used;
435 result.gas_used += self.gas_used_offset;
436 result.blob_gas_used += self.blob_gas_used_offset;
437
438 let last_receipt_cumulative =
439 result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
440 debug!(
441 target: "engine::bb::evm",
442 last_segment_gas,
443 gas_used_offset = self.gas_used_offset,
444 total_gas_used = result.gas_used,
445 last_receipt_cumulative,
446 receipt_count = result.receipts.len(),
447 "Finished final segment"
448 );
449
450 if !self.accumulated_requests.is_empty() {
453 let mut merged = self.accumulated_requests;
454 merged.extend(result.requests);
455 result.requests = merged;
456 }
457
458 Ok((evm, result))
459 }
460
461 fn set_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) {
462 if self.plan.is_some() {
463 *self.shared_hook.lock().unwrap() = hook;
467 let fwd = self.forwarding_hook();
468 self.inner_mut().set_state_hook(fwd);
469 } else {
470 self.inner_mut().set_state_hook(hook);
471 }
472 }
473
474 fn evm_mut(&mut self) -> &mut Self::Evm {
475 self.inner_mut().evm_mut()
476 }
477
478 fn evm(&self) -> &Self::Evm {
479 self.inner().evm()
480 }
481
482 fn receipts(&self) -> &[Self::Receipt] {
483 self.inner().receipts()
484 }
485}
486
487#[derive(Debug, Clone)]
494pub struct BbBlockExecutorFactory<Spec> {
495 receipt_builder: RethReceiptBuilder,
496 spec: Spec,
497 evm_factory: EthEvmFactory,
498 pub(crate) staged_plan: Arc<Mutex<Option<BbEvmPlan>>>,
500}
501
502impl<Spec> BbBlockExecutorFactory<Spec> {
503 pub fn new(
504 receipt_builder: RethReceiptBuilder,
505 spec: Spec,
506 evm_factory: EthEvmFactory,
507 ) -> Self {
508 Self { receipt_builder, spec, evm_factory, staged_plan: Arc::new(Mutex::new(None)) }
509 }
510
511 pub const fn evm_factory(&self) -> &EthEvmFactory {
512 &self.evm_factory
513 }
514
515 pub const fn spec(&self) -> &Spec {
516 &self.spec
517 }
518
519 pub const fn receipt_builder(&self) -> &RethReceiptBuilder {
520 &self.receipt_builder
521 }
522
523 pub(crate) fn stage_plan(&self, plan: BbEvmPlan) {
524 *self.staged_plan.lock().unwrap() = Some(plan);
525 }
526
527 fn take_plan(&self) -> Option<BbEvmPlan> {
528 self.staged_plan.lock().unwrap().take()
529 }
530
531 pub(crate) fn create_executor_with_seeder<'a, DB, I>(
532 &'a self,
533 evm: EthEvm<DB, I, PrecompilesMap>,
534 ctx: EthBlockExecutionCtx<'a>,
535 block_hash_seeder: Option<BlockHashSeeder<DB>>,
536 ) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
537 where
538 Spec: alloy_evm::eth::spec::EthExecutorSpec,
539 DB: StateDB + 'a,
540 I: Inspector<EthEvmContext<DB>> + 'a,
541 {
542 let plan = self.take_plan();
543 BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, block_hash_seeder)
544 }
545}
546
547impl<Spec> BlockExecutorFactory for BbBlockExecutorFactory<Spec>
548where
549 Spec: alloy_evm::eth::spec::EthExecutorSpec + 'static,
550 TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
551{
552 type EvmFactory = EthEvmFactory;
553 type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>;
554 type Transaction = TransactionSigned;
555 type Receipt = Receipt;
556
557 fn evm_factory(&self) -> &Self::EvmFactory {
558 &self.evm_factory
559 }
560
561 fn create_executor<'a, DB, I>(
562 &'a self,
563 evm: EthEvm<DB, I, PrecompilesMap>,
564 ctx: EthBlockExecutionCtx<'a>,
565 ) -> impl BlockExecutorFor<'a, Self, DB, I>
566 where
567 DB: StateDB + 'a,
568 I: Inspector<EthEvmContext<DB>> + 'a,
569 {
570 let plan = self.take_plan();
571 BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None)
572 }
573}