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 reth_evm::execute::{BlockAssembler, BlockAssemblerInput};
6use reth_evm_ethereum::EthBlockAssembler;
7use reth_primitives_traits::SealedHeader;
8use std::sync::Arc;
9use tempo_chainspec::TempoChainSpec;
10use tempo_primitives::TempoHeader;
11
12/// Assembler for Tempo blocks.
13#[derive(Debug, Clone)]
14pub struct TempoBlockAssembler {
15    pub(crate) inner: EthBlockAssembler<TempoChainSpec>,
16}
17
18impl TempoBlockAssembler {
19    pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
20        Self {
21            inner: EthBlockAssembler::new(chain_spec),
22        }
23    }
24}
25
26impl BlockAssembler<TempoEvmConfig> for TempoBlockAssembler {
27    type Block = tempo_primitives::Block;
28
29    fn assemble_block(
30        &self,
31        input: BlockAssemblerInput<'_, '_, TempoEvmConfig, TempoHeader>,
32    ) -> Result<Self::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            ..
51        } = input;
52
53        let parent = SealedHeader::new_unhashed(parent.clone().into_header().inner);
54
55        let timestamp_millis_part = evm_env.block_env.timestamp_millis_part;
56
57        // Delegate block building to the inner assembler
58        let block = self.inner.assemble_block(BlockAssemblerInput::<
59            EthBlockExecutorFactory<TempoReceiptBuilder, TempoChainSpec, TempoEvmFactory>,
60        >::new(
61            evm_env,
62            inner,
63            &parent,
64            transactions,
65            output,
66            bundle_state,
67            state_provider,
68            state_root,
69        ))?;
70
71        Ok(block.map_header(|inner| TempoHeader {
72            inner,
73            general_gas_limit,
74            timestamp_millis_part,
75            shared_gas_limit,
76            consensus_context,
77        }))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use alloy_consensus::{Signed, TxLegacy};
85    use alloy_evm::{EvmEnv, block::BlockExecutionResult, eth::EthBlockExecutionCtx};
86    use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256};
87    use reth_chainspec::EthChainSpec;
88    use reth_evm::execute::BlockAssembler;
89    use reth_primitives_traits::SealedHeader;
90    use reth_storage_api::noop::NoopProvider;
91    use revm::{context::BlockEnv, database::BundleState};
92    use std::collections::HashMap;
93    use tempo_chainspec::spec::MODERATO;
94    use tempo_primitives::{
95        TempoHeader, TempoPrimitives, TempoReceipt, TempoTxEnvelope, TempoTxType,
96    };
97    use tempo_revm::TempoBlockEnv;
98
99    fn create_legacy_tx() -> TempoTxEnvelope {
100        let tx = TxLegacy {
101            chain_id: Some(1),
102            nonce: 0,
103            gas_price: 1,
104            gas_limit: 21000,
105            to: TxKind::Call(Address::ZERO),
106            value: U256::ZERO,
107            input: Bytes::new(),
108        };
109        TempoTxEnvelope::Legacy(Signed::new_unhashed(tx, Signature::test_signature()))
110    }
111
112    fn create_test_receipt(gas_used: u64) -> TempoReceipt {
113        TempoReceipt {
114            tx_type: TempoTxType::Legacy,
115            success: true,
116            cumulative_gas_used: gas_used,
117            logs: vec![],
118        }
119    }
120
121    #[test]
122    fn test_assemble_block() {
123        let chainspec = Arc::new(TempoChainSpec::from_genesis(MODERATO.genesis().clone()));
124        let assembler = TempoBlockAssembler::new(chainspec.clone());
125
126        let block_number = 1u64;
127        let timestamp = 1000u64;
128        let timestamp_millis_part = 500u64;
129        let gas_limit = 30_000_000u64;
130        let general_gas_limit = 10_000_000u64;
131        let shared_gas_limit = 10_000_000u64;
132
133        let evm_env = EvmEnv {
134            block_env: TempoBlockEnv {
135                inner: BlockEnv {
136                    number: U256::from(block_number),
137                    timestamp: U256::from(timestamp),
138                    beneficiary: Address::repeat_byte(0x01),
139                    basefee: 1,
140                    gas_limit,
141                    ..Default::default()
142                },
143                timestamp_millis_part,
144            },
145            ..Default::default()
146        };
147
148        let parent_header = TempoHeader {
149            inner: alloy_consensus::Header {
150                number: 0,
151                timestamp: 0,
152                gas_limit,
153                ..Default::default()
154            },
155            general_gas_limit,
156            timestamp_millis_part: 0,
157            shared_gas_limit,
158            ..Default::default()
159        };
160        let parent = SealedHeader::seal_slow(parent_header);
161
162        let execution_ctx = TempoBlockExecutionCtx {
163            inner: EthBlockExecutionCtx {
164                parent_hash: parent.hash(),
165                parent_beacon_block_root: Some(B256::ZERO),
166                ommers: &[],
167                withdrawals: None,
168                extra_data: Bytes::new(),
169                tx_count_hint: None,
170                slot_number: None,
171            },
172            general_gas_limit,
173            shared_gas_limit,
174            validator_set: None,
175            consensus_context: None,
176            subblock_fee_recipients: HashMap::new(),
177        };
178
179        let tx = create_legacy_tx();
180        let transactions = vec![tx];
181
182        let receipt = create_test_receipt(21000);
183        let output = BlockExecutionResult {
184            receipts: vec![receipt],
185            requests: Default::default(),
186            gas_used: 21000,
187            blob_gas_used: 0,
188        };
189
190        let bundle_state = BundleState::default();
191        let state_provider = NoopProvider::<TempoChainSpec, TempoPrimitives>::new(chainspec);
192        let state_root = B256::ZERO;
193
194        let input = BlockAssemblerInput::<TempoEvmConfig, TempoHeader>::new(
195            evm_env,
196            execution_ctx,
197            &parent,
198            transactions,
199            &output,
200            &bundle_state,
201            &state_provider,
202            state_root,
203        );
204
205        let block = assembler
206            .assemble_block(input)
207            .expect("should assemble block");
208
209        // Verify block header fields
210        assert_eq!(block.header.inner.number, block_number);
211        assert_eq!(block.header.inner.timestamp, timestamp);
212        assert_eq!(block.header.inner.gas_used, 21000);
213        assert_eq!(block.header.inner.gas_limit, gas_limit);
214        assert_eq!(block.header.inner.parent_hash, parent.hash());
215        assert_eq!(block.header.inner.beneficiary, Address::repeat_byte(0x01));
216        assert_eq!(block.header.inner.state_root, state_root);
217
218        // Verify Tempo-specific header fields
219        assert_eq!(block.header.general_gas_limit, general_gas_limit);
220        assert_eq!(block.header.shared_gas_limit, shared_gas_limit);
221        assert_eq!(block.header.timestamp_millis_part, timestamp_millis_part);
222
223        // Verify body
224        assert_eq!(block.body.transactions.len(), 1);
225
226        // Verify consensus context is None when not provided
227        assert!(block.header.consensus_context.is_none());
228    }
229
230    #[test]
231    fn test_assemble_block_with_consensus_context() {
232        let chainspec = Arc::new(TempoChainSpec::from_genesis(MODERATO.genesis().clone()));
233        let assembler = TempoBlockAssembler::new(chainspec.clone());
234
235        let gas_limit = 30_000_000u64;
236        let general_gas_limit = 10_000_000u64;
237        let shared_gas_limit = 10_000_000u64;
238
239        let ctx = tempo_primitives::TempoConsensusContext {
240            epoch: 1,
241            view: 5,
242            proposer: tempo_primitives::ed25519::PublicKey::from_seed([0xab; 32]),
243            parent_view: 4,
244        };
245
246        let evm_env = EvmEnv {
247            block_env: TempoBlockEnv {
248                inner: BlockEnv {
249                    number: U256::from(1),
250                    timestamp: U256::from(1000),
251                    beneficiary: Address::repeat_byte(0x01),
252                    basefee: 1,
253                    gas_limit,
254                    ..Default::default()
255                },
256                timestamp_millis_part: 0,
257            },
258            ..Default::default()
259        };
260
261        let parent_header = TempoHeader {
262            inner: alloy_consensus::Header {
263                gas_limit,
264                ..Default::default()
265            },
266            general_gas_limit,
267            shared_gas_limit,
268            ..Default::default()
269        };
270        let parent = SealedHeader::seal_slow(parent_header);
271
272        let execution_ctx = TempoBlockExecutionCtx {
273            inner: EthBlockExecutionCtx {
274                parent_hash: parent.hash(),
275                parent_beacon_block_root: Some(B256::ZERO),
276                ommers: &[],
277                withdrawals: None,
278                extra_data: Bytes::new(),
279                tx_count_hint: None,
280                slot_number: None,
281            },
282            general_gas_limit,
283            shared_gas_limit,
284            validator_set: None,
285            consensus_context: Some(ctx),
286            subblock_fee_recipients: HashMap::new(),
287        };
288
289        let transactions = vec![create_legacy_tx()];
290        let output = BlockExecutionResult {
291            receipts: vec![create_test_receipt(21000)],
292            requests: Default::default(),
293            gas_used: 21000,
294            blob_gas_used: 0,
295        };
296
297        let bundle_state = BundleState::default();
298        let state_provider = NoopProvider::<TempoChainSpec, TempoPrimitives>::new(chainspec);
299
300        let input = BlockAssemblerInput::<TempoEvmConfig, TempoHeader>::new(
301            evm_env,
302            execution_ctx,
303            &parent,
304            transactions,
305            &output,
306            &bundle_state,
307            &state_provider,
308            B256::ZERO,
309        );
310
311        let block = assembler
312            .assemble_block(input)
313            .expect("should assemble block");
314
315        assert_eq!(block.header.consensus_context, Some(ctx));
316    }
317}