reth_optimism_flashblocks/
worker.rs

1use crate::{ExecutionPayloadBaseV1, PendingFlashBlock};
2use alloy_eips::{eip2718::WithEncoded, BlockNumberOrTag};
3use alloy_primitives::B256;
4use reth_chain_state::{CanonStateSubscriptions, ExecutedBlock};
5use reth_errors::RethError;
6use reth_evm::{
7    execute::{BlockBuilder, BlockBuilderOutcome},
8    ConfigureEvm,
9};
10use reth_execution_types::ExecutionOutcome;
11use reth_primitives_traits::{
12    AlloyBlockHeader, BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered,
13};
14use reth_revm::{cached::CachedReads, database::StateProviderDatabase, db::State};
15use reth_rpc_eth_types::{EthApiError, PendingBlock};
16use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, StateProviderFactory};
17use std::{
18    sync::Arc,
19    time::{Duration, Instant},
20};
21use tracing::trace;
22
23/// The `FlashBlockBuilder` builds [`PendingBlock`] out of a sequence of transactions.
24#[derive(Debug)]
25pub(crate) struct FlashBlockBuilder<EvmConfig, Provider> {
26    evm_config: EvmConfig,
27    provider: Provider,
28}
29
30impl<EvmConfig, Provider> FlashBlockBuilder<EvmConfig, Provider> {
31    pub(crate) const fn new(evm_config: EvmConfig, provider: Provider) -> Self {
32        Self { evm_config, provider }
33    }
34
35    pub(crate) const fn provider(&self) -> &Provider {
36        &self.provider
37    }
38}
39
40pub(crate) struct BuildArgs<I> {
41    pub(crate) base: ExecutionPayloadBaseV1,
42    pub(crate) transactions: I,
43    pub(crate) cached_state: Option<(B256, CachedReads)>,
44    pub(crate) last_flashblock_index: u64,
45    pub(crate) last_flashblock_hash: B256,
46    pub(crate) compute_state_root: bool,
47}
48
49impl<N, EvmConfig, Provider> FlashBlockBuilder<EvmConfig, Provider>
50where
51    N: NodePrimitives,
52    EvmConfig: ConfigureEvm<Primitives = N, NextBlockEnvCtx: From<ExecutionPayloadBaseV1> + Unpin>,
53    Provider: StateProviderFactory
54        + CanonStateSubscriptions<Primitives = N>
55        + BlockReaderIdExt<
56            Header = HeaderTy<N>,
57            Block = BlockTy<N>,
58            Transaction = N::SignedTx,
59            Receipt = ReceiptTy<N>,
60        > + Unpin,
61{
62    /// Returns the [`PendingFlashBlock`] made purely out of transactions and
63    /// [`ExecutionPayloadBaseV1`] in `args`.
64    ///
65    /// Returns `None` if the flashblock doesn't attach to the latest header.
66    pub(crate) fn execute<I: IntoIterator<Item = WithEncoded<Recovered<N::SignedTx>>>>(
67        &self,
68        mut args: BuildArgs<I>,
69    ) -> eyre::Result<Option<(PendingFlashBlock<N>, CachedReads)>> {
70        trace!("Attempting new pending block from flashblocks");
71
72        let latest = self
73            .provider
74            .latest_header()?
75            .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;
76        let latest_hash = latest.hash();
77
78        if args.base.parent_hash != latest_hash {
79            trace!(flashblock_parent = ?args.base.parent_hash, local_latest=?latest.num_hash(),"Skipping non consecutive flashblock");
80            // doesn't attach to the latest block
81            return Ok(None)
82        }
83
84        let state_provider = self.provider.history_by_block_hash(latest.hash())?;
85
86        let mut request_cache = args
87            .cached_state
88            .take()
89            .filter(|(hash, _)| hash == &latest_hash)
90            .map(|(_, state)| state)
91            .unwrap_or_default();
92        let cached_db = request_cache.as_db_mut(StateProviderDatabase::new(&state_provider));
93        let mut state = State::builder().with_database(cached_db).with_bundle_update().build();
94
95        let mut builder = self
96            .evm_config
97            .builder_for_next_block(&mut state, &latest, args.base.into())
98            .map_err(RethError::other)?;
99
100        builder.apply_pre_execution_changes()?;
101
102        for tx in args.transactions {
103            let _gas_used = builder.execute_transaction(tx)?;
104        }
105
106        // if the real state root should be computed
107        let BlockBuilderOutcome { execution_result, block, hashed_state, .. } =
108            if args.compute_state_root {
109                builder.finish(&state_provider)?
110            } else {
111                builder.finish(NoopProvider::default())?
112            };
113
114        let execution_outcome = ExecutionOutcome::new(
115            state.take_bundle(),
116            vec![execution_result.receipts],
117            block.number(),
118            vec![execution_result.requests],
119        );
120
121        let pending_block = PendingBlock::with_executed_block(
122            Instant::now() + Duration::from_secs(1),
123            ExecutedBlock {
124                recovered_block: block.into(),
125                execution_output: Arc::new(execution_outcome),
126                hashed_state: Arc::new(hashed_state),
127            },
128        );
129        let pending_flashblock = PendingFlashBlock::new(
130            pending_block,
131            args.last_flashblock_index,
132            args.last_flashblock_hash,
133            args.compute_state_root,
134        );
135
136        Ok(Some((pending_flashblock, request_cache)))
137    }
138}
139
140impl<EvmConfig: Clone, Provider: Clone> Clone for FlashBlockBuilder<EvmConfig, Provider> {
141    fn clone(&self) -> Self {
142        Self { evm_config: self.evm_config.clone(), provider: self.provider.clone() }
143    }
144}