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_rlp::Decodable;
9pub use assemble::TempoBlockAssembler;
10mod block;
11pub use block::{TempoBlockExecutor, TempoReceiptBuilder, TempoTxResult};
12mod context;
13pub use context::{TempoBlockExecutionCtx, TempoNextBlockEnvAttributes};
14pub mod consensus;
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,
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::evm::TempoEvm;
40use reth_evm_ethereum::EthEvmConfig;
41use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
42use tempo_revm::{evm::TempoContext, gas_params::tempo_gas_params_with_amsterdam};
43
44pub use tempo_revm::{TempoBlockEnv, TempoHaltReason, TempoInvalidTransaction, 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 moderato() -> Self {
82 Self::new(Arc::new(TempoChainSpec::moderato()))
83 }
84
85 pub fn mainnet() -> Self {
87 Self::new(Arc::new(TempoChainSpec::mainnet()))
88 }
89}
90
91impl BlockExecutorFactory for TempoEvmConfig {
92 type EvmFactory = TempoEvmFactory;
93 type ExecutionCtx<'a> = TempoBlockExecutionCtx<'a>;
94 type Transaction = TempoTxEnvelope;
95 type Receipt = TempoReceipt;
96 type TxExecutionResult = TempoTxResult;
97 type Executor<'a, DB: StateDB, I: Inspector<TempoContext<DB>>> = TempoBlockExecutor<'a, DB, I>;
98
99 fn evm_factory(&self) -> &Self::EvmFactory {
100 self.inner.executor_factory.evm_factory()
101 }
102
103 fn create_executor<'a, DB, I>(
104 &'a self,
105 evm: TempoEvm<DB, I>,
106 ctx: Self::ExecutionCtx<'a>,
107 ) -> Self::Executor<'a, DB, I>
108 where
109 DB: StateDB,
110 I: Inspector<TempoContext<DB>>,
111 {
112 TempoBlockExecutor::new(evm, ctx, self.chain_spec())
113 }
114}
115
116impl ConfigureEvm for TempoEvmConfig {
117 type Primitives = TempoPrimitives;
118 type Error = TempoEvmError;
119 type NextBlockEnvCtx = TempoNextBlockEnvAttributes;
120 type BlockExecutorFactory = Self;
121 type BlockAssembler = TempoBlockAssembler;
122
123 fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
124 self
125 }
126
127 fn block_assembler(&self) -> &Self::BlockAssembler {
128 &self.block_assembler
129 }
130
131 fn evm_env(&self, header: &TempoHeader) -> Result<EvmEnvFor<Self>, Self::Error> {
132 let EvmEnv { cfg_env, block_env } = EvmEnv::for_eth_block(
133 header,
134 self.chain_spec(),
135 self.chain_spec().chain().id(),
136 self.chain_spec()
137 .blob_params_at_timestamp(header.timestamp()),
138 );
139
140 let spec = self.chain_spec().tempo_hardfork_at(header.timestamp());
141
142 let amsterdam_eip8037_enabled = cfg_env.enable_amsterdam_eip8037;
154 let mut cfg_env = cfg_env.with_spec_and_gas_params(
155 spec,
156 tempo_gas_params_with_amsterdam(spec, amsterdam_eip8037_enabled),
157 );
158 cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
159
160 Ok(EvmEnv {
161 cfg_env,
162 block_env: TempoBlockEnv {
163 inner: block_env,
164 timestamp_millis_part: header.timestamp_millis_part,
165 },
166 })
167 }
168
169 fn next_evm_env(
170 &self,
171 parent: &TempoHeader,
172 attributes: &Self::NextBlockEnvCtx,
173 ) -> Result<EvmEnvFor<Self>, Self::Error> {
174 let EvmEnv { cfg_env, block_env } = EvmEnv::for_eth_next_block(
175 parent,
176 NextEvmEnvAttributes {
177 timestamp: attributes.timestamp,
178 suggested_fee_recipient: attributes.suggested_fee_recipient,
179 prev_randao: attributes.prev_randao,
180 gas_limit: attributes.gas_limit,
181 slot_number: attributes.slot_number,
182 },
183 self.chain_spec()
184 .next_block_base_fee(parent, attributes.timestamp)
185 .unwrap_or_default(),
186 self.chain_spec(),
187 self.chain_spec().chain().id(),
188 self.chain_spec()
189 .blob_params_at_timestamp(attributes.timestamp),
190 );
191
192 let spec = self.chain_spec().tempo_hardfork_at(attributes.timestamp);
193
194 let amsterdam_eip8037_enabled = cfg_env.enable_amsterdam_eip8037;
203 let mut cfg_env = cfg_env.with_spec_and_gas_params(
204 spec,
205 tempo_gas_params_with_amsterdam(spec, amsterdam_eip8037_enabled),
206 );
207 cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
208
209 Ok(EvmEnv {
210 cfg_env,
211 block_env: TempoBlockEnv {
212 inner: block_env,
213 timestamp_millis_part: attributes.timestamp_millis_part,
214 },
215 })
216 }
217
218 fn context_for_block<'a>(
219 &self,
220 block: &'a SealedBlock<Block>,
221 ) -> Result<TempoBlockExecutionCtx<'a>, Self::Error> {
222 let subblock_fee_recipients = block
224 .body()
225 .transactions
226 .iter()
227 .rev()
228 .filter(|tx| tx.is_system_tx())
229 .find_map(|tx| Vec::<SubBlockMetadata>::decode(&mut tx.input().as_ref()).ok())
230 .unwrap_or_default()
231 .into_iter()
232 .map(|metadata| {
233 (
234 PartialValidatorKey::from_slice(&metadata.validator[..15]),
235 metadata.fee_recipient,
236 )
237 })
238 .collect();
239
240 Ok(TempoBlockExecutionCtx {
241 inner: EthBlockExecutionCtx {
242 parent_hash: block.header().parent_hash(),
243 parent_beacon_block_root: block.header().parent_beacon_block_root(),
244 ommers: &[],
246 withdrawals: block
247 .body()
248 .withdrawals
249 .as_ref()
250 .map(|w| Cow::Borrowed(w.as_slice())),
251 extra_data: block.extra_data().clone(),
252 tx_count_hint: Some(block.body().transactions.len()),
253 slot_number: block.slot_number(),
254 },
255 general_gas_limit: block.header().general_gas_limit,
256 shared_gas_limit: block.header().shared_gas_limit,
257 validator_set: None,
259 consensus_context: block.header().consensus_context,
260 subblock_fee_recipients,
261 })
262 }
263
264 fn context_for_next_block(
265 &self,
266 parent: &SealedHeader<TempoHeader>,
267 attributes: Self::NextBlockEnvCtx,
268 ) -> Result<TempoBlockExecutionCtx<'_>, Self::Error> {
269 Ok(TempoBlockExecutionCtx {
270 inner: EthBlockExecutionCtx {
271 parent_hash: parent.hash(),
272 parent_beacon_block_root: attributes.parent_beacon_block_root,
273 slot_number: attributes.slot_number,
274 ommers: &[],
275 withdrawals: attributes
276 .inner
277 .withdrawals
278 .map(|w| Cow::Owned(w.into_inner())),
279 extra_data: attributes.inner.extra_data,
280 tx_count_hint: None,
281 },
282 general_gas_limit: attributes.general_gas_limit,
283 shared_gas_limit: attributes.shared_gas_limit,
284 validator_set: None,
286 consensus_context: attributes.consensus_context,
287 subblock_fee_recipients: attributes.subblock_fee_recipients,
288 })
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::test_utils::test_chainspec;
296 use alloy_consensus::{BlockHeader, Signed, TxLegacy};
297 use alloy_primitives::{Address, B256, Bytes, TxKind, U256};
298 use alloy_rlp::{Encodable, bytes::BytesMut};
299 use reth_evm::{ConfigureEvm, NextBlockEnvAttributes};
300 use std::collections::HashMap;
301 use tempo_chainspec::hardfork::TempoHardfork;
302 use tempo_primitives::{
303 BlockBody, SubBlockMetadata, subblock::SubBlockVersion,
304 transaction::envelope::TEMPO_SYSTEM_TX_SIGNATURE,
305 };
306
307 #[test]
308 fn test_evm_config_can_query_tempo_hardforks() {
309 let evm_config = TempoEvmConfig::new(test_chainspec());
310 let activation = evm_config
311 .chain_spec()
312 .tempo_fork_activation(TempoHardfork::Genesis);
313 assert_eq!(activation, reth_chainspec::ForkCondition::Timestamp(0));
314 }
315
316 #[test]
317 fn test_evm_env() {
318 let evm_config = TempoEvmConfig::new(test_chainspec());
319
320 let header = TempoHeader {
321 inner: alloy_consensus::Header {
322 number: 100,
323 timestamp: 1000,
324 gas_limit: 30_000_000,
325 base_fee_per_gas: Some(1000),
326 beneficiary: alloy_primitives::Address::repeat_byte(0x01),
327 ..Default::default()
328 },
329 general_gas_limit: 10_000_000,
330 timestamp_millis_part: 500,
331 shared_gas_limit: 3_000_000,
332 ..Default::default()
333 };
334
335 let result = evm_config.evm_env(&header);
336 assert!(result.is_ok());
337
338 let evm_env = result.unwrap();
339
340 assert_eq!(evm_env.block_env.inner.number, U256::from(header.number()));
342 assert_eq!(
343 evm_env.block_env.inner.timestamp,
344 U256::from(header.timestamp())
345 );
346 assert_eq!(evm_env.block_env.inner.gas_limit, header.gas_limit());
347 assert_eq!(evm_env.block_env.inner.beneficiary, header.beneficiary());
348
349 assert_eq!(evm_env.block_env.timestamp_millis_part, 500);
351 }
352
353 #[test]
357 fn test_evm_env_t1_gas_cap() {
358 use tempo_chainspec::spec::DEV;
359
360 let chainspec = DEV.clone();
362 let evm_config = TempoEvmConfig::new(chainspec.clone());
363
364 let header = TempoHeader {
365 inner: alloy_consensus::Header {
366 number: 100,
367 timestamp: 1000, gas_limit: 30_000_000,
369 base_fee_per_gas: Some(1000),
370 ..Default::default()
371 },
372 general_gas_limit: 10_000_000,
373 timestamp_millis_part: 0,
374 shared_gas_limit: 3_000_000,
375 ..Default::default()
376 };
377
378 assert!(chainspec.tempo_hardfork_at(header.timestamp()).is_t1());
380
381 let evm_env = evm_config.evm_env(&header).unwrap();
382
383 assert_eq!(
385 evm_env.cfg_env.tx_gas_limit_cap,
386 Some(tempo_chainspec::spec::TEMPO_T1_TX_GAS_LIMIT_CAP),
387 "TIP-1000 requires 30M gas limit cap for T1 hardfork"
388 );
389 }
390
391 #[test]
392 fn test_next_evm_env() {
393 let evm_config = TempoEvmConfig::new(test_chainspec());
394
395 let parent = TempoHeader {
396 inner: alloy_consensus::Header {
397 number: 99,
398 timestamp: 900,
399 gas_limit: 30_000_000,
400 base_fee_per_gas: Some(1000),
401 ..Default::default()
402 },
403 general_gas_limit: 10_000_000,
404 timestamp_millis_part: 0,
405 shared_gas_limit: 3_000_000,
406 ..Default::default()
407 };
408
409 let attributes = TempoNextBlockEnvAttributes {
410 inner: NextBlockEnvAttributes {
411 timestamp: 1000,
412 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x02),
413 prev_randao: B256::repeat_byte(0x03),
414 gas_limit: 30_000_000,
415 parent_beacon_block_root: Some(B256::ZERO),
416 withdrawals: None,
417 extra_data: Default::default(),
418 slot_number: None,
419 },
420 general_gas_limit: 10_000_000,
421 shared_gas_limit: 3_000_000,
422 timestamp_millis_part: 750,
423 consensus_context: None,
424 subblock_fee_recipients: HashMap::new(),
425 };
426
427 let result = evm_config.next_evm_env(&parent, &attributes);
428 assert!(result.is_ok());
429
430 let evm_env = result.unwrap();
431
432 assert_eq!(evm_env.block_env.inner.number, U256::from(100));
435 assert_eq!(evm_env.block_env.inner.timestamp, U256::from(1000));
436 assert_eq!(
437 evm_env.block_env.inner.beneficiary,
438 Address::repeat_byte(0x02)
439 );
440 assert_eq!(evm_env.block_env.inner.gas_limit, 30_000_000);
441
442 assert_eq!(evm_env.block_env.timestamp_millis_part, 750);
444 }
445
446 #[test]
447 fn test_context_for_block() {
448 let chainspec = test_chainspec();
449 let evm_config = TempoEvmConfig::new(chainspec.clone());
450
451 let validator_key = B256::repeat_byte(0x01);
453 let fee_recipient = alloy_primitives::Address::repeat_byte(0x02);
454 let metadata = vec![SubBlockMetadata {
455 version: SubBlockVersion::V1,
456 validator: validator_key,
457 fee_recipient,
458 signature: Bytes::from_static(&[0; 64]),
459 }];
460
461 let block_number = 1u64;
463 let mut input = BytesMut::new();
464 metadata.encode(&mut input);
465 input.extend_from_slice(&U256::from(block_number).to_be_bytes::<32>());
466
467 let system_tx = TempoTxEnvelope::Legacy(Signed::new_unhashed(
468 TxLegacy {
469 chain_id: Some(reth_chainspec::EthChainSpec::chain(&*chainspec).id()),
470 nonce: 0,
471 gas_price: 0,
472 gas_limit: 0,
473 to: TxKind::Call(alloy_primitives::Address::ZERO),
474 value: U256::ZERO,
475 input: input.freeze().into(),
476 },
477 TEMPO_SYSTEM_TX_SIGNATURE,
478 ));
479
480 let header = TempoHeader {
481 inner: alloy_consensus::Header {
482 number: block_number,
483 timestamp: 1000,
484 gas_limit: 30_000_000,
485 parent_beacon_block_root: Some(B256::ZERO),
486 ..Default::default()
487 },
488 general_gas_limit: 10_000_000,
489 timestamp_millis_part: 500,
490 shared_gas_limit: 3_000_000,
491 ..Default::default()
492 };
493
494 let body = BlockBody {
495 transactions: vec![system_tx],
496 ommers: vec![],
497 withdrawals: None,
498 };
499
500 let block = Block { header, body };
501 let sealed_block = SealedBlock::seal_slow(block);
502
503 let result = evm_config.context_for_block(&sealed_block);
504 assert!(result.is_ok());
505
506 let context = result.unwrap();
507
508 assert_eq!(context.general_gas_limit, 10_000_000);
510 assert_eq!(context.shared_gas_limit, 3_000_000);
511 assert!(context.validator_set.is_none());
512
513 let partial_key = PartialValidatorKey::from_slice(&validator_key[..15]);
515 assert_eq!(
516 context.subblock_fee_recipients.get(&partial_key),
517 Some(&fee_recipient)
518 );
519 }
520
521 #[test]
522 fn test_context_for_block_t4_without_metadata_has_empty_fee_recipients() {
523 use tempo_chainspec::spec::DEV;
524
525 let chainspec = DEV.clone();
526 let evm_config = TempoEvmConfig::new(chainspec);
527
528 let header = TempoHeader {
529 inner: alloy_consensus::Header {
530 number: 1,
531 timestamp: 1000,
532 gas_limit: 30_000_000,
533 parent_beacon_block_root: Some(B256::ZERO),
534 ..Default::default()
535 },
536 general_gas_limit: 10_000_000,
537 timestamp_millis_part: 500,
538 shared_gas_limit: 3_000_000,
539 ..Default::default()
540 };
541
542 let body = BlockBody {
543 transactions: vec![],
544 ommers: vec![],
545 withdrawals: None,
546 };
547
548 let block = Block { header, body };
549 let sealed_block = SealedBlock::seal_slow(block);
550
551 let context = evm_config.context_for_block(&sealed_block).unwrap();
552 assert!(context.subblock_fee_recipients.is_empty());
553 }
554
555 #[test]
556 fn test_context_for_next_block() {
557 let evm_config = TempoEvmConfig::new(test_chainspec());
558
559 let parent_header = TempoHeader {
560 inner: alloy_consensus::Header {
561 number: 99,
562 timestamp: 900,
563 gas_limit: 30_000_000,
564 ..Default::default()
565 },
566 general_gas_limit: 10_000_000,
567 timestamp_millis_part: 0,
568 shared_gas_limit: 0,
569 ..Default::default()
570 };
571 let parent = SealedHeader::seal_slow(parent_header);
572
573 let fee_recipient = Address::repeat_byte(0x02);
574 let mut subblock_fee_recipients = HashMap::new();
575 let partial_key = PartialValidatorKey::from_slice(&[0x01; 15]);
576 subblock_fee_recipients.insert(partial_key, fee_recipient);
577
578 let attributes = TempoNextBlockEnvAttributes {
579 inner: NextBlockEnvAttributes {
580 timestamp: 1000,
581 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x03),
582 prev_randao: B256::repeat_byte(0x04),
583 gas_limit: 30_000_000,
584 parent_beacon_block_root: Some(B256::repeat_byte(0x05)),
585 withdrawals: None,
586 extra_data: Default::default(),
587 slot_number: None,
588 },
589 general_gas_limit: 12_000_000,
590 shared_gas_limit: 4_000_000,
591 timestamp_millis_part: 999,
592 consensus_context: None,
593 subblock_fee_recipients: subblock_fee_recipients.clone(),
594 };
595
596 let result = evm_config.context_for_next_block(&parent, attributes);
597 assert!(result.is_ok());
598
599 let context = result.unwrap();
600
601 assert_eq!(context.general_gas_limit, 12_000_000);
603 assert_eq!(context.shared_gas_limit, 4_000_000);
604 assert!(context.validator_set.is_none());
605 assert_eq!(context.inner.parent_hash, parent.hash());
606 assert_eq!(
607 context.inner.parent_beacon_block_root,
608 Some(B256::repeat_byte(0x05))
609 );
610
611 assert_eq!(
613 context.subblock_fee_recipients.get(&partial_key),
614 Some(&fee_recipient)
615 );
616 }
617}