Skip to main content

reth_stages/stages/execution/
mod.rs

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