kona_executor/builder/
assemble.rs1use super::StatelessL2Builder;
4use crate::{
5 ExecutorError, ExecutorResult, TrieDBError, TrieDBProvider,
6 util::encode_holocene_eip_1559_params,
7};
8use alloc::vec::Vec;
9use alloy_consensus::{EMPTY_OMMER_ROOT_HASH, Header, Sealed};
10use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH};
11use alloy_evm::{EvmFactory, block::BlockExecutionResult};
12use alloy_primitives::{B256, Sealable, U256, logs_bloom};
13use alloy_trie::EMPTY_ROOT_HASH;
14use kona_genesis::RollupConfig;
15use kona_mpt::{TrieHinter, ordered_trie_with_encoder};
16use kona_protocol::{OutputRoot, Predeploys};
17use op_alloy_consensus::OpReceiptEnvelope;
18use op_alloy_rpc_types_engine::OpPayloadAttributes;
19use revm::{context::BlockEnv, database::BundleState};
20
21impl<P, H, Evm> StatelessL2Builder<'_, P, H, Evm>
22where
23 P: TrieDBProvider,
24 H: TrieHinter,
25 Evm: EvmFactory,
26{
27 pub(crate) fn seal_block(
30 &mut self,
31 attrs: &OpPayloadAttributes,
32 parent_hash: B256,
33 block_env: &BlockEnv,
34 ex_result: &BlockExecutionResult<OpReceiptEnvelope>,
35 bundle: BundleState,
36 ) -> ExecutorResult<Sealed<Header>> {
37 let timestamp = block_env.timestamp.saturating_to::<u64>();
38
39 let state_root = self.trie_db.state_root(&bundle)?;
41 let transactions_root = ordered_trie_with_encoder(
42 attrs.transactions.as_ref().expect("Transactions must be non-empty"),
46 |tx, buf| buf.put_slice(tx.as_ref()),
47 )
48 .root();
49 let receipts_root = compute_receipts_root(&ex_result.receipts, self.config, timestamp);
50 let withdrawals_root = if self.config.is_isthmus_active(timestamp) {
51 Some(self.message_passer_account(block_env.number.saturating_to::<u64>())?)
52 } else if self.config.is_canyon_active(timestamp) {
53 Some(EMPTY_ROOT_HASH)
54 } else {
55 None
56 };
57
58 let logs_bloom = logs_bloom(ex_result.receipts.iter().flat_map(|r| r.logs()));
60
61 let (blob_gas_used, excess_blob_gas) = self
63 .config
64 .is_ecotone_active(timestamp)
65 .then_some((Some(0), Some(0)))
66 .unwrap_or_default();
67
68 let encoded_base_fee_params = self
74 .config
75 .is_holocene_active(timestamp)
76 .then(|| encode_holocene_eip_1559_params(self.config, attrs))
77 .transpose()?
78 .unwrap_or_default();
79
80 let requests_hash = self.config.is_isthmus_active(timestamp).then_some(EMPTY_REQUESTS_HASH);
82
83 let header = Header {
85 parent_hash,
86 ommers_hash: EMPTY_OMMER_ROOT_HASH,
87 beneficiary: attrs.payload_attributes.suggested_fee_recipient,
88 state_root,
89 transactions_root,
90 receipts_root,
91 withdrawals_root,
92 requests_hash,
93 logs_bloom,
94 difficulty: U256::ZERO,
95 number: block_env.number.saturating_to::<u64>(),
96 gas_limit: attrs.gas_limit.ok_or(ExecutorError::MissingGasLimit)?,
97 gas_used: ex_result.gas_used,
98 timestamp,
99 mix_hash: attrs.payload_attributes.prev_randao,
100 nonce: Default::default(),
101 base_fee_per_gas: Some(block_env.basefee),
102 blob_gas_used,
103 excess_blob_gas: excess_blob_gas.and_then(|x| x.try_into().ok()),
104 parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root,
105 extra_data: encoded_base_fee_params,
106 }
107 .seal_slow();
108
109 Ok(header)
110 }
111
112 pub fn compute_output_root(&mut self) -> ExecutorResult<B256> {
121 let parent_number = self.trie_db.parent_block_header().number;
122
123 info!(
124 target: "block_builder",
125 state_root = ?self.trie_db.parent_block_header().state_root,
126 block_number = parent_number,
127 "Computing output root",
128 );
129
130 let storage_root = self.message_passer_account(parent_number)?;
131 let parent_header = self.trie_db.parent_block_header();
132
133 let output_root_hash =
135 OutputRoot::from_parts(parent_header.state_root, storage_root, parent_header.seal())
136 .hash();
137
138 info!(
139 target: "block_builder",
140 block_number = parent_number,
141 output_root = ?output_root_hash,
142 "Computed output root",
143 );
144
145 Ok(output_root_hash)
147 }
148
149 fn message_passer_account(&mut self, block_number: u64) -> Result<B256, TrieDBError> {
151 match self.trie_db.storage_roots().get(&Predeploys::L2_TO_L1_MESSAGE_PASSER) {
152 Some(storage_root) => Ok(storage_root.blind()),
153 None => Ok(self
154 .trie_db
155 .get_trie_account(&Predeploys::L2_TO_L1_MESSAGE_PASSER, block_number)?
156 .ok_or(TrieDBError::MissingAccountInfo)?
157 .storage_root),
158 }
159 }
160}
161
162pub fn compute_receipts_root(
164 receipts: &[OpReceiptEnvelope],
165 config: &RollupConfig,
166 timestamp: u64,
167) -> B256 {
168 if config.is_regolith_active(timestamp) && !config.is_canyon_active(timestamp) {
173 let receipts = receipts
174 .iter()
175 .cloned()
176 .map(|receipt| match receipt {
177 OpReceiptEnvelope::Deposit(mut deposit_receipt) => {
178 deposit_receipt.receipt.deposit_nonce = None;
179 OpReceiptEnvelope::Deposit(deposit_receipt)
180 }
181 _ => receipt,
182 })
183 .collect::<Vec<_>>();
184
185 ordered_trie_with_encoder(receipts.as_ref(), |receipt, mut buf| {
186 receipt.encode_2718(&mut buf)
187 })
188 .root()
189 } else {
190 ordered_trie_with_encoder(receipts, |receipt, mut buf| receipt.encode_2718(&mut buf)).root()
191 }
192}