kona_executor/builder/
core.rs

1//! The [StatelessL2Builder] is a block builder that pulls state from a [TrieDB] during execution.
2
3use crate::{ExecutorError, ExecutorResult, TrieDB, TrieDBError, TrieDBProvider};
4use alloc::{string::ToString, vec::Vec};
5use alloy_consensus::{Header, Sealed, crypto::RecoveryError};
6use alloy_evm::{
7    EvmFactory, FromRecoveredTx, FromTxWithEncoded,
8    block::{BlockExecutionResult, BlockExecutor, BlockExecutorFactory},
9};
10use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutorFactory, block::OpAlloyReceiptBuilder};
11use core::fmt::Debug;
12use kona_genesis::RollupConfig;
13use kona_mpt::TrieHinter;
14use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope};
15use op_alloy_rpc_types_engine::OpPayloadAttributes;
16use op_revm::OpSpecId;
17use revm::database::{State, states::bundle_state::BundleRetention};
18
19/// The [`StatelessL2Builder`] is an OP Stack block builder that traverses a merkle patricia trie
20/// via the [`TrieDB`] during execution.
21#[derive(Debug)]
22pub struct StatelessL2Builder<'a, P, H, Evm>
23where
24    P: TrieDBProvider,
25    H: TrieHinter,
26    Evm: EvmFactory,
27{
28    /// The [RollupConfig].
29    pub(crate) config: &'a RollupConfig,
30    /// The inner trie database.
31    pub(crate) trie_db: TrieDB<P, H>,
32    /// The executor factory, used to create new [`op_revm::OpEvm`] instances for block building
33    /// routines.
34    pub(crate) factory: OpBlockExecutorFactory<OpAlloyReceiptBuilder, RollupConfig, Evm>,
35}
36
37impl<'a, P, H, Evm> StatelessL2Builder<'a, P, H, Evm>
38where
39    P: TrieDBProvider + Debug,
40    H: TrieHinter + Debug,
41    Evm: EvmFactory<Spec = OpSpecId> + 'static,
42    <Evm as EvmFactory>::Tx: FromTxWithEncoded<OpTxEnvelope> + FromRecoveredTx<OpTxEnvelope>,
43{
44    /// Creates a new [StatelessL2Builder] instance.
45    pub fn new(
46        config: &'a RollupConfig,
47        evm_factory: Evm,
48        provider: P,
49        hinter: H,
50        parent_header: Sealed<Header>,
51    ) -> Self {
52        let trie_db = TrieDB::new(parent_header, provider, hinter);
53        let factory = OpBlockExecutorFactory::new(
54            OpAlloyReceiptBuilder::default(),
55            config.clone(),
56            evm_factory,
57        );
58        Self { config, trie_db, factory }
59    }
60
61    /// Builds a new block on top of the parent state, using the given [`OpPayloadAttributes`].
62    pub fn build_block(
63        &mut self,
64        attrs: OpPayloadAttributes,
65    ) -> ExecutorResult<BlockBuildingOutcome> {
66        // Step 1. Set up the execution environment.
67        let base_fee_params =
68            Self::active_base_fee_params(self.config, self.trie_db.parent_block_header(), &attrs)?;
69        let evm_env = self.evm_env(
70            self.config.spec_id(attrs.payload_attributes.timestamp),
71            self.trie_db.parent_block_header(),
72            &attrs,
73            &base_fee_params,
74        )?;
75        let block_env = evm_env.block_env().clone();
76        let parent_hash = self.trie_db.parent_block_header().seal();
77
78        // Attempt to send a payload witness hint to the host. This hint instructs the host to
79        // populate its preimage store with the preimages required to statelessly execute
80        // this payload. This feature is experimental, so if the hint fails, we continue
81        // without it and fall back on on-demand preimage fetching for execution.
82        self.trie_db
83            .hinter
84            .hint_execution_witness(parent_hash, &attrs)
85            .map_err(|e| TrieDBError::Provider(e.to_string()))?;
86
87        info!(
88            target: "block_builder",
89            block_number = %block_env.number,
90            block_timestamp = %block_env.timestamp,
91            block_gas_limit = block_env.gas_limit,
92            transactions = attrs.transactions.as_ref().map_or(0, |txs| txs.len()),
93            "Beginning block building."
94        );
95
96        // Step 2. Create the executor, using the trie database.
97        let mut state = State::builder()
98            .with_database(&mut self.trie_db)
99            .with_bundle_update()
100            .without_state_clear()
101            .build();
102        let evm = self.factory.evm_factory().create_evm(&mut state, evm_env);
103        let ctx = OpBlockExecutionCtx {
104            parent_hash,
105            parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root,
106            // This field is unused for individual block building jobs.
107            extra_data: Default::default(),
108        };
109        let executor = self.factory.create_executor(evm, ctx);
110
111        // Step 3. Execute the block containing the transactions within the payload attributes.
112        let transactions = attrs
113            .recovered_transactions_with_encoded()
114            .collect::<Result<Vec<_>, RecoveryError>>()
115            .map_err(ExecutorError::Recovery)?;
116        let ex_result = executor.execute_block(transactions.iter())?;
117
118        info!(
119            target: "block_builder",
120            gas_used = ex_result.gas_used,
121            gas_limit = block_env.gas_limit,
122            "Finished block building. Beginning sealing job."
123        );
124
125        // Step 4. Merge state transitions and seal the block.
126        state.merge_transitions(BundleRetention::Reverts);
127        let bundle = state.take_bundle();
128        let header = self.seal_block(&attrs, parent_hash, &block_env, &ex_result, bundle)?;
129
130        info!(
131            target: "block_builder",
132            number = header.number,
133            hash = ?header.seal(),
134            state_root = ?header.state_root,
135            transactions_root = ?header.transactions_root,
136            receipts_root = ?header.receipts_root,
137            "Sealed new block",
138        );
139
140        // Update the parent block hash in the state database, preparing for the next block.
141        self.trie_db.set_parent_block_header(header.clone());
142        Ok((header, ex_result).into())
143    }
144}
145
146/// The outcome of a block building operation, returning the sealed block [`Header`] and the
147/// [`BlockExecutionResult`].
148#[derive(Debug, Clone)]
149pub struct BlockBuildingOutcome {
150    /// The block header.
151    pub header: Sealed<Header>,
152    /// The block execution result.
153    pub execution_result: BlockExecutionResult<OpReceiptEnvelope>,
154}
155
156impl From<(Sealed<Header>, BlockExecutionResult<OpReceiptEnvelope>)> for BlockBuildingOutcome {
157    fn from(
158        (header, execution_result): (Sealed<Header>, BlockExecutionResult<OpReceiptEnvelope>),
159    ) -> Self {
160        Self { header, execution_result }
161    }
162}
163
164#[cfg(test)]
165mod test {
166    use crate::test_utils::run_test_fixture;
167    use rstest::rstest;
168    use std::path::PathBuf;
169
170    #[rstest]
171    #[tokio::test]
172    async fn test_statelessly_execute_block(
173        #[base_dir = "./testdata"]
174        #[files("*.tar.gz")]
175        path: PathBuf,
176    ) {
177        run_test_fixture(path).await;
178    }
179}