reth_stages/stages/
execution.rs

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