Skip to main content

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