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#[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 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 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 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 assert_eq!(block.body.transactions.len(), 1);
243
244 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}