1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6mod assemble;
7use alloy_consensus::{BlockHeader as _, Transaction};
8use alloy_primitives::Address;
9use alloy_rlp::Decodable;
10pub use assemble::TempoBlockAssembler;
11mod block;
12pub use block::TempoReceiptBuilder;
13mod context;
14pub use context::{TempoBlockExecutionCtx, TempoNextBlockEnvAttributes};
15#[cfg(feature = "engine")]
16mod engine;
17#[cfg(feature = "engine")]
18use rayon as _;
19mod error;
20pub use error::TempoEvmError;
21pub mod evm;
22use std::{borrow::Cow, sync::Arc};
23
24use alloy_evm::{
25 self, EvmEnv,
26 block::{BlockExecutorFactory, BlockExecutorFor},
27 eth::{EthBlockExecutionCtx, NextEvmEnvAttributes},
28 revm::Inspector,
29};
30pub use evm::TempoEvmFactory;
31use reth_chainspec::EthChainSpec;
32use reth_evm::{self, ConfigureEvm, EvmEnvFor, block::StateDB};
33use reth_primitives_traits::{SealedBlock, SealedHeader};
34use tempo_primitives::{
35 Block, SubBlockMetadata, TempoHeader, TempoPrimitives, TempoReceipt, TempoTxEnvelope,
36 subblock::PartialValidatorKey,
37};
38
39use crate::{block::TempoBlockExecutor, evm::TempoEvm};
40use reth_evm_ethereum::EthEvmConfig;
41use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
42use tempo_revm::{evm::TempoContext, gas_params::tempo_gas_params};
43
44pub use tempo_revm::{TempoBlockEnv, TempoHaltReason, TempoStateAccess};
45
46#[cfg(test)]
47mod test_utils;
48
49#[derive(Debug, Clone)]
51pub struct TempoEvmConfig {
52 pub inner: EthEvmConfig<TempoChainSpec, TempoEvmFactory>,
54
55 pub block_assembler: TempoBlockAssembler,
57}
58
59impl TempoEvmConfig {
60 pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
62 let inner =
63 EthEvmConfig::new_with_evm_factory(chain_spec.clone(), TempoEvmFactory::default());
64 Self {
65 inner,
66 block_assembler: TempoBlockAssembler::new(chain_spec),
67 }
68 }
69
70 pub const fn chain_spec(&self) -> &Arc<TempoChainSpec> {
72 self.inner.chain_spec()
73 }
74
75 pub const fn inner(&self) -> &EthEvmConfig<TempoChainSpec, TempoEvmFactory> {
77 &self.inner
78 }
79
80 pub fn mainnet() -> Self {
82 Self::new(Arc::new(TempoChainSpec::mainnet()))
83 }
84}
85
86impl BlockExecutorFactory for TempoEvmConfig {
87 type EvmFactory = TempoEvmFactory;
88 type ExecutionCtx<'a> = TempoBlockExecutionCtx<'a>;
89 type Transaction = TempoTxEnvelope;
90 type Receipt = TempoReceipt;
91
92 fn evm_factory(&self) -> &Self::EvmFactory {
93 self.inner.executor_factory.evm_factory()
94 }
95
96 fn create_executor<'a, DB, I>(
97 &'a self,
98 evm: TempoEvm<DB, I>,
99 ctx: Self::ExecutionCtx<'a>,
100 ) -> impl BlockExecutorFor<'a, Self, DB, I>
101 where
102 DB: StateDB + 'a,
103 I: Inspector<TempoContext<DB>> + 'a,
104 {
105 TempoBlockExecutor::new(evm, ctx, self.chain_spec())
106 }
107}
108
109impl ConfigureEvm for TempoEvmConfig {
110 type Primitives = TempoPrimitives;
111 type Error = TempoEvmError;
112 type NextBlockEnvCtx = TempoNextBlockEnvAttributes;
113 type BlockExecutorFactory = Self;
114 type BlockAssembler = TempoBlockAssembler;
115
116 fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
117 self
118 }
119
120 fn block_assembler(&self) -> &Self::BlockAssembler {
121 &self.block_assembler
122 }
123
124 fn evm_env(&self, header: &TempoHeader) -> Result<EvmEnvFor<Self>, Self::Error> {
125 let EvmEnv { cfg_env, block_env } = EvmEnv::for_eth_block(
126 header,
127 self.chain_spec(),
128 self.chain_spec().chain().id(),
129 self.chain_spec()
130 .blob_params_at_timestamp(header.timestamp()),
131 );
132
133 let spec = self.chain_spec().tempo_hardfork_at(header.timestamp());
134
135 let mut cfg_env = cfg_env.with_spec_and_gas_params(spec, tempo_gas_params(spec));
137 cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
138
139 Ok(EvmEnv {
140 cfg_env,
141 block_env: TempoBlockEnv {
142 inner: block_env,
143 timestamp_millis_part: header.timestamp_millis_part,
144 },
145 })
146 }
147
148 fn next_evm_env(
149 &self,
150 parent: &TempoHeader,
151 attributes: &Self::NextBlockEnvCtx,
152 ) -> Result<EvmEnvFor<Self>, Self::Error> {
153 let EvmEnv { cfg_env, block_env } = EvmEnv::for_eth_next_block(
154 parent,
155 NextEvmEnvAttributes {
156 timestamp: attributes.timestamp,
157 suggested_fee_recipient: attributes.suggested_fee_recipient,
158 prev_randao: attributes.prev_randao,
159 gas_limit: attributes.gas_limit,
160 },
161 self.chain_spec()
162 .next_block_base_fee(parent, attributes.timestamp)
163 .unwrap_or_default(),
164 self.chain_spec(),
165 self.chain_spec().chain().id(),
166 self.chain_spec()
167 .blob_params_at_timestamp(attributes.timestamp),
168 );
169
170 let spec = self.chain_spec().tempo_hardfork_at(attributes.timestamp);
171
172 let mut cfg_env = cfg_env.with_spec_and_gas_params(spec, tempo_gas_params(spec));
174 cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
175
176 Ok(EvmEnv {
177 cfg_env,
178 block_env: TempoBlockEnv {
179 inner: block_env,
180 timestamp_millis_part: attributes.timestamp_millis_part,
181 },
182 })
183 }
184
185 fn context_for_block<'a>(
186 &self,
187 block: &'a SealedBlock<Block>,
188 ) -> Result<TempoBlockExecutionCtx<'a>, Self::Error> {
189 let subblock_fee_recipients = block
191 .body()
192 .transactions
193 .iter()
194 .rev()
195 .filter(|tx| (*tx).to() == Some(Address::ZERO))
196 .find_map(|tx| Vec::<SubBlockMetadata>::decode(&mut tx.input().as_ref()).ok())
197 .ok_or(TempoEvmError::NoSubblockMetadataFound)?
198 .into_iter()
199 .map(|metadata| {
200 (
201 PartialValidatorKey::from_slice(&metadata.validator[..15]),
202 metadata.fee_recipient,
203 )
204 })
205 .collect();
206
207 Ok(TempoBlockExecutionCtx {
208 inner: EthBlockExecutionCtx {
209 parent_hash: block.header().parent_hash(),
210 parent_beacon_block_root: block.header().parent_beacon_block_root(),
211 ommers: &[],
213 withdrawals: block
214 .body()
215 .withdrawals
216 .as_ref()
217 .map(|w| Cow::Borrowed(w.as_slice())),
218 extra_data: block.extra_data().clone(),
219 tx_count_hint: Some(block.body().transactions.len()),
220 },
221 general_gas_limit: block.header().general_gas_limit,
222 shared_gas_limit: block.header().gas_limit()
223 / tempo_consensus::TEMPO_SHARED_GAS_DIVISOR,
224 validator_set: None,
226 subblock_fee_recipients,
227 })
228 }
229
230 fn context_for_next_block(
231 &self,
232 parent: &SealedHeader<TempoHeader>,
233 attributes: Self::NextBlockEnvCtx,
234 ) -> Result<TempoBlockExecutionCtx<'_>, Self::Error> {
235 Ok(TempoBlockExecutionCtx {
236 inner: EthBlockExecutionCtx {
237 parent_hash: parent.hash(),
238 parent_beacon_block_root: attributes.parent_beacon_block_root,
239 ommers: &[],
240 withdrawals: attributes
241 .inner
242 .withdrawals
243 .map(|w| Cow::Owned(w.into_inner())),
244 extra_data: attributes.inner.extra_data,
245 tx_count_hint: None,
246 },
247 general_gas_limit: attributes.general_gas_limit,
248 shared_gas_limit: attributes.inner.gas_limit
249 / tempo_consensus::TEMPO_SHARED_GAS_DIVISOR,
250 validator_set: None,
252 subblock_fee_recipients: attributes.subblock_fee_recipients,
253 })
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::test_utils::test_chainspec;
261 use alloy_consensus::{BlockHeader, Signed, TxLegacy};
262 use alloy_primitives::{B256, Bytes, Signature, TxKind, U256};
263 use alloy_rlp::{Encodable, bytes::BytesMut};
264 use reth_evm::{ConfigureEvm, NextBlockEnvAttributes};
265 use std::collections::HashMap;
266 use tempo_chainspec::hardfork::TempoHardfork;
267 use tempo_primitives::{
268 BlockBody, SubBlockMetadata, subblock::SubBlockVersion,
269 transaction::envelope::TEMPO_SYSTEM_TX_SIGNATURE,
270 };
271
272 #[test]
273 fn test_evm_config_can_query_tempo_hardforks() {
274 let evm_config = TempoEvmConfig::new(test_chainspec());
275 let activation = evm_config
276 .chain_spec()
277 .tempo_fork_activation(TempoHardfork::Genesis);
278 assert_eq!(activation, reth_chainspec::ForkCondition::Timestamp(0));
279 }
280
281 #[test]
282 fn test_evm_env() {
283 let evm_config = TempoEvmConfig::new(test_chainspec());
284
285 let header = TempoHeader {
286 inner: alloy_consensus::Header {
287 number: 100,
288 timestamp: 1000,
289 gas_limit: 30_000_000,
290 base_fee_per_gas: Some(1000),
291 beneficiary: alloy_primitives::Address::repeat_byte(0x01),
292 ..Default::default()
293 },
294 general_gas_limit: 10_000_000,
295 timestamp_millis_part: 500,
296 shared_gas_limit: 3_000_000,
297 };
298
299 let result = evm_config.evm_env(&header);
300 assert!(result.is_ok());
301
302 let evm_env = result.unwrap();
303
304 assert_eq!(evm_env.block_env.inner.number, U256::from(header.number()));
306 assert_eq!(
307 evm_env.block_env.inner.timestamp,
308 U256::from(header.timestamp())
309 );
310 assert_eq!(evm_env.block_env.inner.gas_limit, header.gas_limit());
311 assert_eq!(evm_env.block_env.inner.beneficiary, header.beneficiary());
312
313 assert_eq!(evm_env.block_env.timestamp_millis_part, 500);
315 }
316
317 #[test]
321 fn test_evm_env_t1_gas_cap() {
322 use tempo_chainspec::spec::DEV;
323
324 let chainspec = DEV.clone();
326 let evm_config = TempoEvmConfig::new(chainspec.clone());
327
328 let header = TempoHeader {
329 inner: alloy_consensus::Header {
330 number: 100,
331 timestamp: 1000, gas_limit: 30_000_000,
333 base_fee_per_gas: Some(1000),
334 ..Default::default()
335 },
336 general_gas_limit: 10_000_000,
337 timestamp_millis_part: 0,
338 shared_gas_limit: 3_000_000,
339 };
340
341 assert!(chainspec.tempo_hardfork_at(header.timestamp()).is_t1());
343
344 let evm_env = evm_config.evm_env(&header).unwrap();
345
346 assert_eq!(
348 evm_env.cfg_env.tx_gas_limit_cap,
349 Some(tempo_chainspec::spec::TEMPO_T1_TX_GAS_LIMIT_CAP),
350 "TIP-1000 requires 30M gas limit cap for T1 hardfork"
351 );
352 }
353
354 #[test]
355 fn test_next_evm_env() {
356 let evm_config = TempoEvmConfig::new(test_chainspec());
357
358 let parent = TempoHeader {
359 inner: alloy_consensus::Header {
360 number: 99,
361 timestamp: 900,
362 gas_limit: 30_000_000,
363 base_fee_per_gas: Some(1000),
364 ..Default::default()
365 },
366 general_gas_limit: 10_000_000,
367 timestamp_millis_part: 0,
368 shared_gas_limit: 3_000_000,
369 };
370
371 let attributes = TempoNextBlockEnvAttributes {
372 inner: NextBlockEnvAttributes {
373 timestamp: 1000,
374 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x02),
375 prev_randao: B256::repeat_byte(0x03),
376 gas_limit: 30_000_000,
377 parent_beacon_block_root: Some(B256::ZERO),
378 withdrawals: None,
379 extra_data: Default::default(),
380 },
381 general_gas_limit: 10_000_000,
382 shared_gas_limit: 3_000_000,
383 timestamp_millis_part: 750,
384 subblock_fee_recipients: HashMap::new(),
385 };
386
387 let result = evm_config.next_evm_env(&parent, &attributes);
388 assert!(result.is_ok());
389
390 let evm_env = result.unwrap();
391
392 assert_eq!(evm_env.block_env.inner.number, U256::from(100));
395 assert_eq!(evm_env.block_env.inner.timestamp, U256::from(1000));
396 assert_eq!(
397 evm_env.block_env.inner.beneficiary,
398 Address::repeat_byte(0x02)
399 );
400 assert_eq!(evm_env.block_env.inner.gas_limit, 30_000_000);
401
402 assert_eq!(evm_env.block_env.timestamp_millis_part, 750);
404 }
405
406 #[test]
407 fn test_context_for_block() {
408 let chainspec = test_chainspec();
409 let evm_config = TempoEvmConfig::new(chainspec.clone());
410
411 let validator_key = B256::repeat_byte(0x01);
413 let fee_recipient = alloy_primitives::Address::repeat_byte(0x02);
414 let metadata = vec![SubBlockMetadata {
415 version: SubBlockVersion::V1,
416 validator: validator_key,
417 fee_recipient,
418 signature: Bytes::from_static(&[0; 64]),
419 }];
420
421 let block_number = 1u64;
423 let mut input = BytesMut::new();
424 metadata.encode(&mut input);
425 input.extend_from_slice(&U256::from(block_number).to_be_bytes::<32>());
426
427 let system_tx = TempoTxEnvelope::Legacy(Signed::new_unhashed(
428 TxLegacy {
429 chain_id: Some(reth_chainspec::EthChainSpec::chain(&*chainspec).id()),
430 nonce: 0,
431 gas_price: 0,
432 gas_limit: 0,
433 to: TxKind::Call(alloy_primitives::Address::ZERO),
434 value: U256::ZERO,
435 input: input.freeze().into(),
436 },
437 TEMPO_SYSTEM_TX_SIGNATURE,
438 ));
439
440 let header = TempoHeader {
441 inner: alloy_consensus::Header {
442 number: block_number,
443 timestamp: 1000,
444 gas_limit: 30_000_000,
445 parent_beacon_block_root: Some(B256::ZERO),
446 ..Default::default()
447 },
448 general_gas_limit: 10_000_000,
449 timestamp_millis_part: 500,
450 shared_gas_limit: 3_000_000,
451 };
452
453 let body = BlockBody {
454 transactions: vec![system_tx],
455 ommers: vec![],
456 withdrawals: None,
457 };
458
459 let block = Block { header, body };
460 let sealed_block = SealedBlock::seal_slow(block);
461
462 let result = evm_config.context_for_block(&sealed_block);
463 assert!(result.is_ok());
464
465 let context = result.unwrap();
466
467 assert_eq!(context.general_gas_limit, 10_000_000);
469 assert_eq!(context.shared_gas_limit, 3_000_000);
470 assert!(context.validator_set.is_none());
471
472 let partial_key = PartialValidatorKey::from_slice(&validator_key[..15]);
474 assert_eq!(
475 context.subblock_fee_recipients.get(&partial_key),
476 Some(&fee_recipient)
477 );
478 }
479
480 #[test]
481 fn test_context_for_block_no_subblock_metadata() {
482 let evm_config = TempoEvmConfig::new(test_chainspec());
483
484 let regular_tx = TempoTxEnvelope::Legacy(Signed::new_unhashed(
486 TxLegacy {
487 chain_id: Some(1),
488 nonce: 0,
489 gas_price: 1,
490 gas_limit: 21000,
491 to: TxKind::Call(alloy_primitives::Address::repeat_byte(0x01)),
492 value: U256::ZERO,
493 input: Bytes::new(),
494 },
495 Signature::test_signature(),
496 ));
497
498 let header = TempoHeader {
499 inner: alloy_consensus::Header {
500 number: 1,
501 timestamp: 1000,
502 gas_limit: 30_000_000,
503 ..Default::default()
504 },
505 general_gas_limit: 10_000_000,
506 timestamp_millis_part: 500,
507 shared_gas_limit: 3_000_000,
508 };
509
510 let body = BlockBody {
511 transactions: vec![regular_tx],
512 ommers: vec![],
513 withdrawals: None,
514 };
515
516 let block = Block { header, body };
517 let sealed_block = SealedBlock::seal_slow(block);
518
519 let result = evm_config.context_for_block(&sealed_block);
520
521 assert!(result.is_err());
523 assert!(matches!(
524 result.unwrap_err(),
525 TempoEvmError::NoSubblockMetadataFound
526 ));
527 }
528
529 #[test]
530 fn test_context_for_next_block() {
531 let evm_config = TempoEvmConfig::new(test_chainspec());
532
533 let parent_header = TempoHeader {
534 inner: alloy_consensus::Header {
535 number: 99,
536 timestamp: 900,
537 gas_limit: 30_000_000,
538 ..Default::default()
539 },
540 general_gas_limit: 10_000_000,
541 timestamp_millis_part: 0,
542 shared_gas_limit: 3_000_000,
543 };
544 let parent = SealedHeader::seal_slow(parent_header);
545
546 let fee_recipient = Address::repeat_byte(0x02);
547 let mut subblock_fee_recipients = HashMap::new();
548 let partial_key = PartialValidatorKey::from_slice(&[0x01; 15]);
549 subblock_fee_recipients.insert(partial_key, fee_recipient);
550
551 let attributes = TempoNextBlockEnvAttributes {
552 inner: NextBlockEnvAttributes {
553 timestamp: 1000,
554 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x03),
555 prev_randao: B256::repeat_byte(0x04),
556 gas_limit: 30_000_000,
557 parent_beacon_block_root: Some(B256::repeat_byte(0x05)),
558 withdrawals: None,
559 extra_data: Default::default(),
560 },
561 general_gas_limit: 12_000_000,
562 shared_gas_limit: 4_000_000,
563 timestamp_millis_part: 999,
564 subblock_fee_recipients: subblock_fee_recipients.clone(),
565 };
566
567 let result = evm_config.context_for_next_block(&parent, attributes);
568 assert!(result.is_ok());
569
570 let context = result.unwrap();
571
572 assert_eq!(context.general_gas_limit, 12_000_000);
574 assert_eq!(context.shared_gas_limit, 3_000_000);
575 assert!(context.validator_set.is_none());
576 assert_eq!(context.inner.parent_hash, parent.hash());
577 assert_eq!(
578 context.inner.parent_beacon_block_root,
579 Some(B256::repeat_byte(0x05))
580 );
581
582 assert_eq!(
584 context.subblock_fee_recipients.get(&partial_key),
585 Some(&fee_recipient)
586 );
587 }
588}