Skip to main content

tempo_evm/
assemble.rs

1use crate::{
2    TempoEvmConfig, TempoEvmFactory, block::TempoReceiptBuilder, context::TempoBlockExecutionCtx,
3};
4use alloy_evm::{block::BlockExecutionError, eth::EthBlockExecutorFactory};
5use alloy_primitives::{B256, Bloom};
6use reth_evm::execute::{BlockAssembler, BlockAssemblerInput};
7use reth_evm_ethereum::EthBlockAssembler;
8use reth_primitives_traits::SealedHeader;
9use std::sync::Arc;
10use tempo_chainspec::TempoChainSpec;
11use tempo_primitives::TempoHeader;
12
13/// Assembler for Tempo blocks.
14#[derive(Debug, Clone)]
15pub struct TempoBlockAssembler {
16    pub(crate) inner: EthBlockAssembler<TempoChainSpec>,
17}
18
19impl TempoBlockAssembler {
20    pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
21        Self {
22            inner: EthBlockAssembler::new(chain_spec),
23        }
24    }
25
26    pub fn assemble_block(
27        &self,
28        input: BlockAssemblerInput<'_, '_, TempoEvmConfig, TempoHeader>,
29        transactions_root: Option<B256>,
30        receipts_root: Option<B256>,
31        receipts_bloom: Option<Bloom>,
32    ) -> Result<tempo_primitives::Block, BlockExecutionError> {
33        let BlockAssemblerInput {
34            evm_env,
35            execution_ctx:
36                TempoBlockExecutionCtx {
37                    inner,
38                    general_gas_limit,
39                    shared_gas_limit,
40                    validator_set: _,
41                    consensus_context,
42                    subblock_fee_recipients: _,
43                },
44            parent,
45            transactions,
46            output,
47            bundle_state,
48            state_provider,
49            state_root,
50            block_access_list_hash,
51            ..
52        } = input;
53
54        let parent = SealedHeader::new_unhashed(parent.clone().into_header().inner);
55
56        let timestamp_millis_part = evm_env.block_env.timestamp_millis_part;
57
58        // Delegate block building to the inner assembler
59        let block = self.inner.assemble_block(
60            BlockAssemblerInput::<
61                EthBlockExecutorFactory<TempoReceiptBuilder, TempoChainSpec, TempoEvmFactory>,
62            >::new(
63                evm_env,
64                inner,
65                &parent,
66                transactions,
67                output,
68                bundle_state,
69                state_provider,
70                state_root,
71                block_access_list_hash,
72            ),
73            transactions_root,
74            receipts_root,
75            receipts_bloom,
76        )?;
77
78        Ok(block.map_header(|inner| TempoHeader {
79            inner,
80            general_gas_limit,
81            timestamp_millis_part,
82            shared_gas_limit,
83            consensus_context,
84        }))
85    }
86}
87
88impl BlockAssembler<TempoEvmConfig> for TempoBlockAssembler {
89    type Block = tempo_primitives::Block;
90
91    fn assemble_block(
92        &self,
93        input: BlockAssemblerInput<'_, '_, TempoEvmConfig, TempoHeader>,
94    ) -> Result<Self::Block, BlockExecutionError> {
95        self.assemble_block(input, None, None, None)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use alloy_consensus::{Signed, TxLegacy};
103    use alloy_evm::{EvmEnv, block::BlockExecutionResult, eth::EthBlockExecutionCtx};
104    use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256};
105    use reth_chainspec::EthChainSpec;
106    use reth_evm::execute::BlockAssembler;
107    use reth_primitives_traits::SealedHeader;
108    use reth_storage_api::noop::NoopProvider;
109    use revm::{context::BlockEnv, database::BundleState};
110    use std::collections::HashMap;
111    use tempo_chainspec::spec::MODERATO;
112    use tempo_primitives::{
113        TempoHeader, TempoPrimitives, TempoReceipt, TempoTxEnvelope, TempoTxType,
114    };
115    use tempo_revm::TempoBlockEnv;
116
117    fn create_legacy_tx() -> TempoTxEnvelope {
118        let tx = TxLegacy {
119            chain_id: Some(1),
120            nonce: 0,
121            gas_price: 1,
122            gas_limit: 21000,
123            to: TxKind::Call(Address::ZERO),
124            value: U256::ZERO,
125            input: Bytes::new(),
126        };
127        TempoTxEnvelope::Legacy(Signed::new_unhashed(tx, Signature::test_signature()))
128    }
129
130    fn create_test_receipt(gas_used: u64) -> TempoReceipt {
131        TempoReceipt {
132            tx_type: TempoTxType::Legacy,
133            success: true,
134            cumulative_gas_used: gas_used,
135            logs: vec![],
136        }
137    }
138
139    #[test]
140    fn test_assemble_block() {
141        let chainspec = Arc::new(TempoChainSpec::from_genesis(MODERATO.genesis().clone()));
142        let assembler = TempoBlockAssembler::new(chainspec.clone());
143
144        let block_number = 1u64;
145        let timestamp = 1000u64;
146        let timestamp_millis_part = 500u64;
147        let gas_limit = 30_000_000u64;
148        let general_gas_limit = 10_000_000u64;
149        let shared_gas_limit = 10_000_000u64;
150
151        let evm_env = EvmEnv {
152            block_env: TempoBlockEnv {
153                inner: BlockEnv {
154                    number: U256::from(block_number),
155                    timestamp: U256::from(timestamp),
156                    beneficiary: Address::repeat_byte(0x01),
157                    basefee: 1,
158                    gas_limit,
159                    ..Default::default()
160                },
161                timestamp_millis_part,
162            },
163            ..Default::default()
164        };
165
166        let parent_header = TempoHeader {
167            inner: alloy_consensus::Header {
168                number: 0,
169                timestamp: 0,
170                gas_limit,
171                ..Default::default()
172            },
173            general_gas_limit,
174            timestamp_millis_part: 0,
175            shared_gas_limit,
176            ..Default::default()
177        };
178        let parent = SealedHeader::seal_slow(parent_header);
179
180        let execution_ctx = TempoBlockExecutionCtx {
181            inner: EthBlockExecutionCtx {
182                parent_hash: parent.hash(),
183                parent_beacon_block_root: Some(B256::ZERO),
184                ommers: &[],
185                withdrawals: None,
186                extra_data: Bytes::new(),
187                tx_count_hint: None,
188                slot_number: None,
189            },
190            general_gas_limit,
191            shared_gas_limit,
192            validator_set: None,
193            consensus_context: None,
194            subblock_fee_recipients: HashMap::new(),
195        };
196
197        let tx = create_legacy_tx();
198        let transactions = vec![tx];
199
200        let receipt = create_test_receipt(21000);
201        let output = BlockExecutionResult {
202            receipts: vec![receipt],
203            requests: Default::default(),
204            gas_used: 21000,
205            blob_gas_used: 0,
206        };
207
208        let bundle_state = BundleState::default();
209        let state_provider = NoopProvider::<TempoChainSpec, TempoPrimitives>::new(chainspec);
210        let state_root = B256::ZERO;
211
212        let input = BlockAssemblerInput::<TempoEvmConfig, TempoHeader>::new(
213            evm_env,
214            execution_ctx,
215            &parent,
216            transactions,
217            &output,
218            &bundle_state,
219            &state_provider,
220            state_root,
221            None,
222        );
223
224        let block =
225            BlockAssembler::assemble_block(&assembler, input).expect("should assemble block");
226
227        // Verify block header fields
228        assert_eq!(block.header.inner.number, block_number);
229        assert_eq!(block.header.inner.timestamp, timestamp);
230        assert_eq!(block.header.inner.gas_used, 21000);
231        assert_eq!(block.header.inner.gas_limit, gas_limit);
232        assert_eq!(block.header.inner.parent_hash, parent.hash());
233        assert_eq!(block.header.inner.beneficiary, Address::repeat_byte(0x01));
234        assert_eq!(block.header.inner.state_root, state_root);
235
236        // Verify Tempo-specific header fields
237        assert_eq!(block.header.general_gas_limit, general_gas_limit);
238        assert_eq!(block.header.shared_gas_limit, shared_gas_limit);
239        assert_eq!(block.header.timestamp_millis_part, timestamp_millis_part);
240
241        // Verify body
242        assert_eq!(block.body.transactions.len(), 1);
243
244        // Verify consensus context is None when not provided
245        assert!(block.header.consensus_context.is_none());
246    }
247
248    #[test]
249    fn test_assemble_block_with_consensus_context() {
250        let chainspec = Arc::new(TempoChainSpec::from_genesis(MODERATO.genesis().clone()));
251        let assembler = TempoBlockAssembler::new(chainspec.clone());
252
253        let gas_limit = 30_000_000u64;
254        let general_gas_limit = 10_000_000u64;
255        let shared_gas_limit = 10_000_000u64;
256
257        let ctx = tempo_primitives::TempoConsensusContext {
258            epoch: 1,
259            view: 5,
260            proposer: tempo_primitives::ed25519::PublicKey::from_seed([0xab; 32]),
261            parent_view: 4,
262        };
263
264        let evm_env = EvmEnv {
265            block_env: TempoBlockEnv {
266                inner: BlockEnv {
267                    number: U256::from(1),
268                    timestamp: U256::from(1000),
269                    beneficiary: Address::repeat_byte(0x01),
270                    basefee: 1,
271                    gas_limit,
272                    ..Default::default()
273                },
274                timestamp_millis_part: 0,
275            },
276            ..Default::default()
277        };
278
279        let parent_header = TempoHeader {
280            inner: alloy_consensus::Header {
281                gas_limit,
282                ..Default::default()
283            },
284            general_gas_limit,
285            shared_gas_limit,
286            ..Default::default()
287        };
288        let parent = SealedHeader::seal_slow(parent_header);
289
290        let execution_ctx = TempoBlockExecutionCtx {
291            inner: EthBlockExecutionCtx {
292                parent_hash: parent.hash(),
293                parent_beacon_block_root: Some(B256::ZERO),
294                ommers: &[],
295                withdrawals: None,
296                extra_data: Bytes::new(),
297                tx_count_hint: None,
298                slot_number: None,
299            },
300            general_gas_limit,
301            shared_gas_limit,
302            validator_set: None,
303            consensus_context: Some(ctx),
304            subblock_fee_recipients: HashMap::new(),
305        };
306
307        let transactions = vec![create_legacy_tx()];
308        let output = BlockExecutionResult {
309            receipts: vec![create_test_receipt(21000)],
310            requests: Default::default(),
311            gas_used: 21000,
312            blob_gas_used: 0,
313        };
314
315        let bundle_state = BundleState::default();
316        let state_provider = NoopProvider::<TempoChainSpec, TempoPrimitives>::new(chainspec);
317
318        let input = BlockAssemblerInput::<TempoEvmConfig, TempoHeader>::new(
319            evm_env,
320            execution_ctx,
321            &parent,
322            transactions,
323            &output,
324            &bundle_state,
325            &state_provider,
326            B256::ZERO,
327            None,
328        );
329
330        let block =
331            BlockAssembler::assemble_block(&assembler, input).expect("should assemble block");
332
333        assert_eq!(block.header.consensus_context, Some(ctx));
334    }
335
336    #[test]
337    fn test_assemble_block_preserves_pre_amsterdam_bal_hash() {
338        let chainspec = Arc::new(TempoChainSpec::from_genesis(MODERATO.genesis().clone()));
339        let assembler = TempoBlockAssembler::new(chainspec.clone());
340
341        let gas_limit = 30_000_000u64;
342        let general_gas_limit = 10_000_000u64;
343        let shared_gas_limit = 10_000_000u64;
344
345        let evm_env = EvmEnv {
346            block_env: TempoBlockEnv {
347                inner: BlockEnv {
348                    number: U256::from(1),
349                    timestamp: U256::from(1000),
350                    beneficiary: Address::repeat_byte(0x01),
351                    basefee: 1,
352                    gas_limit,
353                    ..Default::default()
354                },
355                timestamp_millis_part: 0,
356            },
357            ..Default::default()
358        };
359
360        let parent_header = TempoHeader {
361            inner: alloy_consensus::Header {
362                gas_limit,
363                ..Default::default()
364            },
365            general_gas_limit,
366            shared_gas_limit,
367            ..Default::default()
368        };
369        let parent = SealedHeader::seal_slow(parent_header);
370
371        let execution_ctx = TempoBlockExecutionCtx {
372            inner: EthBlockExecutionCtx {
373                parent_hash: parent.hash(),
374                parent_beacon_block_root: Some(B256::ZERO),
375                ommers: &[],
376                withdrawals: None,
377                extra_data: Bytes::new(),
378                tx_count_hint: None,
379                slot_number: None,
380            },
381            general_gas_limit,
382            shared_gas_limit,
383            validator_set: None,
384            consensus_context: None,
385            subblock_fee_recipients: HashMap::new(),
386        };
387
388        let transactions = vec![create_legacy_tx()];
389        let output = BlockExecutionResult {
390            receipts: vec![create_test_receipt(21000)],
391            requests: Default::default(),
392            gas_used: 21000,
393            blob_gas_used: 0,
394        };
395
396        let bundle_state = BundleState::default();
397        let state_provider = NoopProvider::<TempoChainSpec, TempoPrimitives>::new(chainspec);
398        let input = BlockAssemblerInput::<TempoEvmConfig, TempoHeader>::new(
399            evm_env,
400            execution_ctx,
401            &parent,
402            transactions,
403            &output,
404            &bundle_state,
405            &state_provider,
406            B256::ZERO,
407            Some(B256::repeat_byte(0x42)),
408        );
409
410        let block =
411            BlockAssembler::assemble_block(&assembler, input).expect("should assemble block");
412
413        assert_eq!(
414            block.header.inner.block_access_list_hash,
415            Some(B256::repeat_byte(0x42))
416        );
417    }
418}