1use reth_execution_types::{BlockState, StateUpdate};
3
4use crate::{
5 execution::{
6 base_block_reward, block_requests_from_receipts, commit_detached_transaction,
7 commit_pending_state, execute_transaction_without_commit, map_transaction_error,
8 post_block_balance_state_changes, post_execution_system_call_state_changes,
9 pre_execution_system_call_state_changes, BlockExecutionContext, BlockSystemCalls,
10 },
11 factory::{EthBlockExecutorFactory, EvmFactory},
12 EthBlockExecutionCtx, EthEvmEnv, RethReceiptBuilder,
13};
14use alloc::{boxed::Box, sync::Arc, vec::Vec};
15use alloy_consensus::{
16 transaction::{TransactionEnvelope, TxHashRef},
17 Header, Transaction, TxType as EthTxType,
18};
19use alloy_eip7928::{BlockAccessIndex, BlockAccessList};
20use alloy_eips::{eip2718::Typed2718, eip4895::Withdrawal, eip7685::Requests};
21use alloy_primitives::{Address, B256};
22use evm2::{
23 ethereum::TxEnvelope, evm::Bal, interpreter::Host, BaseEvmTypes, Evm, EvmTypes,
24 TxResultWithState,
25};
26use reth_chainspec::EthChainSpec;
27use reth_ethereum_forks::{EthereumHardfork, EthereumHardforks};
28use reth_ethereum_primitives::{Receipt, TransactionSigned};
29use reth_evm::{
30 BlockExecutionError, BlockExecutionOutput, BlockExecutor, BlockTransactionResult,
31 BlockValidationError, ExecutorTx, GasOutput, ReceiptBuilder, ReceiptBuilderCtx, RecoveredTx,
32};
33use reth_execution_types::BlockExecutionResult;
34
35#[expect(missing_debug_implementations)]
37pub struct EthBlockExecutor<'a, T = BaseEvmTypes, R = RethReceiptBuilder>
38where
39 T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
40 R: ReceiptBuilder,
41{
42 pub evm: Evm<'a, T>,
44 ctx: EthBlockExecutionCtx<'a>,
45 receipt_builder: R,
46 spec_id: evm2::SpecId,
47 base_block_reward: Option<u128>,
48 dao_fork_transition: bool,
49 deposit_contract_address: Option<Address>,
50 pub block_state: BlockState,
52 state_update_hook: StateUpdateHook,
53 pub receipts: Vec<R::Receipt>,
55 pub cumulative_gas_used: u64,
57 block_regular_gas_used: u64,
58 block_state_gas_used: u64,
59 separate_block_gas: bool,
60 pub blob_gas_used: u64,
62 bal_index_offset: u64,
63}
64
65#[derive(Debug)]
67pub struct EthTransactionResultWithState<T = BaseEvmTypes, TxType = EthTxType>
68where
69 T: EvmTypes,
70{
71 result: TxResultWithState<T>,
72 tx_type: TxType,
73 blob_gas_used: u64,
74}
75
76impl<T: EvmTypes, TxType> BlockTransactionResult<T> for EthTransactionResultWithState<T, TxType> {
77 fn result(&self) -> &TxResultWithState<T> {
78 &self.result
79 }
80}
81
82impl<T: EvmTypes, TxType> EthTransactionResultWithState<T, TxType> {
83 pub const fn new(result: TxResultWithState<T>, tx_type: TxType, blob_gas_used: u64) -> Self {
85 Self { result, tx_type, blob_gas_used }
86 }
87
88 pub const fn result(&self) -> &TxResultWithState<T> {
90 &self.result
91 }
92}
93
94type StateUpdateHook = Option<Box<dyn FnMut(StateUpdate) + Send>>;
95
96impl<'a, T, R> EthBlockExecutor<'a, T, R>
97where
98 T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
99 R: ReceiptBuilder,
100{
101 pub fn new<C>(
103 mut evm: Evm<'a, T>,
104 ctx: EthBlockExecutionCtx<'a>,
105 chain_spec: &C,
106 receipt_builder: R,
107 ) -> Self
108 where
109 C: EthChainSpec + EthereumHardforks + ?Sized,
110 {
111 #[allow(clippy::useless_conversion)]
114 let spec_id = evm.spec_id().into();
115 let block_number = evm.block_env().number.to::<u64>();
116 let separate_block_gas = evm.version().feature(evm2::EvmFeatures::EIP8037);
117 let dao_fork_transition = chain_spec
118 .ethereum_fork_activation(EthereumHardfork::Dao)
119 .transitions_at_block(block_number);
120 let receipts = Vec::with_capacity(ctx.tx_count_hint.unwrap_or_default());
121
122 Self {
123 evm,
124 ctx,
125 receipt_builder,
126 spec_id,
127 base_block_reward: base_block_reward(chain_spec, block_number),
128 dao_fork_transition,
129 deposit_contract_address: chain_spec
130 .deposit_contract()
131 .map(|contract| contract.address),
132 block_state: BlockState::new(),
133 state_update_hook: None,
134 receipts,
135 cumulative_gas_used: 0,
136 block_regular_gas_used: 0,
137 block_state_gas_used: 0,
138 separate_block_gas,
139 blob_gas_used: 0,
140 bal_index_offset: 0,
141 }
142 }
143
144 const fn block_context<'ctx>(
145 deposit_contract_address: Option<Address>,
146 parent_hash: B256,
147 parent_beacon_block_root: Option<B256>,
148 ommers: &'ctx [Header],
149 withdrawals: Option<&'ctx [Withdrawal]>,
150 ) -> BlockExecutionContext<'ctx> {
151 BlockExecutionContext {
152 system_calls: Some(BlockSystemCalls { parent_hash, parent_beacon_block_root }),
153 ommers: Some(ommers),
154 withdrawals,
155 deposit_contract_address,
156 }
157 }
158
159 const fn block_access_list_builder_enabled(&self) -> bool {
160 self.evm.state().bal_builder().is_some()
161 }
162
163 pub const fn context(&self) -> &EthBlockExecutionCtx<'a> {
165 &self.ctx
166 }
167
168 pub const fn block_state(&self) -> &BlockState {
170 &self.block_state
171 }
172
173 pub fn commit_pending_state(&mut self, state: &evm2::evm::PendingState) {
175 let stream_state = self.state_update_hook.is_some();
176 commit_pending_state(
177 &mut self.evm,
178 &mut self.block_state,
179 stream_state,
180 &mut |state| emit_state(&mut self.state_update_hook, state),
181 state,
182 );
183 }
184
185 pub const fn set_spec_id(&mut self, spec_id: evm2::SpecId) {
187 self.spec_id = spec_id;
188 }
189
190 #[inline]
191 const fn set_transaction_block_access_index(&mut self) {
192 if self.block_access_list_builder_enabled() {
193 let index = self.receipts.len() as u64 + 1 + self.bal_index_offset;
194 self.evm.state_mut().set_bal_index(BlockAccessIndex::new(index));
195 }
196 }
197}
198
199impl<'a, T, R> BlockExecutor for EthBlockExecutor<'a, T, R>
200where
201 T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
202 R: ReceiptBuilder,
203{
204 type Transaction = R::Transaction;
205 type Receipt = R::Receipt;
206 type Evm = Evm<'a, T>;
207 type TransactionResultWithState =
208 EthTransactionResultWithState<T, <R::Transaction as TransactionEnvelope>::TxType>;
209 type BlockAccessList = Bal;
210
211 fn evm(&self) -> &Self::Evm {
212 &self.evm
213 }
214
215 fn evm_mut(&mut self) -> &mut Self::Evm {
216 &mut self.evm
217 }
218
219 fn set_state_hook(&mut self, hook: impl FnMut(StateUpdate) + Send + 'static) -> bool {
220 self.state_update_hook = Some(Box::new(hook));
221 true
222 }
223
224 fn convert_block_access_list(
225 block_access_list: &BlockAccessList,
226 ) -> Result<Self::BlockAccessList, BlockExecutionError> {
227 Bal::try_from(block_access_list.as_slice()).map_err(BlockExecutionError::other)
228 }
229
230 fn set_block_access_list(
231 &mut self,
232 block_access_list: alloc::sync::Arc<Self::BlockAccessList>,
233 ) {
234 self.evm.state_mut().set_bal(block_access_list);
235 }
236
237 fn set_block_access_index(&mut self, index: BlockAccessIndex) {
238 self.evm.state_mut().set_bal_index(index);
239 }
240
241 fn enable_block_access_list_builder(&mut self) {
242 self.evm.state_mut().enable_bal_builder();
243 self.evm.state_mut().reset_bal_index();
244 }
245
246 fn take_block_access_list(&mut self) -> Option<BlockAccessList> {
247 self.evm.state_mut().take_bal_builder().map(Into::into)
248 }
249
250 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
251 let context = Self::block_context(
252 self.deposit_contract_address,
253 self.ctx.parent_hash,
254 self.ctx.parent_beacon_block_root,
255 self.ctx.ommers,
256 None,
257 );
258 let block_number = self.evm.block_env().number.to::<u64>();
259 let stream_state = self.state_update_hook.is_some();
260 pre_execution_system_call_state_changes(
261 &mut self.evm,
262 &mut self.block_state,
263 stream_state,
264 &mut |state| emit_state(&mut self.state_update_hook, state),
265 self.spec_id,
266 block_number,
267 context,
268 )
269 }
270
271 fn into_state(self) -> reth_evm::BundleState {
272 self.block_state.into_bundle()
273 }
274
275 fn validate_transaction_gas_limit(
276 &mut self,
277 transaction_gas_limit: u64,
278 ) -> Result<(), BlockExecutionError> {
279 let block_gas_limit = self.evm.block_env().gas_limit.to::<u64>();
280 let unavailable = if self.separate_block_gas {
281 let regular_available = block_gas_limit.saturating_sub(self.block_regular_gas_used);
282 let state_available = block_gas_limit.saturating_sub(self.block_state_gas_used);
283 let regular_limit = transaction_gas_limit.min(self.evm.version().tx_gas_limit_cap);
284 if regular_limit > regular_available {
285 Some((transaction_gas_limit, regular_available))
286 } else if transaction_gas_limit > state_available {
287 Some((transaction_gas_limit, state_available))
288 } else {
289 None
290 }
291 } else {
292 let available = block_gas_limit.saturating_sub(self.cumulative_gas_used);
293 let regular_limit = transaction_gas_limit.min(self.evm.version().tx_gas_limit_cap);
294 (regular_limit > available).then_some((transaction_gas_limit, available))
295 };
296 if let Some((transaction_gas_limit, block_available_gas)) = unavailable {
297 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
298 transaction_gas_limit,
299 block_available_gas,
300 }
301 .into())
302 }
303 Ok(())
304 }
305
306 fn execute_transaction_with_commit_condition(
307 &mut self,
308 transaction: impl ExecutorTx<Self>,
309 commit: impl FnOnce(&evm2::TxResult<T>, evm2::TxStateChanges<'_>) -> reth_evm::CommitChanges,
310 ) -> Result<Option<GasOutput>, BlockExecutionError> {
311 let (transaction, tx) = transaction.into_parts();
312 self.set_transaction_block_access_index();
313 self.validate_transaction_gas_limit(tx.tx().gas_limit())?;
314 let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
315 let tx_type = tx.tx().tx_type();
316 let Some(outcome) = crate::execution::execute_transaction_with_condition(
317 &mut self.evm,
318 &mut self.block_state,
319 self.state_update_hook.is_some(),
320 &mut |state| emit_state(&mut self.state_update_hook, state),
321 &transaction,
322 commit,
323 )
324 .map_err(|error| map_transaction_error(error, *tx.tx().tx_hash()))?
325 else {
326 return Ok(None);
327 };
328 let tx_gas_used = outcome.tx_gas_used();
329 let regular_gas_used = outcome.execution_gas_spent();
330 let state_gas_used = outcome.state_gas_spent();
331 self.block_regular_gas_used = self.block_regular_gas_used.saturating_add(regular_gas_used);
332 self.block_state_gas_used = self.block_state_gas_used.saturating_add(state_gas_used);
333 self.cumulative_gas_used += tx_gas_used;
334 self.blob_gas_used += blob_gas_used;
335 self.receipts.push(self.receipt_builder.build_receipt::<T>(ReceiptBuilderCtx {
336 tx_type,
337 result: outcome,
338 cumulative_gas_used: self.cumulative_gas_used,
339 }));
340 Ok(Some(GasOutput::new_with_regular(tx_gas_used, regular_gas_used, state_gas_used)))
341 }
342
343 fn execute_transaction_without_commit(
344 &mut self,
345 transaction: impl ExecutorTx<Self>,
346 ) -> Result<Self::TransactionResultWithState, BlockExecutionError> {
347 let (transaction, tx) = transaction.into_parts();
348 self.set_transaction_block_access_index();
349 let transaction_gas_limit = tx.tx().gas_limit();
350 self.validate_transaction_gas_limit(transaction_gas_limit)?;
351 let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
352 let tx_type = tx.tx().tx_type();
353 let result = execute_transaction_without_commit(&mut self.evm, &transaction)
354 .map_err(|err| map_transaction_error(err, *tx.tx().tx_hash()))?;
355 Ok(EthTransactionResultWithState::new(result, tx_type, blob_gas_used))
356 }
357
358 fn commit_transaction(
359 &mut self,
360 output: Self::TransactionResultWithState,
361 ) -> Result<GasOutput, BlockExecutionError> {
362 let EthTransactionResultWithState { result, tx_type, blob_gas_used } = output;
363 self.set_transaction_block_access_index();
364 let stream_state = self.state_update_hook.is_some();
365 let outcome = commit_detached_transaction(
366 &mut self.evm,
367 &mut self.block_state,
368 stream_state,
369 &mut |state| emit_state(&mut self.state_update_hook, state),
370 result,
371 );
372 let tx_gas_used = outcome.tx_gas_used();
373 let regular_gas_used = outcome.execution_gas_spent();
374 let state_gas_used = outcome.state_gas_spent();
375 self.block_regular_gas_used = self.block_regular_gas_used.saturating_add(regular_gas_used);
376 self.block_state_gas_used = self.block_state_gas_used.saturating_add(state_gas_used);
377 self.cumulative_gas_used += tx_gas_used;
378 self.blob_gas_used += blob_gas_used;
379 self.receipts.push(self.receipt_builder.build_receipt::<T>(ReceiptBuilderCtx {
380 tx_type,
381 result: outcome,
382 cumulative_gas_used: self.cumulative_gas_used,
383 }));
384 Ok(GasOutput::new_with_regular(tx_gas_used, regular_gas_used, state_gas_used))
385 }
386
387 fn receipts(&self) -> &[Self::Receipt] {
388 &self.receipts
389 }
390
391 fn finish_with_block_access_list(
392 self,
393 ) -> Result<(BlockExecutionOutput<Self::Receipt>, Option<BlockAccessList>), BlockExecutionError>
394 {
395 let (output, bal) = self.finish_with_prepared_block_access_list()?;
396 Ok((output, bal.map(Into::into)))
397 }
398
399 fn finish_with_prepared_block_access_list(
400 mut self,
401 ) -> Result<(BlockExecutionOutput<Self::Receipt>, Option<Bal>), BlockExecutionError> {
402 self.set_transaction_block_access_index();
403 let context = Self::block_context(
404 self.deposit_contract_address,
405 self.ctx.parent_hash,
406 self.ctx.parent_beacon_block_root,
407 self.ctx.ommers,
408 None,
409 );
410 let mut requests = block_requests_from_receipts(self.spec_id, context, &self.receipts)?;
411 let stream_state = self.state_update_hook.is_some();
412 post_execution_system_call_state_changes(
413 &mut self.evm,
414 &mut self.block_state,
415 stream_state,
416 &mut |state| emit_state(&mut self.state_update_hook, state),
417 self.spec_id,
418 context,
419 &mut requests,
420 )?;
421
422 let withdrawals =
423 self.ctx.withdrawals.clone().filter(|_| self.spec_id.enables(evm2::SpecId::SHANGHAI));
424 let context = Self::block_context(
425 self.deposit_contract_address,
426 self.ctx.parent_hash,
427 self.ctx.parent_beacon_block_root,
428 self.ctx.ommers,
429 withdrawals.as_deref(),
430 );
431 let block_number = self.evm.block_env().number.to::<u64>();
432 let block_beneficiary = self.evm.block_env().beneficiary;
433 post_block_balance_state_changes(
434 &mut self.evm,
435 &mut self.block_state,
436 stream_state,
437 &mut |state| emit_state(&mut self.state_update_hook, state),
438 self.base_block_reward,
439 self.dao_fork_transition,
440 block_number,
441 block_beneficiary,
442 context.ommers,
443 context.withdrawals,
444 )?;
445
446 let block_access_list = self.evm.state_mut().take_bal_builder();
447 let block_gas_used = final_block_gas_used(
448 self.separate_block_gas,
449 self.cumulative_gas_used,
450 self.block_regular_gas_used,
451 self.block_state_gas_used,
452 );
453 let output = BlockExecutionOutput::new(
454 BlockExecutionResult {
455 receipts: self.receipts,
456 requests,
457 gas_used: block_gas_used,
458 blob_gas_used: self.blob_gas_used,
459 },
460 self.block_state.into_bundle(),
461 );
462
463 Ok((output, block_access_list))
464 }
465}
466
467pub struct EthBigBlockSegment<'a, T = BaseEvmTypes>
469where
470 T: EvmTypes<Tx = TxEnvelope>,
471{
472 pub start_tx: usize,
474 pub evm_env: EthEvmEnv<T>,
476 pub ctx: EthBlockExecutionCtx<'a>,
478}
479
480impl<'a, T> Clone for EthBigBlockSegment<'a, T>
481where
482 T: EvmTypes<Tx = TxEnvelope>,
483{
484 fn clone(&self) -> Self {
485 Self { start_tx: self.start_tx, evm_env: self.evm_env.clone(), ctx: self.ctx.clone() }
486 }
487}
488
489impl<'a, T> core::fmt::Debug for EthBigBlockSegment<'a, T>
490where
491 T: EvmTypes<Tx = TxEnvelope>,
492 T::SpecId: core::fmt::Debug,
493{
494 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
495 f.debug_struct("EthBigBlockSegment")
496 .field("start_tx", &self.start_tx)
497 .field("evm_env", &self.evm_env)
498 .field("ctx", &self.ctx)
499 .finish()
500 }
501}
502
503pub struct EthBigBlockPlan<'a, T = BaseEvmTypes>
505where
506 T: EvmTypes<Tx = TxEnvelope>,
507{
508 pub segments: Vec<EthBigBlockSegment<'a, T>>,
510 pub transaction_count: usize,
512 pub block_hashes: Vec<(u64, B256)>,
514}
515
516impl<'a, T> Clone for EthBigBlockPlan<'a, T>
517where
518 T: EvmTypes<Tx = TxEnvelope>,
519{
520 fn clone(&self) -> Self {
521 Self {
522 segments: self.segments.clone(),
523 transaction_count: self.transaction_count,
524 block_hashes: self.block_hashes.clone(),
525 }
526 }
527}
528
529impl<'a, T> core::fmt::Debug for EthBigBlockPlan<'a, T>
530where
531 T: EvmTypes<Tx = TxEnvelope>,
532 T::SpecId: core::fmt::Debug,
533{
534 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
535 f.debug_struct("EthBigBlockPlan")
536 .field("segments", &self.segments)
537 .field("transaction_count", &self.transaction_count)
538 .field("block_hashes", &self.block_hashes)
539 .finish()
540 }
541}
542
543impl<'a, T> EthBigBlockPlan<'a, T>
544where
545 T: EvmTypes<Tx = TxEnvelope>,
546{
547 pub fn new(
549 segments: Vec<EthBigBlockSegment<'a, T>>,
550 prior_block_hashes: Vec<(u64, B256)>,
551 transaction_count: usize,
552 ) -> Self {
553 let mut block_hashes = segments
554 .iter()
555 .skip(1)
556 .map(|segment| {
557 (
558 segment.evm_env.block.number.to::<u64>().saturating_sub(1),
559 segment.ctx.parent_hash,
560 )
561 })
562 .collect::<Vec<_>>();
563 block_hashes.extend(prior_block_hashes);
564 block_hashes.sort_unstable_by_key(|(number, _)| *number);
565 Self { segments, transaction_count, block_hashes }
566 }
567
568 fn hashes_for_block(&self, block_number: u64) -> impl Iterator<Item = (u64, B256)> + '_ {
569 let min = block_number.saturating_sub(256);
570 self.block_hashes
571 .iter()
572 .copied()
573 .filter(move |(number, _)| *number >= min && *number < block_number)
574 }
575
576 fn segment_index_for_tx(&self, tx_index: usize) -> usize {
577 self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
578 }
579}
580
581struct FinishedBigBlockSegment {
582 requests: Requests,
583 gas_used: u64,
584 blob_gas_used: u64,
585}
586
587#[expect(missing_debug_implementations)]
589pub struct EthBigBlockExecutor<'a, C, F = crate::factory::RethEvmFactory>
590where
591 F: EvmFactory,
592 F::Types: EvmTypes<Tx = TxEnvelope>,
593 <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
594 <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
595{
596 inner: EthBlockExecutor<'a, F::Types, &'a RethReceiptBuilder>,
597 factory: &'a EthBlockExecutorFactory<RethReceiptBuilder, C, F>,
598 chain_spec: Arc<C>,
599 plan: EthBigBlockPlan<'a, F::Types>,
600 next_segment: usize,
601 tx_counter: usize,
602 segment_receipt_start: usize,
603 worker_segment: Option<usize>,
604 initialized: bool,
605 requests: Requests,
606 gas_used_offset: u64,
607 receipt_gas_used_offset: u64,
608 blob_gas_used_offset: u64,
609}
610
611impl<'a, C, F> EthBigBlockExecutor<'a, C, F>
612where
613 C: EthereumHardforks,
614 F: EvmFactory,
615 F::Types: EvmTypes<Tx = TxEnvelope>,
616 <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
617 <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
618{
619 pub(crate) fn new(
620 inner: EthBlockExecutor<'a, F::Types, &'a RethReceiptBuilder>,
621 factory: &'a EthBlockExecutorFactory<RethReceiptBuilder, C, F>,
622 chain_spec: Arc<C>,
623 plan: EthBigBlockPlan<'a, F::Types>,
624 ) -> Self {
625 assert!(!plan.segments.is_empty(), "big-block execution requires at least one segment");
626 Self {
627 inner,
628 factory,
629 chain_spec,
630 plan,
631 next_segment: 1,
632 tx_counter: 0,
633 segment_receipt_start: 0,
634 worker_segment: None,
635 initialized: false,
636 requests: Requests::default(),
637 gas_used_offset: 0,
638 receipt_gas_used_offset: 0,
639 blob_gas_used_offset: 0,
640 }
641 }
642
643 fn configure_segment(&mut self, segment_idx: usize) {
644 let segment = &self.plan.segments[segment_idx];
645 let env = &segment.evm_env;
646 self.factory.reconfigure_evm(&mut self.inner.evm, env);
649
650 let block_number = env.block.number.to::<u64>();
651 self.inner.spec_id = env.spec.into();
652 self.inner.base_block_reward = base_block_reward(self.chain_spec.as_ref(), block_number);
653 self.inner.dao_fork_transition = self
654 .chain_spec
655 .ethereum_fork_activation(EthereumHardfork::Dao)
656 .transitions_at_block(block_number);
657 self.inner.ctx = segment.ctx.clone();
658 self.inner.separate_block_gas = env.version.feature(evm2::EvmFeatures::EIP8037);
659 self.inner.bal_index_offset = segment_idx as u64 * 2;
660 self.inner.cumulative_gas_used = 0;
661 self.inner.block_regular_gas_used = 0;
662 self.inner.block_state_gas_used = 0;
663 self.inner.blob_gas_used = 0;
664 }
665
666 fn reseed_block_hashes(&mut self, block_number: u64) {
667 for (number, hash) in self.plan.hashes_for_block(block_number) {
668 let number = evm2::interpreter::Word::from(number);
669 self.inner.evm.overlay_db_mut().insert_block_hash(&number, &hash);
670 }
671 }
672
673 fn initialize(&mut self) -> Result<(), BlockExecutionError> {
674 if self.initialized {
675 return Ok(())
676 }
677
678 let raw_index = self.inner.evm.state().bal_index().get();
679 let segment_idx = if raw_index == 0 {
680 0
681 } else {
682 let tx_index = (raw_index - 1) as usize;
683 if tx_index >= self.plan.transaction_count {
684 return Err(BlockExecutionError::msg("BAL index is outside big-block transactions"))
685 }
686 let segment_idx = self.plan.segment_index_for_tx(tx_index);
687 self.worker_segment = Some(segment_idx);
688 self.inner
689 .evm
690 .state_mut()
691 .set_bal_index(BlockAccessIndex::new(raw_index + segment_idx as u64 * 2));
692 self.next_segment = self.plan.segments.len();
693 segment_idx
694 };
695
696 self.configure_segment(segment_idx);
697 self.reseed_block_hashes(self.plan.segments[segment_idx].evm_env.block.number.to::<u64>());
698 self.initialized = true;
699 Ok(())
700 }
701
702 fn finish_segment(&mut self) -> Result<FinishedBigBlockSegment, BlockExecutionError> {
703 self.inner.set_transaction_block_access_index();
704 let context = EthBlockExecutor::<F::Types>::block_context(
705 self.inner.deposit_contract_address,
706 self.inner.ctx.parent_hash,
707 self.inner.ctx.parent_beacon_block_root,
708 self.inner.ctx.ommers,
709 None,
710 );
711 let receipts = &self.inner.receipts[self.segment_receipt_start..];
712 let mut requests = block_requests_from_receipts(self.inner.spec_id, context, receipts)?;
713 let stream_state = self.inner.state_update_hook.is_some();
714 post_execution_system_call_state_changes(
715 &mut self.inner.evm,
716 &mut self.inner.block_state,
717 stream_state,
718 &mut |state| emit_state(&mut self.inner.state_update_hook, state),
719 self.inner.spec_id,
720 context,
721 &mut requests,
722 )?;
723
724 let withdrawals = self
725 .inner
726 .ctx
727 .withdrawals
728 .clone()
729 .filter(|_| self.inner.spec_id.enables(evm2::SpecId::SHANGHAI));
730 let context = EthBlockExecutor::<F::Types>::block_context(
731 self.inner.deposit_contract_address,
732 self.inner.ctx.parent_hash,
733 self.inner.ctx.parent_beacon_block_root,
734 self.inner.ctx.ommers,
735 withdrawals.as_deref(),
736 );
737 let block_number = self.inner.evm.block_env().number.to::<u64>();
738 let block_beneficiary = self.inner.evm.block_env().beneficiary;
739 post_block_balance_state_changes(
740 &mut self.inner.evm,
741 &mut self.inner.block_state,
742 stream_state,
743 &mut |state| emit_state(&mut self.inner.state_update_hook, state),
744 self.inner.base_block_reward,
745 self.inner.dao_fork_transition,
746 block_number,
747 block_beneficiary,
748 context.ommers,
749 context.withdrawals,
750 )?;
751
752 Ok(FinishedBigBlockSegment {
753 requests,
754 gas_used: final_block_gas_used(
755 self.inner.separate_block_gas,
756 self.inner.cumulative_gas_used,
757 self.inner.block_regular_gas_used,
758 self.inner.block_state_gas_used,
759 ),
760 blob_gas_used: self.inner.blob_gas_used,
761 })
762 }
763
764 fn merge_segment(&mut self, segment: FinishedBigBlockSegment) {
765 self.requests.extend(segment.requests);
766 self.gas_used_offset += segment.gas_used;
767 self.receipt_gas_used_offset += self.inner.cumulative_gas_used;
768 self.blob_gas_used_offset += segment.blob_gas_used;
769 self.inner.cumulative_gas_used = 0;
770 self.inner.block_regular_gas_used = 0;
771 self.inner.block_state_gas_used = 0;
772 self.inner.blob_gas_used = 0;
773 }
774
775 fn after_committed_transaction(&mut self) -> Result<(), BlockExecutionError> {
776 if let Some(receipt) = self.inner.receipts.last_mut() {
777 receipt.cumulative_gas_used += self.receipt_gas_used_offset;
778 }
779 self.tx_counter += 1;
780 while self.worker_segment.is_none() &&
781 self.next_segment < self.plan.segments.len() &&
782 self.tx_counter == self.plan.segments[self.next_segment].start_tx
783 {
784 self.apply_segment_boundary()?;
785 }
786 Ok(())
787 }
788
789 fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
790 let segment = self.finish_segment()?;
791 self.merge_segment(segment);
792 self.inner.evm.state_mut().bump_bal_index();
793
794 let segment_idx = self.next_segment;
795 self.next_segment += 1;
796 self.segment_receipt_start = self.inner.receipts.len();
797 self.configure_segment(segment_idx);
798 self.reseed_block_hashes(self.plan.segments[segment_idx].evm_env.block.number.to::<u64>());
799 self.inner.apply_pre_execution_changes()
800 }
801}
802
803impl<'a, C, F> BlockExecutor for EthBigBlockExecutor<'a, C, F>
804where
805 C: EthereumHardforks,
806 F: EvmFactory,
807 F::Types: EvmTypes<Tx = TxEnvelope>,
808 <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
809 <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
810{
811 type Transaction = TransactionSigned;
812 type Receipt = Receipt;
813 type Evm = Evm<'a, F::Types>;
814 type TransactionResultWithState = EthTransactionResultWithState<F::Types>;
815 type BlockAccessList = Bal;
816
817 fn evm(&self) -> &Self::Evm {
818 self.inner.evm()
819 }
820
821 fn evm_mut(&mut self) -> &mut Self::Evm {
822 self.inner.evm_mut()
823 }
824
825 fn set_state_hook(&mut self, hook: impl FnMut(StateUpdate) + Send + 'static) -> bool {
826 self.inner.set_state_hook(hook)
827 }
828
829 fn convert_block_access_list(
830 block_access_list: &BlockAccessList,
831 ) -> Result<Self::BlockAccessList, BlockExecutionError> {
832 EthBlockExecutor::<F::Types>::convert_block_access_list(block_access_list)
833 }
834
835 fn set_block_access_list(&mut self, block_access_list: Arc<Self::BlockAccessList>) {
836 self.inner.set_block_access_list(block_access_list);
837 }
838
839 fn set_block_access_index(&mut self, index: BlockAccessIndex) {
840 let index = if let Some(current_segment) = self.worker_segment {
841 let raw_index = index.get();
842 if let Some(tx_index) = raw_index.checked_sub(1).map(|index| index as usize) {
843 if tx_index < self.plan.transaction_count {
844 let segment = self.plan.segment_index_for_tx(tx_index);
845 if segment != current_segment {
846 self.configure_segment(segment);
847 self.reseed_block_hashes(
848 self.plan.segments[segment].evm_env.block.number.to::<u64>(),
849 );
850 }
851 self.worker_segment = Some(segment);
852 BlockAccessIndex::new(raw_index + segment as u64 * 2)
853 } else {
854 index
855 }
856 } else {
857 index
858 }
859 } else {
860 index
861 };
862 self.inner.set_block_access_index(index);
863 }
864
865 fn enable_block_access_list_builder(&mut self) {
866 self.inner.enable_block_access_list_builder();
867 }
868
869 fn take_block_access_list(&mut self) -> Option<BlockAccessList> {
870 self.inner.take_block_access_list()
871 }
872
873 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
874 self.initialize()?;
875 self.inner.apply_pre_execution_changes()?;
876 while self.worker_segment.is_none() &&
877 self.next_segment < self.plan.segments.len() &&
878 self.tx_counter == self.plan.segments[self.next_segment].start_tx
879 {
880 self.apply_segment_boundary()?;
881 }
882 Ok(())
883 }
884
885 fn into_state(self) -> reth_evm::BundleState {
886 self.inner.into_state()
887 }
888
889 fn validate_transaction_gas_limit(
890 &mut self,
891 gas_limit: u64,
892 ) -> Result<(), BlockExecutionError> {
893 self.inner.validate_transaction_gas_limit(gas_limit)
894 }
895
896 fn execute_transaction_with_commit_condition(
897 &mut self,
898 transaction: impl ExecutorTx<Self>,
899 commit: impl FnOnce(
900 &evm2::TxResult<F::Types>,
901 evm2::TxStateChanges<'_>,
902 ) -> reth_evm::CommitChanges,
903 ) -> Result<Option<GasOutput>, BlockExecutionError> {
904 self.initialize()?;
905 let output = self.inner.execute_transaction_with_commit_condition(transaction, commit)?;
906 if output.is_some() {
907 self.after_committed_transaction()?;
908 }
909 Ok(output)
910 }
911
912 fn execute_transaction_without_commit(
913 &mut self,
914 transaction: impl ExecutorTx<Self>,
915 ) -> Result<Self::TransactionResultWithState, BlockExecutionError> {
916 self.initialize()?;
917 self.inner.execute_transaction_without_commit(transaction)
918 }
919
920 fn commit_transaction(
921 &mut self,
922 output: Self::TransactionResultWithState,
923 ) -> Result<GasOutput, BlockExecutionError> {
924 self.initialize()?;
925 let output = self.inner.commit_transaction(output)?;
926 self.after_committed_transaction()?;
927 Ok(output)
928 }
929
930 fn receipts(&self) -> &[Receipt] {
931 self.inner.receipts()
932 }
933
934 fn finish_with_block_access_list(
935 self,
936 ) -> Result<(BlockExecutionOutput<Receipt>, Option<BlockAccessList>), BlockExecutionError> {
937 let (output, bal) = self.finish_with_prepared_block_access_list()?;
938 Ok((output, bal.map(Into::into)))
939 }
940
941 fn finish_with_prepared_block_access_list(
942 mut self,
943 ) -> Result<(BlockExecutionOutput<Receipt>, Option<Bal>), BlockExecutionError> {
944 self.initialize()?;
945 let segment = self.finish_segment()?;
946 self.merge_segment(segment);
947 let block_access_list = self.inner.evm.state_mut().take_bal_builder();
948 let result = reth_execution_types::BlockExecutionResult {
949 receipts: core::mem::take(&mut self.inner.receipts),
950 requests: self.requests,
951 gas_used: self.gas_used_offset,
952 blob_gas_used: self.blob_gas_used_offset,
953 };
954 Ok((
955 BlockExecutionOutput::new(result, self.inner.block_state.into_bundle()),
956 block_access_list,
957 ))
958 }
959}
960
961const fn final_block_gas_used(
962 separate_block_gas: bool,
963 cumulative_gas_used: u64,
964 block_regular_gas_used: u64,
965 block_state_gas_used: u64,
966) -> u64 {
967 if separate_block_gas {
968 if block_regular_gas_used > block_state_gas_used {
969 block_regular_gas_used
970 } else {
971 block_state_gas_used
972 }
973 } else {
974 cumulative_gas_used
975 }
976}
977
978fn emit_state(hook: &mut StateUpdateHook, state: StateUpdate) {
979 if let Some(hook) = hook.as_mut() {
980 hook(state);
981 }
982}
983
984#[cfg(test)]
985mod tests {
986 use super::*;
987 use crate::dao_fork;
988 use alloy_consensus::{SignableTransaction, TxEip4844, TxLegacy};
989 use alloy_eip7928::BlockAccessIndex;
990 use alloy_primitives::{address, Address, Bytes, Signature, TxKind, B256, U256};
991 use evm2::{
992 bytecode::Bytecode,
993 env::BlockEnv,
994 evm::{AccountInfo, Database, Db},
995 interpreter::Word,
996 BaseEvmTypes, SpecId,
997 };
998 use reth_chainspec::{Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition};
999 use reth_ethereum_primitives::TransactionSigned;
1000 use reth_evm::{BlockExecutor, BlockExecutorFactory, CommitChanges};
1001 use reth_primitives_traits::Recovered;
1002 use std::{collections::BTreeMap, convert::Infallible, sync::Arc};
1003
1004 fn segment(
1005 start_tx: usize,
1006 block_number: u64,
1007 parent_hash: B256,
1008 ) -> EthBigBlockSegment<'static> {
1009 let block =
1010 BlockEnv::<BaseEvmTypes> { number: U256::from(block_number), ..Default::default() };
1011 EthBigBlockSegment {
1012 start_tx,
1013 evm_env: EthEvmEnv { block, ..EthEvmEnv::default() },
1014 ctx: EthBlockExecutionCtx {
1015 tx_count_hint: Some(1),
1016 parent_hash,
1017 parent_beacon_block_root: None,
1018 ommers: &[],
1019 withdrawals: None,
1020 extra_data: Bytes::new(),
1021 slot_number: None,
1022 },
1023 }
1024 }
1025
1026 #[test]
1027 fn big_block_plan_tracks_segment_boundaries_and_hashes() {
1028 let plan = EthBigBlockPlan::new(
1029 vec![segment(0, 10, B256::with_last_byte(1)), segment(2, 11, B256::with_last_byte(2))],
1030 vec![(8, B256::with_last_byte(8)), (9, B256::with_last_byte(9))],
1031 3,
1032 );
1033
1034 assert_eq!(plan.transaction_count, 3);
1035 assert_eq!(plan.segment_index_for_tx(0), 0);
1036 assert_eq!(plan.segment_index_for_tx(1), 0);
1037 assert_eq!(plan.segment_index_for_tx(2), 1);
1038 assert_eq!(
1039 plan.block_hashes,
1040 vec![
1041 (8, B256::with_last_byte(8)),
1042 (9, B256::with_last_byte(9)),
1043 (10, B256::with_last_byte(2)),
1044 ]
1045 );
1046 }
1047
1048 #[derive(Default)]
1049 struct TestDatabase {
1050 accounts: BTreeMap<Address, AccountInfo>,
1051 }
1052
1053 impl Database for TestDatabase {
1054 type Error = Infallible;
1055
1056 fn get_account(&mut self, address: &Address) -> Result<Option<AccountInfo>, Self::Error> {
1057 Ok(self.accounts.get(address).cloned())
1058 }
1059
1060 fn get_code_by_hash(&mut self, _code_hash: &B256) -> Result<Bytecode, Self::Error> {
1061 Ok(Bytecode::default())
1062 }
1063
1064 fn get_storage(&mut self, _address: &Address, _key: &Word) -> Result<Word, Self::Error> {
1065 Ok(Word::ZERO)
1066 }
1067
1068 fn get_block_hash(&mut self, _number: &Word) -> Result<B256, Self::Error> {
1069 Ok(B256::ZERO)
1070 }
1071 }
1072
1073 fn transfer(from: Address, to: Address, nonce: u64) -> Recovered<TransactionSigned> {
1074 let tx = TransactionSigned::Legacy(
1075 TxLegacy {
1076 chain_id: Some(1),
1077 nonce,
1078 gas_limit: 21_000,
1079 gas_price: 1,
1080 to: TxKind::Call(to),
1081 value: U256::from(1),
1082 ..Default::default()
1083 }
1084 .into_signed(Signature::test_signature()),
1085 );
1086 Recovered::new_unchecked(tx, from)
1087 }
1088
1089 #[test]
1090 fn big_block_executor_preserves_state_across_segment_switch() {
1091 let from = address!("0000000000000000000000000000000000000001");
1092 let first_target = address!("0000000000000000000000000000000000000010");
1093 let second_target = address!("0000000000000000000000000000000000000020");
1094 let mut database = TestDatabase::default();
1095 database
1096 .accounts
1097 .insert(from, AccountInfo::default().with_balance(U256::from(1_000_000u64)));
1098
1099 let mut first = segment(0, 1, B256::with_last_byte(1));
1100 first.evm_env = EthEvmEnv::new(SpecId::LONDON, first.evm_env.block, 1);
1101 first.evm_env.block.basefee = U256::from(1);
1102 let mut second = segment(1, 2, B256::with_last_byte(2));
1103 second.evm_env = EthEvmEnv::new(SpecId::SHANGHAI, second.evm_env.block, 1);
1104 second.evm_env.block.basefee = U256::from(1);
1105 let plan = EthBigBlockPlan::new(vec![first, second], Vec::new(), 2);
1106 let chain_spec =
1107 ChainSpecBuilder::mainnet().chain(Chain::mainnet()).paris_activated().build();
1108 let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1109 super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec)),
1110 );
1111 let mut env = plan.segments[0].evm_env.clone();
1112 env.version.features.remove(evm2::EvmFeatures::BASE_FEE_CHECK);
1113 let evm = factory.evm_with_env(Db::new(database), env);
1114 let mut executor = factory.create_executor(evm, plan);
1115
1116 executor.apply_pre_execution_changes().expect("first segment pre-execution");
1117 assert!(executor
1118 .execute_transaction_with_commit_condition(
1119 transfer(from, first_target, 0),
1120 |_, changes| {
1121 assert_eq!(changes.account_info(&from).expect("sender changed").nonce, 1);
1123 CommitChanges::No
1124 }
1125 )
1126 .expect("discarded transaction")
1127 .is_none());
1128 executor
1129 .execute_transaction_with_commit_condition(transfer(from, first_target, 0), |_, _| {
1130 CommitChanges::Yes
1131 })
1132 .expect("first segment transaction")
1133 .expect("transaction committed");
1134 executor
1135 .execute_transaction_with_commit_condition(transfer(from, second_target, 1), |_, _| {
1136 CommitChanges::Yes
1137 })
1138 .expect("second segment transaction")
1139 .expect("transaction committed");
1140 let (output, _) = executor.finish_with_block_access_list().expect("finish big block");
1141
1142 assert_eq!(output.result.receipts.len(), 2);
1143 assert_eq!(output.result.gas_used, 42_000);
1144 assert_eq!(output.account(&first_target).unwrap().unwrap().balance, U256::from(1));
1145 assert_eq!(output.account(&second_target).unwrap().unwrap().balance, U256::from(1));
1146 assert_eq!(
1148 output
1149 .account(&Address::ZERO)
1150 .flatten()
1151 .map(|account| account.balance)
1152 .unwrap_or_default(),
1153 U256::ZERO
1154 );
1155 }
1156
1157 #[test]
1158 fn amsterdam_admission_enforces_uncapped_state_gas_budget() {
1159 let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1160 ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1161 ));
1162 let ctx = segment(0, 1, B256::ZERO).ctx;
1163 for cap in [u64::MAX, 400_000] {
1164 let mut env = EthEvmEnv::new(
1165 SpecId::AMSTERDAM,
1166 BlockEnv::<BaseEvmTypes> { gas_limit: U256::from(1_000_000), ..Default::default() },
1167 1,
1168 );
1169 env.version.tx_gas_limit_cap = cap;
1170 let evm = factory.evm_with_env(Db::new(TestDatabase::default()), env);
1171 let mut executor = factory.create_executor(evm, ctx.clone());
1172 executor.block_state_gas_used = 600_000;
1173 executor.block_regular_gas_used = 0;
1174 assert!(executor.validate_transaction_gas_limit(500_000).is_err());
1175 assert!(executor.validate_transaction_gas_limit(400_000).is_ok());
1176 }
1177 }
1178
1179 #[test]
1180 fn pre_london_creation_preserves_legacy_ef01_code() {
1181 let sender = Address::with_last_byte(1);
1182 let mut database = TestDatabase::default();
1183 database
1184 .accounts
1185 .insert(sender, AccountInfo::default().with_balance(U256::from(1_000_000)));
1186 let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1187 ChainSpecBuilder::mainnet().berlin_activated().build(),
1188 ));
1189 let env = EthEvmEnv::new(
1190 SpecId::BERLIN,
1191 BlockEnv::<BaseEvmTypes> { gas_limit: U256::from(1_000_000), ..Default::default() },
1192 1,
1193 );
1194 let evm = factory.evm_with_env(Db::new(database), env);
1195 let mut executor = factory.create_executor(evm, segment(0, 1, B256::ZERO).ctx);
1196 let tx = TransactionSigned::Legacy(
1197 TxLegacy {
1198 chain_id: Some(1),
1199 gas_limit: 100_000,
1200 gas_price: 1,
1201 to: TxKind::Create,
1202 input: alloy_primitives::bytes!("61ef016000526002601ef3"),
1204 ..Default::default()
1205 }
1206 .into_signed(Signature::test_signature()),
1207 );
1208 executor.apply_pre_execution_changes().unwrap();
1209 executor.execute_transaction(Recovered::new_unchecked(tx, sender)).unwrap();
1210 let (output, _) = executor.finish_with_block_access_list().unwrap();
1211 let code = &output.state.contracts[&alloy_primitives::keccak256([0xef, 0x01])];
1212 assert!(code.is_legacy());
1213 assert_eq!(code.original_bytes().as_ref(), &[0xef, 0x01]);
1214 }
1215
1216 #[test]
1217 fn executor_applies_dao_fork_balance_transfer_before_rewards() {
1218 let dao_account = address!("d4fe7bc31cedb7bfb8a345f31e668033056b2728");
1219 let beneficiary = address!("bf4ed7b27f1d666546e30d74d50d173d20bca754");
1220 let mut database = TestDatabase::default();
1221 database.accounts.insert(dao_account, AccountInfo::default().with_balance(U256::from(7)));
1222
1223 let chain_spec = ChainSpecBuilder::mainnet()
1224 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(1))
1225 .build();
1226 let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec));
1227 let env = EthEvmEnv::new(
1228 SpecId::FRONTIER,
1229 BlockEnv::<BaseEvmTypes> {
1230 number: U256::from(1),
1231 gas_limit: U256::from(30_000),
1232 beneficiary: dao_account,
1233 ..Default::default()
1234 },
1235 1,
1236 );
1237 let evm = factory.evm_with_env(Db::new(database), env);
1238 let mut executor = factory.create_executor(
1239 evm,
1240 EthBlockExecutionCtx {
1241 tx_count_hint: Some(0),
1242 parent_hash: B256::ZERO,
1243 parent_beacon_block_root: None,
1244 ommers: &[],
1245 withdrawals: None,
1246 extra_data: Bytes::new(),
1247 slot_number: None,
1248 },
1249 );
1250
1251 executor.apply_pre_execution_changes().expect("DAO block pre-execution succeeds");
1252 let (output, _) =
1253 executor.finish_with_block_access_list().expect("DAO block post-execution succeeds");
1254
1255 assert_eq!(
1256 output.account(&dao_account).unwrap().unwrap().balance,
1257 U256::from(5 * alloy_consensus::constants::ETH_TO_WEI)
1258 );
1259 assert_eq!(output.account(&beneficiary).unwrap().unwrap().balance, U256::from(7));
1260 }
1261
1262 #[test]
1263 fn dao_fork_drain_removes_empty_accounts() {
1264 let [absent, empty, empty_with_balance, nonce_only, ..] = dao_fork::DAO_HARDFORK_ACCOUNTS;
1265 let beneficiary = dao_fork::DAO_HARDFORK_BENEFICIARY;
1266
1267 let execute = |accounts: &[(Address, AccountInfo)]| {
1268 let mut database = TestDatabase::default();
1269 database.accounts.extend(accounts.iter().cloned());
1270 let chain_spec = ChainSpecBuilder::mainnet()
1271 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(1))
1272 .build();
1273 let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec));
1274 let env = EthEvmEnv::new(
1275 SpecId::FRONTIER,
1276 BlockEnv::<BaseEvmTypes> {
1277 number: U256::from(1),
1278 gas_limit: U256::from(30_000),
1279 beneficiary: Address::with_last_byte(0xbe),
1280 ..Default::default()
1281 },
1282 1,
1283 );
1284 let evm = factory.evm_with_env(Db::new(database), env);
1285 let mut executor = factory.create_executor(evm, segment(0, 1, B256::ZERO).ctx);
1286 executor.apply_pre_execution_changes().unwrap();
1287 executor.finish_with_block_access_list().unwrap().0
1288 };
1289
1290 let output = execute(&[
1293 (empty, AccountInfo::default()),
1294 (empty_with_balance, AccountInfo::default().with_balance(U256::from(7))),
1295 (nonce_only, AccountInfo { nonce: 1, balance: U256::from(3), ..Default::default() }),
1296 ]);
1297 assert_eq!(output.account(&absent), None);
1298 assert_eq!(output.account(&empty), Some(None));
1299 assert_eq!(output.account(&empty_with_balance), Some(None));
1300 let nonce_only_account = output.account(&nonce_only).unwrap().unwrap();
1301 assert_eq!((nonce_only_account.nonce, nonce_only_account.balance), (1, U256::ZERO));
1302 assert_eq!(output.account(&beneficiary).unwrap().unwrap().balance, U256::from(10));
1303
1304 let output = execute(&[(beneficiary, AccountInfo::default())]);
1307 assert_eq!(output.account(&beneficiary), Some(None));
1308 }
1309
1310 #[test]
1311 fn big_block_worker_switches_segments_from_bal_index() {
1312 let from = address!("0000000000000000000000000000000000000001");
1313 let target = address!("0000000000000000000000000000000000000010");
1314 let mut database = TestDatabase::default();
1315 database
1316 .accounts
1317 .insert(from, AccountInfo::default().with_balance(U256::from(1_000_000u64)));
1318
1319 let mut first = segment(0, 1, B256::with_last_byte(1));
1320 first.evm_env = EthEvmEnv::new(SpecId::FRONTIER, first.evm_env.block, 1);
1321 let mut second = segment(1, 2, B256::with_last_byte(2));
1322 second.evm_env = EthEvmEnv::new(SpecId::LONDON, second.evm_env.block, 1);
1323 let plan = EthBigBlockPlan::new(vec![first, second], Vec::new(), 2);
1324 let chain_spec =
1325 ChainSpecBuilder::mainnet().chain(Chain::mainnet()).paris_activated().build();
1326 let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1327 super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec)),
1328 );
1329 let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1330 let mut executor = factory.create_executor(evm, plan);
1331
1332 executor.set_block_access_index(BlockAccessIndex::new(2));
1333 executor
1334 .execute_transaction_without_commit(transfer(from, target, 0))
1335 .expect("worker transaction in second segment");
1336 assert_eq!(executor.evm().block().number, U256::from(2));
1337
1338 executor.set_block_access_index(BlockAccessIndex::new(1));
1339 executor
1340 .execute_transaction_without_commit(transfer(from, target, 0))
1341 .expect("worker transaction in first segment");
1342 assert_eq!(executor.evm().block().number, U256::from(1));
1343 }
1344
1345 #[test]
1346 fn amsterdam_header_uses_bottleneck_gas_dimension() {
1347 assert_eq!(final_block_gas_used(true, 90, 70, 120), 120);
1348 assert_eq!(final_block_gas_used(true, 90, 130, 120), 130);
1349 assert_eq!(final_block_gas_used(false, 90, 130, 120), 90);
1350 }
1351
1352 #[test]
1353 fn amsterdam_segment_receipts_use_transaction_gas() {
1354 let sender = Address::with_last_byte(0x10);
1355 let mut database = TestDatabase::default();
1356 database.accounts.insert(
1357 sender,
1358 AccountInfo::default().with_balance(U256::from(1_000_000_000_000_000_000u64)),
1359 );
1360 let segments = (0..2)
1361 .map(|index| {
1362 let mut segment = segment(index, index as u64 + 1, B256::ZERO);
1363 segment.ctx.parent_beacon_block_root = Some(B256::ZERO);
1364 segment.evm_env = EthEvmEnv::new(
1365 SpecId::AMSTERDAM,
1366 BlockEnv::<BaseEvmTypes> {
1367 number: U256::from(index + 1),
1368 gas_limit: U256::from(30_000_000),
1369 ..Default::default()
1370 },
1371 1,
1372 );
1373 segment
1374 })
1375 .collect();
1376 let plan = EthBigBlockPlan::new(segments, Vec::new(), 2);
1377 let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1378 super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1379 ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1380 )),
1381 );
1382 let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1383 let mut executor = factory.create_executor(evm, plan);
1384 executor.apply_pre_execution_changes().unwrap();
1385 let mut receipt_gas = 0;
1386 let mut block_gas = 0;
1387 for nonce in 0..2 {
1388 let tx = TransactionSigned::Legacy(
1390 TxLegacy {
1391 chain_id: Some(1),
1392 nonce,
1393 gas_limit: 1_000_000,
1394 gas_price: 1,
1395 to: TxKind::Call(Address::with_last_byte(0x20 + nonce as u8)),
1396 value: U256::from(1),
1397 ..Default::default()
1398 }
1399 .into_signed(Signature::test_signature()),
1400 );
1401 let gas = executor.execute_transaction(Recovered::new_unchecked(tx, sender)).unwrap();
1402 let segment_block_gas = gas.regular_gas_used().max(gas.state_gas_used());
1403 assert_ne!(gas.tx_gas_used(), segment_block_gas);
1404 receipt_gas += gas.tx_gas_used();
1405 block_gas += segment_block_gas;
1406 let receipt = executor.receipts().last().unwrap();
1407 assert!(receipt.success);
1408 assert_eq!(receipt.cumulative_gas_used, receipt_gas);
1409 }
1410 let (output, _) = executor.finish_with_block_access_list().unwrap();
1411 assert_eq!(output.result.gas_used, block_gas);
1412 }
1413
1414 #[test]
1415 fn big_block_segment_switch_installs_segment_blob_cap() {
1416 let sender = Address::with_last_byte(0x10);
1417 let mut database = TestDatabase::default();
1418 database.accounts.insert(
1419 sender,
1420 AccountInfo::default().with_balance(U256::from(1_000_000_000_000_000_000u64)),
1421 );
1422 let segments = [9, 6]
1423 .into_iter()
1424 .enumerate()
1425 .map(|(index, max_blobs_per_tx)| {
1426 let mut segment = segment(index, index as u64 + 1, B256::ZERO);
1427 segment.ctx.parent_beacon_block_root = Some(B256::ZERO);
1428 segment.evm_env = EthEvmEnv::new(
1429 SpecId::PRAGUE,
1430 BlockEnv::<BaseEvmTypes> {
1431 number: U256::from(index + 1),
1432 gas_limit: U256::from(30_000_000),
1433 ..Default::default()
1434 },
1435 1,
1436 );
1437 segment.evm_env.version.max_blobs_per_tx = max_blobs_per_tx;
1438 segment
1439 })
1440 .collect();
1441 let plan = EthBigBlockPlan::new(segments, Vec::new(), 2);
1442 let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1443 super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1444 ChainSpecBuilder::mainnet().prague_activated().build(),
1445 )),
1446 );
1447 let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1448 let mut executor = factory.create_executor(evm, plan);
1449 executor.apply_pre_execution_changes().unwrap();
1450
1451 let blob_tx = |nonce| {
1452 let tx = TransactionSigned::Eip4844(
1453 TxEip4844 {
1454 chain_id: 1,
1455 nonce,
1456 gas_limit: 100_000,
1457 max_fee_per_gas: 1,
1458 max_priority_fee_per_gas: 1,
1459 to: Address::with_last_byte(0x20),
1460 max_fee_per_blob_gas: 1,
1461 blob_versioned_hashes: vec![B256::repeat_byte(1); 7],
1462 ..Default::default()
1463 }
1464 .into_signed(Signature::test_signature()),
1465 );
1466 Recovered::new_unchecked(tx, sender)
1467 };
1468 executor.execute_transaction(blob_tx(0)).expect("seven blobs fit the first segment cap");
1469 let err = executor.execute_transaction(blob_tx(1)).unwrap_err();
1470 assert!(err.to_string().contains("too many blobs: have 7, max 6"), "{err}");
1471 }
1472}