reth_optimism_flashblocks/
worker.rs

1use crate::PendingFlashBlock;
2use alloy_eips::{eip2718::WithEncoded, BlockNumberOrTag};
3use alloy_primitives::B256;
4use op_alloy_rpc_types_engine::OpFlashblockPayloadBase;
5use reth_chain_state::{ComputedTrieData, ExecutedBlock};
6use reth_errors::RethError;
7use reth_evm::{
8    execute::{BlockBuilder, BlockBuilderOutcome},
9    ConfigureEvm,
10};
11use reth_execution_types::ExecutionOutcome;
12use reth_primitives_traits::{
13    AlloyBlockHeader, BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered,
14};
15use reth_revm::{cached::CachedReads, database::StateProviderDatabase, db::State};
16use reth_rpc_eth_types::{EthApiError, PendingBlock};
17use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, StateProviderFactory};
18use std::{
19    sync::Arc,
20    time::{Duration, Instant},
21};
22use tracing::trace;
23
24/// The `FlashBlockBuilder` builds [`PendingBlock`] out of a sequence of transactions.
25#[derive(Debug)]
26pub(crate) struct FlashBlockBuilder<EvmConfig, Provider> {
27    evm_config: EvmConfig,
28    provider: Provider,
29}
30
31impl<EvmConfig, Provider> FlashBlockBuilder<EvmConfig, Provider> {
32    pub(crate) const fn new(evm_config: EvmConfig, provider: Provider) -> Self {
33        Self { evm_config, provider }
34    }
35
36    pub(crate) const fn provider(&self) -> &Provider {
37        &self.provider
38    }
39}
40
41pub(crate) struct BuildArgs<I> {
42    pub(crate) base: OpFlashblockPayloadBase,
43    pub(crate) transactions: I,
44    pub(crate) cached_state: Option<(B256, CachedReads)>,
45    pub(crate) last_flashblock_index: u64,
46    pub(crate) last_flashblock_hash: B256,
47    pub(crate) compute_state_root: bool,
48}
49
50impl<N, EvmConfig, Provider> FlashBlockBuilder<EvmConfig, Provider>
51where
52    N: NodePrimitives,
53    EvmConfig: ConfigureEvm<Primitives = N, NextBlockEnvCtx: From<OpFlashblockPayloadBase> + Unpin>,
54    Provider: StateProviderFactory
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    /// [`OpFlashblockPayloadBase`] 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!(target: "flashblocks", "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!(target: "flashblocks", 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                trace!(target: "flashblocks", "Computing block state root");
110                builder.finish(&state_provider)?
111            } else {
112                builder.finish(NoopProvider::default())?
113            };
114
115        let execution_outcome = ExecutionOutcome::new(
116            state.take_bundle(),
117            vec![execution_result.receipts],
118            block.number(),
119            vec![execution_result.requests],
120        );
121
122        let pending_block = PendingBlock::with_executed_block(
123            Instant::now() + Duration::from_secs(1),
124            ExecutedBlock::new(
125                block.into(),
126                Arc::new(execution_outcome),
127                ComputedTrieData::without_trie_input(
128                    Arc::new(hashed_state.into_sorted()),
129                    Arc::default(),
130                ),
131            ),
132        );
133        let pending_flashblock = PendingFlashBlock::new(
134            pending_block,
135            args.last_flashblock_index,
136            args.last_flashblock_hash,
137            args.compute_state_root,
138        );
139
140        Ok(Some((pending_flashblock, request_cache)))
141    }
142}
143
144impl<EvmConfig: Clone, Provider: Clone> Clone for FlashBlockBuilder<EvmConfig, Provider> {
145    fn clone(&self) -> Self {
146        Self { evm_config: self.evm_config.clone(), provider: self.provider.clone() }
147    }
148}