reth_stages/stages/
execution.rs

1use crate::stages::MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD;
2use alloy_consensus::BlockHeader;
3use alloy_primitives::BlockNumber;
4use num_traits::Zero;
5use reth_config::config::ExecutionConfig;
6use reth_consensus::{ConsensusError, FullConsensus};
7use reth_db::{static_file::HeaderMask, tables};
8use reth_evm::{execute::Executor, metrics::ExecutorMetrics, ConfigureEvm};
9use reth_execution_types::Chain;
10use reth_exex::{ExExManagerHandle, ExExNotification, ExExNotificationSource};
11use reth_primitives_traits::{format_gas_throughput, BlockBody, NodePrimitives};
12use reth_provider::{
13    providers::{StaticFileProvider, StaticFileWriter},
14    BlockHashReader, BlockReader, DBProvider, EitherWriter, ExecutionOutcome, HeaderProvider,
15    LatestStateProviderRef, OriginalValuesKnown, ProviderError, StateWriter,
16    StaticFileProviderFactory, StatsReader, StorageSettingsCache, TransactionVariant,
17};
18use reth_revm::database::StateProviderDatabase;
19use reth_stages_api::{
20    BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, ExecOutput,
21    ExecutionCheckpoint, ExecutionStageThresholds, Stage, StageCheckpoint, StageError, StageId,
22    UnwindInput, UnwindOutput,
23};
24use reth_static_file_types::StaticFileSegment;
25use std::{
26    cmp::{max, Ordering},
27    ops::RangeInclusive,
28    sync::Arc,
29    task::{ready, Context, Poll},
30    time::{Duration, Instant},
31};
32use tracing::*;
33
34use super::missing_static_data_error;
35
36/// The execution stage executes all transactions and
37/// update history indexes.
38///
39/// Input tables:
40/// - [`tables::CanonicalHeaders`] get next block to execute.
41/// - [`tables::Headers`] get for revm environment variables.
42/// - [`tables::BlockBodyIndices`] to get tx number
43/// - [`tables::Transactions`] to execute
44///
45/// For state access [`LatestStateProviderRef`] provides us latest state and history state
46/// For latest most recent state [`LatestStateProviderRef`] would need (Used for execution Stage):
47/// - [`tables::PlainAccountState`]
48/// - [`tables::Bytecodes`]
49/// - [`tables::PlainStorageState`]
50///
51/// Tables updated after state finishes execution:
52/// - [`tables::PlainAccountState`]
53/// - [`tables::PlainStorageState`]
54/// - [`tables::Bytecodes`]
55/// - [`tables::AccountChangeSets`]
56/// - [`tables::StorageChangeSets`]
57///
58/// For unwinds we are accessing:
59/// - [`tables::BlockBodyIndices`] get tx index to know what needs to be unwinded
60/// - [`tables::AccountsHistory`] to remove change set and apply old values to
61/// - [`tables::PlainAccountState`] [`tables::StoragesHistory`] to remove change set and apply old
62///   values to [`tables::PlainStorageState`]
63// false positive, we cannot derive it if !DB: Debug.
64#[derive(Debug)]
65pub struct ExecutionStage<E>
66where
67    E: ConfigureEvm,
68{
69    /// The stage's internal block executor
70    evm_config: E,
71    /// The consensus instance for validating blocks.
72    consensus: Arc<dyn FullConsensus<E::Primitives, Error = ConsensusError>>,
73    /// The commit thresholds of the execution stage.
74    thresholds: ExecutionStageThresholds,
75    /// The highest threshold (in number of blocks) for switching between incremental
76    /// and full calculations across [`super::MerkleStage`], [`super::AccountHashingStage`] and
77    /// [`super::StorageHashingStage`]. This is required to figure out if can prune or not
78    /// changesets on subsequent pipeline runs.
79    external_clean_threshold: u64,
80    /// Input for the post execute commit hook.
81    /// Set after every [`ExecutionStage::execute`] and cleared after
82    /// [`ExecutionStage::post_execute_commit`].
83    post_execute_commit_input: Option<Chain<E::Primitives>>,
84    /// Input for the post unwind commit hook.
85    /// Set after every [`ExecutionStage::unwind`] and cleared after
86    /// [`ExecutionStage::post_unwind_commit`].
87    post_unwind_commit_input: Option<Chain<E::Primitives>>,
88    /// Handle to communicate with `ExEx` manager.
89    exex_manager_handle: ExExManagerHandle<E::Primitives>,
90    /// Executor metrics.
91    metrics: ExecutorMetrics,
92}
93
94impl<E> ExecutionStage<E>
95where
96    E: ConfigureEvm,
97{
98    /// Create new execution stage with specified config.
99    pub fn new(
100        evm_config: E,
101        consensus: Arc<dyn FullConsensus<E::Primitives, Error = ConsensusError>>,
102        thresholds: ExecutionStageThresholds,
103        external_clean_threshold: u64,
104        exex_manager_handle: ExExManagerHandle<E::Primitives>,
105    ) -> Self {
106        Self {
107            external_clean_threshold,
108            evm_config,
109            consensus,
110            thresholds,
111            post_execute_commit_input: None,
112            post_unwind_commit_input: None,
113            exex_manager_handle,
114            metrics: ExecutorMetrics::default(),
115        }
116    }
117
118    /// Create an execution stage with the provided executor.
119    ///
120    /// The commit threshold will be set to [`MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD`].
121    pub fn new_with_executor(
122        evm_config: E,
123        consensus: Arc<dyn FullConsensus<E::Primitives, Error = ConsensusError>>,
124    ) -> Self {
125        Self::new(
126            evm_config,
127            consensus,
128            ExecutionStageThresholds::default(),
129            MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD,
130            ExExManagerHandle::empty(),
131        )
132    }
133
134    /// Create new instance of [`ExecutionStage`] from configuration.
135    pub fn from_config(
136        evm_config: E,
137        consensus: Arc<dyn FullConsensus<E::Primitives, Error = ConsensusError>>,
138        config: ExecutionConfig,
139        external_clean_threshold: u64,
140    ) -> Self {
141        Self::new(
142            evm_config,
143            consensus,
144            config.into(),
145            external_clean_threshold,
146            ExExManagerHandle::empty(),
147        )
148    }
149
150    /// Returns whether we can perform pruning of [`tables::AccountChangeSets`] and
151    /// [`tables::StorageChangeSets`].
152    ///
153    /// This function verifies whether the [`super::MerkleStage`] or Hashing stages will run from
154    /// scratch. If at least one stage isn't starting anew, it implies that pruning of
155    /// changesets cannot occur. This is determined by checking the highest clean threshold
156    /// (`self.external_clean_threshold`) across the stages.
157    ///
158    /// Given that `start_block` changes with each checkpoint, it's necessary to inspect
159    /// [`tables::AccountsTrie`] to ensure that [`super::MerkleStage`] hasn't
160    /// been previously executed.
161    fn can_prune_changesets(
162        &self,
163        provider: impl StatsReader,
164        start_block: u64,
165        max_block: u64,
166    ) -> Result<bool, StageError> {
167        // We can only prune changesets if we're not executing MerkleStage from scratch (by
168        // threshold or first-sync)
169        Ok(max_block - start_block > self.external_clean_threshold ||
170            provider.count_entries::<tables::AccountsTrie>()?.is_zero())
171    }
172
173    /// Performs consistency check on static files.
174    ///
175    /// This function compares the highest receipt number recorded in the database with that in the
176    /// static file to detect any discrepancies due to unexpected shutdowns or database rollbacks.
177    /// **If the height in the static file is higher**, it rolls back (unwinds) the static file.
178    /// **Conversely, if the height in the database is lower**, it triggers a rollback in the
179    /// database (by returning [`StageError`]) until the heights in both the database and static
180    /// file match.
181    fn ensure_consistency<Provider>(
182        &self,
183        provider: &Provider,
184        checkpoint: u64,
185        unwind_to: Option<u64>,
186    ) -> Result<(), StageError>
187    where
188        Provider: StaticFileProviderFactory
189            + DBProvider
190            + BlockReader
191            + HeaderProvider
192            + StorageSettingsCache,
193    {
194        // On old nodes, if there's any receipts pruning configured, receipts are written directly
195        // to database and inconsistencies are expected.
196        if EitherWriter::receipts_destination(provider).is_database() {
197            return Ok(())
198        }
199
200        // Get next expected receipt number
201        let next_receipt_num =
202            provider.block_body_indices(checkpoint)?.map(|b| b.next_tx_num()).unwrap_or(0);
203
204        let static_file_provider = provider.static_file_provider();
205
206        // Get next expected receipt number in static files
207        let next_static_file_receipt_num = static_file_provider
208            .get_highest_static_file_tx(StaticFileSegment::Receipts)
209            .map(|num| num + 1)
210            .unwrap_or(0);
211
212        // Get highest block number in static files for receipts
213        let static_file_block_num = static_file_provider
214            .get_highest_static_file_block(StaticFileSegment::Receipts)
215            .unwrap_or(0);
216
217        // Check if we had any unexpected shutdown after committing to static files, but
218        // NOT committing to database.
219        match static_file_block_num.cmp(&checkpoint) {
220            // It can be equal when it's a chain of empty blocks, but we still need to update the
221            // last block in the range.
222            Ordering::Greater | Ordering::Equal => {
223                let mut static_file_producer =
224                    static_file_provider.latest_writer(StaticFileSegment::Receipts)?;
225                static_file_producer.prune_receipts(
226                    next_static_file_receipt_num.saturating_sub(next_receipt_num),
227                    checkpoint,
228                )?;
229                // Since this is a database <-> static file inconsistency, we commit the change
230                // straight away.
231                static_file_producer.commit()?;
232            }
233            Ordering::Less => {
234                // If we are already in the process of unwind, this might be fine because we will
235                // fix the inconsistency right away.
236                if let Some(unwind_to) = unwind_to &&
237                    unwind_to <= static_file_block_num
238                {
239                    return Ok(())
240                }
241
242                // Otherwise, this is a real inconsistency - database has more blocks than static
243                // files
244                return Err(missing_static_data_error(
245                    next_static_file_receipt_num.saturating_sub(1),
246                    &static_file_provider,
247                    provider,
248                    StaticFileSegment::Receipts,
249                )?)
250            }
251        }
252
253        Ok(())
254    }
255}
256
257impl<E, Provider> Stage<Provider> for ExecutionStage<E>
258where
259    E: ConfigureEvm,
260    Provider: DBProvider
261        + BlockReader<
262            Block = <E::Primitives as NodePrimitives>::Block,
263            Header = <E::Primitives as NodePrimitives>::BlockHeader,
264        > + StaticFileProviderFactory<
265            Primitives: NodePrimitives<BlockHeader: reth_db_api::table::Value>,
266        > + StatsReader
267        + BlockHashReader
268        + StateWriter<Receipt = <E::Primitives as NodePrimitives>::Receipt>
269        + StorageSettingsCache,
270{
271    /// Return the id of the stage
272    fn id(&self) -> StageId {
273        StageId::Execution
274    }
275
276    fn poll_execute_ready(
277        &mut self,
278        cx: &mut Context<'_>,
279        _: ExecInput,
280    ) -> Poll<Result<(), StageError>> {
281        ready!(self.exex_manager_handle.poll_ready(cx));
282
283        Poll::Ready(Ok(()))
284    }
285
286    /// Execute the stage
287    fn execute(&mut self, provider: &Provider, input: ExecInput) -> Result<ExecOutput, StageError> {
288        if input.target_reached() {
289            return Ok(ExecOutput::done(input.checkpoint()))
290        }
291
292        let start_block = input.next_block();
293        let max_block = input.target();
294        let static_file_provider = provider.static_file_provider();
295
296        self.ensure_consistency(provider, input.checkpoint().block_number, None)?;
297
298        let db = StateProviderDatabase(LatestStateProviderRef::new(provider));
299        let mut executor = self.evm_config.batch_executor(db);
300
301        // Progress tracking
302        let mut stage_progress = start_block;
303        let mut stage_checkpoint = execution_checkpoint(
304            &static_file_provider,
305            start_block,
306            max_block,
307            input.checkpoint(),
308        )?;
309
310        let mut fetch_block_duration = Duration::default();
311        let mut execution_duration = Duration::default();
312
313        let mut last_block = start_block;
314        let mut last_execution_duration = Duration::default();
315        let mut last_cumulative_gas = 0;
316        let mut last_log_instant = Instant::now();
317        let log_duration = Duration::from_secs(10);
318
319        debug!(target: "sync::stages::execution", start = start_block, end = max_block, "Executing range");
320
321        // Execute block range
322        let mut cumulative_gas = 0;
323        let batch_start = Instant::now();
324
325        let mut blocks = Vec::new();
326        let mut results = Vec::new();
327        for block_number in start_block..=max_block {
328            // Fetch the block
329            let fetch_block_start = Instant::now();
330
331            // we need the block's transactions but we don't need the transaction hashes
332            let block = provider
333                .recovered_block(block_number.into(), TransactionVariant::NoHash)?
334                .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?;
335
336            fetch_block_duration += fetch_block_start.elapsed();
337
338            cumulative_gas += block.header().gas_used();
339
340            // Configure the executor to use the current state.
341            trace!(target: "sync::stages::execution", number = block_number, txs = block.body().transactions().len(), "Executing block");
342
343            // Execute the block
344            let execute_start = Instant::now();
345
346            let result = self.metrics.metered_one(&block, |input| {
347                executor.execute_one(input).map_err(|error| StageError::Block {
348                    block: Box::new(block.block_with_parent()),
349                    error: BlockErrorKind::Execution(error),
350                })
351            })?;
352
353            if let Err(err) = self.consensus.validate_block_post_execution(&block, &result) {
354                return Err(StageError::Block {
355                    block: Box::new(block.block_with_parent()),
356                    error: BlockErrorKind::Validation(err),
357                })
358            }
359            results.push(result);
360
361            execution_duration += execute_start.elapsed();
362
363            // Log execution throughput
364            if last_log_instant.elapsed() >= log_duration {
365                info!(
366                    target: "sync::stages::execution",
367                    start = last_block,
368                    end = block_number,
369                    throughput = format_gas_throughput(cumulative_gas - last_cumulative_gas, execution_duration - last_execution_duration),
370                    "Executed block range"
371                );
372
373                last_block = block_number + 1;
374                last_execution_duration = execution_duration;
375                last_cumulative_gas = cumulative_gas;
376                last_log_instant = Instant::now();
377            }
378
379            stage_progress = block_number;
380            stage_checkpoint.progress.processed += block.header().gas_used();
381
382            // If we have ExExes we need to save the block in memory for later
383            if self.exex_manager_handle.has_exexs() {
384                blocks.push(block);
385            }
386
387            // Check if we should commit now
388            if self.thresholds.is_end_of_batch(
389                block_number - start_block,
390                executor.size_hint() as u64,
391                cumulative_gas,
392                batch_start.elapsed(),
393            ) {
394                break
395            }
396        }
397
398        // prepare execution output for writing
399        let time = Instant::now();
400        let mut state = ExecutionOutcome::from_blocks(
401            start_block,
402            executor.into_state().take_bundle(),
403            results,
404        );
405        let write_preparation_duration = time.elapsed();
406
407        // log the gas per second for the range we just executed
408        debug!(
409            target: "sync::stages::execution",
410            start = start_block,
411            end = stage_progress,
412            throughput = format_gas_throughput(cumulative_gas, execution_duration),
413            "Finished executing block range"
414        );
415
416        // Prepare the input for post execute commit hook, where an `ExExNotification` will be sent.
417        //
418        // Note: Since we only write to `blocks` if there are any ExExes, we don't need to perform
419        // the `has_exexs` check here as well
420        if !blocks.is_empty() {
421            let previous_input =
422                self.post_execute_commit_input.replace(Chain::new(blocks, state.clone(), None));
423
424            if previous_input.is_some() {
425                // Not processing the previous post execute commit input is a critical error, as it
426                // means that we didn't send the notification to ExExes
427                return Err(StageError::PostExecuteCommit(
428                    "Previous post execute commit input wasn't processed",
429                ))
430            }
431        }
432
433        let time = Instant::now();
434
435        if self.can_prune_changesets(provider, start_block, max_block)? {
436            let prune_modes = provider.prune_modes_ref();
437
438            // Iterate over all reverts and clear them if pruning is configured.
439            for block_number in start_block..=max_block {
440                let Some(reverts) =
441                    state.bundle.reverts.get_mut((block_number - start_block) as usize)
442                else {
443                    break
444                };
445
446                // If both account history and storage history pruning is configured, clear reverts
447                // for this block.
448                if prune_modes
449                    .account_history
450                    .is_some_and(|m| m.should_prune(block_number, max_block)) &&
451                    prune_modes
452                        .storage_history
453                        .is_some_and(|m| m.should_prune(block_number, max_block))
454                {
455                    reverts.clear();
456                }
457            }
458        }
459
460        // write output
461        provider.write_state(&state, OriginalValuesKnown::Yes)?;
462
463        let db_write_duration = time.elapsed();
464        debug!(
465            target: "sync::stages::execution",
466            block_fetch = ?fetch_block_duration,
467            execution = ?execution_duration,
468            write_preparation = ?write_preparation_duration,
469            write = ?db_write_duration,
470            "Execution time"
471        );
472
473        let done = stage_progress == max_block;
474        Ok(ExecOutput {
475            checkpoint: StageCheckpoint::new(stage_progress)
476                .with_execution_stage_checkpoint(stage_checkpoint),
477            done,
478        })
479    }
480
481    fn post_execute_commit(&mut self) -> Result<(), StageError> {
482        let Some(chain) = self.post_execute_commit_input.take() else { return Ok(()) };
483
484        // NOTE: We can ignore the error here, since an error means that the channel is closed,
485        // which means the manager has died, which then in turn means the node is shutting down.
486        let _ = self.exex_manager_handle.send(
487            ExExNotificationSource::Pipeline,
488            ExExNotification::ChainCommitted { new: Arc::new(chain) },
489        );
490
491        Ok(())
492    }
493
494    /// Unwind the stage.
495    fn unwind(
496        &mut self,
497        provider: &Provider,
498        input: UnwindInput,
499    ) -> Result<UnwindOutput, StageError> {
500        let (range, unwind_to, _) =
501            input.unwind_block_range_with_threshold(self.thresholds.max_blocks.unwrap_or(u64::MAX));
502        if range.is_empty() {
503            return Ok(UnwindOutput {
504                checkpoint: input.checkpoint.with_block_number(input.unwind_to),
505            })
506        }
507
508        self.ensure_consistency(provider, input.checkpoint.block_number, Some(unwind_to))?;
509
510        // Unwind account and storage changesets, as well as receipts.
511        //
512        // This also updates `PlainStorageState` and `PlainAccountState`.
513        let bundle_state_with_receipts = provider.take_state_above(unwind_to)?;
514
515        // Prepare the input for post unwind commit hook, where an `ExExNotification` will be sent.
516        if self.exex_manager_handle.has_exexs() {
517            // Get the blocks for the unwound range.
518            let blocks = provider.recovered_block_range(range.clone())?;
519            let previous_input = self.post_unwind_commit_input.replace(Chain::new(
520                blocks,
521                bundle_state_with_receipts,
522                None,
523            ));
524
525            debug_assert!(
526                previous_input.is_none(),
527                "Previous post unwind commit input wasn't processed"
528            );
529            if let Some(previous_input) = previous_input {
530                tracing::debug!(target: "sync::stages::execution", ?previous_input, "Previous post unwind commit input wasn't processed");
531            }
532        }
533
534        // Update the checkpoint.
535        let mut stage_checkpoint = input.checkpoint.execution_stage_checkpoint();
536        if let Some(stage_checkpoint) = stage_checkpoint.as_mut() {
537            for block_number in range {
538                stage_checkpoint.progress.processed -= provider
539                    .header_by_number(block_number)?
540                    .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?
541                    .gas_used();
542            }
543        }
544        let checkpoint = if let Some(stage_checkpoint) = stage_checkpoint {
545            StageCheckpoint::new(unwind_to).with_execution_stage_checkpoint(stage_checkpoint)
546        } else {
547            StageCheckpoint::new(unwind_to)
548        };
549
550        Ok(UnwindOutput { checkpoint })
551    }
552
553    fn post_unwind_commit(&mut self) -> Result<(), StageError> {
554        let Some(chain) = self.post_unwind_commit_input.take() else { return Ok(()) };
555
556        // NOTE: We can ignore the error here, since an error means that the channel is closed,
557        // which means the manager has died, which then in turn means the node is shutting down.
558        let _ = self.exex_manager_handle.send(
559            ExExNotificationSource::Pipeline,
560            ExExNotification::ChainReverted { old: Arc::new(chain) },
561        );
562
563        Ok(())
564    }
565}
566
567fn execution_checkpoint<N>(
568    provider: &StaticFileProvider<N>,
569    start_block: BlockNumber,
570    max_block: BlockNumber,
571    checkpoint: StageCheckpoint,
572) -> Result<ExecutionCheckpoint, ProviderError>
573where
574    N: NodePrimitives<BlockHeader: reth_db_api::table::Value>,
575{
576    Ok(match checkpoint.execution_stage_checkpoint() {
577        // If checkpoint block range fully matches our range,
578        // we take the previously used stage checkpoint as-is.
579        Some(stage_checkpoint @ ExecutionCheckpoint { block_range, .. })
580            if block_range == CheckpointBlockRange::from(start_block..=max_block) =>
581        {
582            stage_checkpoint
583        }
584        // If checkpoint block range precedes our range seamlessly, we take the previously used
585        // stage checkpoint and add the amount of gas from our range to the checkpoint total.
586        Some(ExecutionCheckpoint {
587            block_range: CheckpointBlockRange { to, .. },
588            progress: EntitiesCheckpoint { processed, total },
589        }) if to == start_block - 1 => ExecutionCheckpoint {
590            block_range: CheckpointBlockRange { from: start_block, to: max_block },
591            progress: EntitiesCheckpoint {
592                processed,
593                total: total + calculate_gas_used_from_headers(provider, start_block..=max_block)?,
594            },
595        },
596        // If checkpoint block range ends on the same block as our range, we take the previously
597        // used stage checkpoint.
598        Some(ExecutionCheckpoint { block_range: CheckpointBlockRange { to, .. }, progress })
599            if to == max_block =>
600        {
601            ExecutionCheckpoint {
602                block_range: CheckpointBlockRange { from: start_block, to: max_block },
603                progress,
604            }
605        }
606        // If there's any other non-empty checkpoint, we calculate the remaining amount of total gas
607        // to be processed not including the checkpoint range.
608        Some(ExecutionCheckpoint { progress: EntitiesCheckpoint { processed, .. }, .. }) => {
609            let after_checkpoint_block_number =
610                calculate_gas_used_from_headers(provider, checkpoint.block_number + 1..=max_block)?;
611
612            ExecutionCheckpoint {
613                block_range: CheckpointBlockRange { from: start_block, to: max_block },
614                progress: EntitiesCheckpoint {
615                    processed,
616                    total: processed + after_checkpoint_block_number,
617                },
618            }
619        }
620        // Otherwise, we recalculate the whole stage checkpoint including the amount of gas
621        // already processed, if there's any.
622        _ => {
623            let genesis_block_number = provider.genesis_block_number();
624            let processed = calculate_gas_used_from_headers(
625                provider,
626                genesis_block_number..=max(start_block - 1, genesis_block_number),
627            )?;
628
629            ExecutionCheckpoint {
630                block_range: CheckpointBlockRange { from: start_block, to: max_block },
631                progress: EntitiesCheckpoint {
632                    processed,
633                    total: processed +
634                        calculate_gas_used_from_headers(provider, start_block..=max_block)?,
635                },
636            }
637        }
638    })
639}
640
641/// Calculates the total amount of gas used from the headers in the given range.
642pub fn calculate_gas_used_from_headers<N>(
643    provider: &StaticFileProvider<N>,
644    range: RangeInclusive<BlockNumber>,
645) -> Result<u64, ProviderError>
646where
647    N: NodePrimitives<BlockHeader: reth_db_api::table::Value>,
648{
649    debug!(target: "sync::stages::execution", ?range, "Calculating gas used from headers");
650
651    let mut gas_total = 0;
652
653    let start = Instant::now();
654
655    for entry in provider.fetch_range_iter(
656        StaticFileSegment::Headers,
657        *range.start()..*range.end() + 1,
658        |cursor, number| cursor.get_one::<HeaderMask<N::BlockHeader>>(number.into()),
659    )? {
660        if let Some(entry) = entry? {
661            gas_total += entry.gas_used();
662        }
663    }
664
665    let duration = start.elapsed();
666    debug!(target: "sync::stages::execution", ?range, ?duration, "Finished calculating gas used from headers");
667
668    Ok(gas_total)
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use crate::{stages::MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, test_utils::TestStageDB};
675    use alloy_primitives::{address, hex_literal::hex, keccak256, Address, B256, U256};
676    use alloy_rlp::Decodable;
677    use assert_matches::assert_matches;
678    use reth_chainspec::ChainSpecBuilder;
679    use reth_db_api::{
680        models::{metadata::StorageSettings, AccountBeforeTx},
681        transaction::{DbTx, DbTxMut},
682    };
683    use reth_ethereum_consensus::EthBeaconConsensus;
684    use reth_ethereum_primitives::Block;
685    use reth_evm_ethereum::EthEvmConfig;
686    use reth_primitives_traits::{Account, Bytecode, SealedBlock, StorageEntry};
687    use reth_provider::{
688        test_utils::create_test_provider_factory, AccountReader, BlockWriter,
689        DatabaseProviderFactory, ReceiptProvider, StaticFileProviderFactory,
690    };
691    use reth_prune::PruneModes;
692    use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig};
693    use reth_stages_api::StageUnitCheckpoint;
694    use reth_testing_utils::generators;
695    use std::collections::BTreeMap;
696
697    fn stage() -> ExecutionStage<EthEvmConfig> {
698        let evm_config =
699            EthEvmConfig::new(Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build()));
700        let consensus = Arc::new(EthBeaconConsensus::new(Arc::new(
701            ChainSpecBuilder::mainnet().berlin_activated().build(),
702        )));
703        ExecutionStage::new(
704            evm_config,
705            consensus,
706            ExecutionStageThresholds {
707                max_blocks: Some(100),
708                max_changes: None,
709                max_cumulative_gas: None,
710                max_duration: None,
711            },
712            MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD,
713            ExExManagerHandle::empty(),
714        )
715    }
716
717    #[test]
718    fn execution_checkpoint_matches() {
719        let factory = create_test_provider_factory();
720
721        let previous_stage_checkpoint = ExecutionCheckpoint {
722            block_range: CheckpointBlockRange { from: 0, to: 0 },
723            progress: EntitiesCheckpoint { processed: 1, total: 2 },
724        };
725        let previous_checkpoint = StageCheckpoint {
726            block_number: 0,
727            stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)),
728        };
729
730        let stage_checkpoint = execution_checkpoint(
731            &factory.static_file_provider(),
732            previous_stage_checkpoint.block_range.from,
733            previous_stage_checkpoint.block_range.to,
734            previous_checkpoint,
735        );
736
737        assert!(
738            matches!(stage_checkpoint, Ok(checkpoint) if checkpoint == previous_stage_checkpoint)
739        );
740    }
741
742    #[test]
743    fn execution_checkpoint_precedes() {
744        let factory = create_test_provider_factory();
745        let provider = factory.provider_rw().unwrap();
746
747        let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
748        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
749        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
750        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
751        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
752        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
753        provider
754            .static_file_provider()
755            .latest_writer(StaticFileSegment::Headers)
756            .unwrap()
757            .commit()
758            .unwrap();
759        provider.commit().unwrap();
760
761        let previous_stage_checkpoint = ExecutionCheckpoint {
762            block_range: CheckpointBlockRange { from: 0, to: 0 },
763            progress: EntitiesCheckpoint { processed: 1, total: 1 },
764        };
765        let previous_checkpoint = StageCheckpoint {
766            block_number: 1,
767            stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)),
768        };
769
770        let stage_checkpoint =
771            execution_checkpoint(&factory.static_file_provider(), 1, 1, previous_checkpoint);
772
773        assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint {
774            block_range: CheckpointBlockRange { from: 1, to: 1 },
775            progress: EntitiesCheckpoint {
776                processed,
777                total
778            }
779        }) if processed == previous_stage_checkpoint.progress.processed &&
780            total == previous_stage_checkpoint.progress.total + block.gas_used);
781    }
782
783    #[test]
784    fn execution_checkpoint_recalculate_full_previous_some() {
785        let factory = create_test_provider_factory();
786        let provider = factory.provider_rw().unwrap();
787
788        let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
789        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
790        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
791        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
792        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
793        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
794        provider
795            .static_file_provider()
796            .latest_writer(StaticFileSegment::Headers)
797            .unwrap()
798            .commit()
799            .unwrap();
800        provider.commit().unwrap();
801
802        let previous_stage_checkpoint = ExecutionCheckpoint {
803            block_range: CheckpointBlockRange { from: 0, to: 0 },
804            progress: EntitiesCheckpoint { processed: 1, total: 1 },
805        };
806        let previous_checkpoint = StageCheckpoint {
807            block_number: 1,
808            stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)),
809        };
810
811        let stage_checkpoint =
812            execution_checkpoint(&factory.static_file_provider(), 1, 1, previous_checkpoint);
813
814        assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint {
815            block_range: CheckpointBlockRange { from: 1, to: 1 },
816            progress: EntitiesCheckpoint {
817                processed,
818                total
819            }
820        }) if processed == previous_stage_checkpoint.progress.processed &&
821            total == previous_stage_checkpoint.progress.total + block.gas_used());
822    }
823
824    #[test]
825    fn execution_checkpoint_recalculate_full_previous_none() {
826        let factory = create_test_provider_factory();
827        let provider = factory.provider_rw().unwrap();
828
829        let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
830        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
831        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
832        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
833        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
834        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
835        provider
836            .static_file_provider()
837            .latest_writer(StaticFileSegment::Headers)
838            .unwrap()
839            .commit()
840            .unwrap();
841        provider.commit().unwrap();
842
843        let previous_checkpoint = StageCheckpoint { block_number: 1, stage_checkpoint: None };
844
845        let stage_checkpoint =
846            execution_checkpoint(&factory.static_file_provider(), 1, 1, previous_checkpoint);
847
848        assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint {
849            block_range: CheckpointBlockRange { from: 1, to: 1 },
850            progress: EntitiesCheckpoint {
851                processed: 0,
852                total
853            }
854        }) if total == block.gas_used);
855    }
856
857    #[tokio::test]
858    async fn sanity_execution_of_block() {
859        let factory = create_test_provider_factory();
860        let provider = factory.provider_rw().unwrap();
861        let input = ExecInput { target: Some(1), checkpoint: None };
862        let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
863        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
864        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
865        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
866        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
867        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
868        provider
869            .static_file_provider()
870            .latest_writer(StaticFileSegment::Headers)
871            .unwrap()
872            .commit()
873            .unwrap();
874        {
875            let static_file_provider = provider.static_file_provider();
876            let mut receipts_writer =
877                static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
878            receipts_writer.increment_block(0).unwrap();
879            receipts_writer.commit().unwrap();
880        }
881        provider.commit().unwrap();
882
883        // insert pre state
884        let provider = factory.provider_rw().unwrap();
885
886        let db_tx = provider.tx_ref();
887        let acc1 = address!("0x1000000000000000000000000000000000000000");
888        let acc2 = address!("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b");
889        let code = hex!("5a465a905090036002900360015500");
890        let balance = U256::from(0x3635c9adc5dea00000u128);
891        let code_hash = keccak256(code);
892        db_tx
893            .put::<tables::PlainAccountState>(
894                acc1,
895                Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) },
896            )
897            .unwrap();
898        db_tx
899            .put::<tables::PlainAccountState>(
900                acc2,
901                Account { nonce: 0, balance, bytecode_hash: None },
902            )
903            .unwrap();
904        db_tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap();
905        provider.commit().unwrap();
906
907        // execute
908
909        // If there is a pruning configuration, then it's forced to use the database.
910        // This way we test both cases.
911        let modes = [None, Some(PruneModes::default())];
912        let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([(
913            Address::random(),
914            PruneMode::Distance(100000),
915        )]));
916
917        // Tests node with database and node with static files
918        for mut mode in modes {
919            let mut provider = factory.database_provider_rw().unwrap();
920
921            if let Some(mode) = &mut mode {
922                // Simulating a full node where we write receipts to database
923                mode.receipts_log_filter = random_filter.clone();
924            }
925
926            let mut execution_stage = stage();
927            provider.set_prune_modes(mode.clone().unwrap_or_default());
928
929            let output = execution_stage.execute(&provider, input).unwrap();
930            provider.commit().unwrap();
931
932            assert_matches!(output, ExecOutput {
933                checkpoint: StageCheckpoint {
934                    block_number: 1,
935                    stage_checkpoint: Some(StageUnitCheckpoint::Execution(ExecutionCheckpoint {
936                        block_range: CheckpointBlockRange {
937                            from: 1,
938                            to: 1,
939                        },
940                        progress: EntitiesCheckpoint {
941                            processed,
942                            total
943                        }
944                    }))
945                },
946                done: true
947            } if processed == total && total == block.gas_used);
948
949            let provider = factory.provider().unwrap();
950
951            // check post state
952            let account1 = address!("0x1000000000000000000000000000000000000000");
953            let account1_info =
954                Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: Some(code_hash) };
955            let account2 = address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba");
956            let account2_info = Account {
957                balance: U256::from(0x1bc16d674ece94bau128),
958                nonce: 0x00,
959                bytecode_hash: None,
960            };
961            let account3 = address!("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b");
962            let account3_info = Account {
963                balance: U256::from(0x3635c9adc5de996b46u128),
964                nonce: 0x01,
965                bytecode_hash: None,
966            };
967
968            // assert accounts
969            assert!(
970                matches!(provider.basic_account(&account1), Ok(Some(acc)) if acc == account1_info)
971            );
972            assert!(
973                matches!(provider.basic_account(&account2), Ok(Some(acc)) if acc == account2_info)
974            );
975            assert!(
976                matches!(provider.basic_account(&account3), Ok(Some(acc)) if acc == account3_info)
977            );
978            // assert storage
979            // Get on dupsort would return only first value. This is good enough for this test.
980            assert!(matches!(
981                provider.tx_ref().get::<tables::PlainStorageState>(account1),
982                Ok(Some(entry)) if entry.key == B256::with_last_byte(1) && entry.value == U256::from(2)
983            ));
984
985            let mut provider = factory.database_provider_rw().unwrap();
986            let mut stage = stage();
987            provider.set_prune_modes(mode.unwrap_or_default());
988
989            let _result = stage
990                .unwind(
991                    &provider,
992                    UnwindInput { checkpoint: output.checkpoint, unwind_to: 0, bad_block: None },
993                )
994                .unwrap();
995            provider.commit().unwrap();
996        }
997    }
998
999    #[tokio::test]
1000    async fn sanity_execute_unwind() {
1001        let factory = create_test_provider_factory();
1002        let provider = factory.provider_rw().unwrap();
1003        let input = ExecInput { target: Some(1), checkpoint: None };
1004        let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
1005        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
1006        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
1007        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
1008        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
1009        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
1010        provider
1011            .static_file_provider()
1012            .latest_writer(StaticFileSegment::Headers)
1013            .unwrap()
1014            .commit()
1015            .unwrap();
1016        {
1017            let static_file_provider = provider.static_file_provider();
1018            let mut receipts_writer =
1019                static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
1020            receipts_writer.increment_block(0).unwrap();
1021            receipts_writer.commit().unwrap();
1022        }
1023        provider.commit().unwrap();
1024
1025        // variables
1026        let code = hex!("5a465a905090036002900360015500");
1027        let balance = U256::from(0x3635c9adc5dea00000u128);
1028        let code_hash = keccak256(code);
1029        // pre state
1030        let provider = factory.provider_rw().unwrap();
1031
1032        let db_tx = provider.tx_ref();
1033        let acc1 = address!("0x1000000000000000000000000000000000000000");
1034        let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) };
1035        let acc2 = address!("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b");
1036        let acc2_info = Account { nonce: 0, balance, bytecode_hash: None };
1037
1038        db_tx.put::<tables::PlainAccountState>(acc1, acc1_info).unwrap();
1039        db_tx.put::<tables::PlainAccountState>(acc2, acc2_info).unwrap();
1040        db_tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap();
1041        provider.commit().unwrap();
1042
1043        // execute
1044        let mut provider = factory.database_provider_rw().unwrap();
1045
1046        // If there is a pruning configuration, then it's forced to use the database.
1047        // This way we test both cases.
1048        let modes = [None, Some(PruneModes::default())];
1049        let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([(
1050            Address::random(),
1051            PruneMode::Before(100000),
1052        )]));
1053
1054        // Tests node with database and node with static files
1055        for mut mode in modes {
1056            if let Some(mode) = &mut mode {
1057                // Simulating a full node where we write receipts to database
1058                mode.receipts_log_filter = random_filter.clone();
1059            }
1060
1061            // Test Execution
1062            let mut execution_stage = stage();
1063            provider.set_prune_modes(mode.clone().unwrap_or_default());
1064
1065            let result = execution_stage.execute(&provider, input).unwrap();
1066            provider.commit().unwrap();
1067
1068            // Test Unwind
1069            provider = factory.database_provider_rw().unwrap();
1070            let mut stage = stage();
1071            provider.set_prune_modes(mode.clone().unwrap_or_default());
1072
1073            let result = stage
1074                .unwind(
1075                    &provider,
1076                    UnwindInput { checkpoint: result.checkpoint, unwind_to: 0, bad_block: None },
1077                )
1078                .unwrap();
1079
1080            provider.static_file_provider().commit().unwrap();
1081
1082            assert_matches!(result, UnwindOutput {
1083                checkpoint: StageCheckpoint {
1084                    block_number: 0,
1085                    stage_checkpoint: Some(StageUnitCheckpoint::Execution(ExecutionCheckpoint {
1086                        block_range: CheckpointBlockRange {
1087                            from: 1,
1088                            to: 1,
1089                        },
1090                        progress: EntitiesCheckpoint {
1091                            processed: 0,
1092                            total
1093                        }
1094                    }))
1095                }
1096            } if total == block.gas_used);
1097
1098            // assert unwind stage
1099            assert!(matches!(provider.basic_account(&acc1), Ok(Some(acc)) if acc == acc1_info));
1100            assert!(matches!(provider.basic_account(&acc2), Ok(Some(acc)) if acc == acc2_info));
1101
1102            let miner_acc = address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba");
1103            assert!(matches!(provider.basic_account(&miner_acc), Ok(None)));
1104
1105            assert!(matches!(provider.receipt(0), Ok(None)));
1106        }
1107    }
1108
1109    #[tokio::test]
1110    async fn test_selfdestruct() {
1111        let test_db = TestStageDB::default();
1112        let provider = test_db.factory.database_provider_rw().unwrap();
1113        let input = ExecInput { target: Some(1), checkpoint: None };
1114        let mut genesis_rlp = hex!("f901f8f901f3a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0c9ceb8372c88cb461724d8d3d87e8b933f6fc5f679d4841800e662f4428ffd0da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000080830f4240808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
1115        let genesis = SealedBlock::<Block>::decode(&mut genesis_rlp).unwrap();
1116        let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice();
1117        let block = SealedBlock::<Block>::decode(&mut block_rlp).unwrap();
1118        provider.insert_block(genesis.try_recover().unwrap()).unwrap();
1119        provider.insert_block(block.clone().try_recover().unwrap()).unwrap();
1120        provider
1121            .static_file_provider()
1122            .latest_writer(StaticFileSegment::Headers)
1123            .unwrap()
1124            .commit()
1125            .unwrap();
1126        {
1127            let static_file_provider = provider.static_file_provider();
1128            let mut receipts_writer =
1129                static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
1130            receipts_writer.increment_block(0).unwrap();
1131            receipts_writer.commit().unwrap();
1132        }
1133        provider.commit().unwrap();
1134
1135        // variables
1136        let caller_address = address!("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b");
1137        let destroyed_address = address!("0x095e7baea6a6c7c4c2dfeb977efac326af552d87");
1138        let beneficiary_address = address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba");
1139
1140        let code = hex!("73095e7baea6a6c7c4c2dfeb977efac326af552d8731ff00");
1141        let balance = U256::from(0x0de0b6b3a7640000u64);
1142        let code_hash = keccak256(code);
1143
1144        // pre state
1145        let caller_info = Account { nonce: 0, balance, bytecode_hash: None };
1146        let destroyed_info =
1147            Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) };
1148
1149        // set account
1150        let provider = test_db.factory.provider_rw().unwrap();
1151        provider.tx_ref().put::<tables::PlainAccountState>(caller_address, caller_info).unwrap();
1152        provider
1153            .tx_ref()
1154            .put::<tables::PlainAccountState>(destroyed_address, destroyed_info)
1155            .unwrap();
1156        provider
1157            .tx_ref()
1158            .put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into()))
1159            .unwrap();
1160        // set storage to check when account gets destroyed.
1161        provider
1162            .tx_ref()
1163            .put::<tables::PlainStorageState>(
1164                destroyed_address,
1165                StorageEntry { key: B256::ZERO, value: U256::ZERO },
1166            )
1167            .unwrap();
1168        provider
1169            .tx_ref()
1170            .put::<tables::PlainStorageState>(
1171                destroyed_address,
1172                StorageEntry { key: B256::with_last_byte(1), value: U256::from(1u64) },
1173            )
1174            .unwrap();
1175
1176        provider.commit().unwrap();
1177
1178        // execute
1179        let provider = test_db.factory.database_provider_rw().unwrap();
1180        let mut execution_stage = stage();
1181        let _ = execution_stage.execute(&provider, input).unwrap();
1182        provider.commit().unwrap();
1183
1184        // assert unwind stage
1185        let provider = test_db.factory.database_provider_rw().unwrap();
1186        assert!(matches!(provider.basic_account(&destroyed_address), Ok(None)));
1187
1188        assert!(matches!(
1189            provider.tx_ref().get::<tables::PlainStorageState>(destroyed_address),
1190            Ok(None)
1191        ));
1192        // drops tx so that it returns write privilege to test_tx
1193        drop(provider);
1194        let plain_accounts = test_db.table::<tables::PlainAccountState>().unwrap();
1195        let plain_storage = test_db.table::<tables::PlainStorageState>().unwrap();
1196
1197        assert_eq!(
1198            plain_accounts,
1199            vec![
1200                (
1201                    beneficiary_address,
1202                    Account {
1203                        nonce: 0,
1204                        balance: U256::from(0x1bc16d674eca30a0u64),
1205                        bytecode_hash: None
1206                    }
1207                ),
1208                (
1209                    caller_address,
1210                    Account {
1211                        nonce: 1,
1212                        balance: U256::from(0xde0b6b3a761cf60u64),
1213                        bytecode_hash: None
1214                    }
1215                )
1216            ]
1217        );
1218        assert!(plain_storage.is_empty());
1219
1220        let account_changesets = test_db.table::<tables::AccountChangeSets>().unwrap();
1221        let storage_changesets = test_db.table::<tables::StorageChangeSets>().unwrap();
1222
1223        assert_eq!(
1224            account_changesets,
1225            vec![
1226                (
1227                    block.number,
1228                    AccountBeforeTx { address: destroyed_address, info: Some(destroyed_info) },
1229                ),
1230                (block.number, AccountBeforeTx { address: beneficiary_address, info: None }),
1231                (
1232                    block.number,
1233                    AccountBeforeTx { address: caller_address, info: Some(caller_info) }
1234                ),
1235            ]
1236        );
1237
1238        assert_eq!(
1239            storage_changesets,
1240            vec![
1241                (
1242                    (block.number, destroyed_address).into(),
1243                    StorageEntry { key: B256::ZERO, value: U256::ZERO }
1244                ),
1245                (
1246                    (block.number, destroyed_address).into(),
1247                    StorageEntry { key: B256::with_last_byte(1), value: U256::from(1u64) }
1248                )
1249            ]
1250        );
1251    }
1252
1253    #[test]
1254    fn test_ensure_consistency_with_skipped_receipts() {
1255        // Test that ensure_consistency allows the case where receipts are intentionally
1256        // skipped. When receipts are skipped, blocks are still incremented in static files
1257        // but no receipt data is written.
1258
1259        let factory = create_test_provider_factory();
1260        factory.set_storage_settings_cache(
1261            StorageSettings::legacy().with_receipts_in_static_files(true),
1262        );
1263
1264        // Setup with block 1
1265        let provider_rw = factory.database_provider_rw().unwrap();
1266        let mut rng = generators::rng();
1267        let genesis = generators::random_block(&mut rng, 0, Default::default());
1268        provider_rw.insert_block(genesis.try_recover().unwrap()).expect("failed to insert genesis");
1269        let block = generators::random_block(
1270            &mut rng,
1271            1,
1272            generators::BlockParams { tx_count: Some(2), ..Default::default() },
1273        );
1274        provider_rw.insert_block(block.try_recover().unwrap()).expect("failed to insert block");
1275
1276        let static_file_provider = provider_rw.static_file_provider();
1277        static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap().commit().unwrap();
1278
1279        // Simulate skipped receipts: increment block in receipts static file but don't write
1280        // receipts
1281        {
1282            let mut receipts_writer =
1283                static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
1284            receipts_writer.increment_block(0).unwrap();
1285            receipts_writer.increment_block(1).unwrap();
1286            receipts_writer.commit().unwrap();
1287        } // Explicitly drop receipts_writer here
1288
1289        provider_rw.commit().expect("failed to commit");
1290
1291        // Verify blocks are incremented but no receipts written
1292        assert_eq!(
1293            factory
1294                .static_file_provider()
1295                .get_highest_static_file_block(StaticFileSegment::Receipts),
1296            Some(1)
1297        );
1298        assert_eq!(
1299            factory.static_file_provider().get_highest_static_file_tx(StaticFileSegment::Receipts),
1300            None
1301        );
1302
1303        // Create execution stage
1304        let stage = stage();
1305
1306        // Run ensure_consistency - should NOT error
1307        // Block numbers match (both at 1), but tx numbers don't (database has txs, static files
1308        // don't) This is fine - receipts are being skipped
1309        let provider = factory.provider().unwrap();
1310        stage
1311            .ensure_consistency(&provider, 1, None)
1312            .expect("ensure_consistency should succeed when receipts are intentionally skipped");
1313    }
1314}