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