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::{TempoBlockExecutor, TempoReceiptBuilder, TempoTxResult};
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,
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, 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).to() == Some(Address::ZERO))
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().gas_limit()
257 / tempo_consensus::TEMPO_SHARED_GAS_DIVISOR,
258 validator_set: None,
260 consensus_context: block.header().consensus_context,
261 subblock_fee_recipients,
262 })
263 }
264
265 fn context_for_next_block(
266 &self,
267 parent: &SealedHeader<TempoHeader>,
268 attributes: Self::NextBlockEnvCtx,
269 ) -> Result<TempoBlockExecutionCtx<'_>, Self::Error> {
270 Ok(TempoBlockExecutionCtx {
271 inner: EthBlockExecutionCtx {
272 parent_hash: parent.hash(),
273 parent_beacon_block_root: attributes.parent_beacon_block_root,
274 slot_number: attributes.slot_number,
275 ommers: &[],
276 withdrawals: attributes
277 .inner
278 .withdrawals
279 .map(|w| Cow::Owned(w.into_inner())),
280 extra_data: attributes.inner.extra_data,
281 tx_count_hint: None,
282 },
283 general_gas_limit: attributes.general_gas_limit,
284 shared_gas_limit: attributes.inner.gas_limit
285 / tempo_consensus::TEMPO_SHARED_GAS_DIVISOR,
286 validator_set: None,
288 consensus_context: attributes.consensus_context,
289 subblock_fee_recipients: attributes.subblock_fee_recipients,
290 })
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::test_utils::test_chainspec;
298 use alloy_consensus::{BlockHeader, Signed, TxLegacy};
299 use alloy_primitives::{B256, Bytes, TxKind, U256};
300 use alloy_rlp::{Encodable, bytes::BytesMut};
301 use reth_evm::{ConfigureEvm, NextBlockEnvAttributes};
302 use std::collections::HashMap;
303 use tempo_chainspec::hardfork::TempoHardfork;
304 use tempo_primitives::{
305 BlockBody, SubBlockMetadata, subblock::SubBlockVersion,
306 transaction::envelope::TEMPO_SYSTEM_TX_SIGNATURE,
307 };
308
309 #[test]
310 fn test_evm_config_can_query_tempo_hardforks() {
311 let evm_config = TempoEvmConfig::new(test_chainspec());
312 let activation = evm_config
313 .chain_spec()
314 .tempo_fork_activation(TempoHardfork::Genesis);
315 assert_eq!(activation, reth_chainspec::ForkCondition::Timestamp(0));
316 }
317
318 #[test]
319 fn test_evm_env() {
320 let evm_config = TempoEvmConfig::new(test_chainspec());
321
322 let header = TempoHeader {
323 inner: alloy_consensus::Header {
324 number: 100,
325 timestamp: 1000,
326 gas_limit: 30_000_000,
327 base_fee_per_gas: Some(1000),
328 beneficiary: alloy_primitives::Address::repeat_byte(0x01),
329 ..Default::default()
330 },
331 general_gas_limit: 10_000_000,
332 timestamp_millis_part: 500,
333 shared_gas_limit: 3_000_000,
334 ..Default::default()
335 };
336
337 let result = evm_config.evm_env(&header);
338 assert!(result.is_ok());
339
340 let evm_env = result.unwrap();
341
342 assert_eq!(evm_env.block_env.inner.number, U256::from(header.number()));
344 assert_eq!(
345 evm_env.block_env.inner.timestamp,
346 U256::from(header.timestamp())
347 );
348 assert_eq!(evm_env.block_env.inner.gas_limit, header.gas_limit());
349 assert_eq!(evm_env.block_env.inner.beneficiary, header.beneficiary());
350
351 assert_eq!(evm_env.block_env.timestamp_millis_part, 500);
353 }
354
355 #[test]
359 fn test_evm_env_t1_gas_cap() {
360 use tempo_chainspec::spec::DEV;
361
362 let chainspec = DEV.clone();
364 let evm_config = TempoEvmConfig::new(chainspec.clone());
365
366 let header = TempoHeader {
367 inner: alloy_consensus::Header {
368 number: 100,
369 timestamp: 1000, gas_limit: 30_000_000,
371 base_fee_per_gas: Some(1000),
372 ..Default::default()
373 },
374 general_gas_limit: 10_000_000,
375 timestamp_millis_part: 0,
376 shared_gas_limit: 3_000_000,
377 ..Default::default()
378 };
379
380 assert!(chainspec.tempo_hardfork_at(header.timestamp()).is_t1());
382
383 let evm_env = evm_config.evm_env(&header).unwrap();
384
385 assert_eq!(
387 evm_env.cfg_env.tx_gas_limit_cap,
388 Some(tempo_chainspec::spec::TEMPO_T1_TX_GAS_LIMIT_CAP),
389 "TIP-1000 requires 30M gas limit cap for T1 hardfork"
390 );
391 }
392
393 #[test]
394 fn test_next_evm_env() {
395 let evm_config = TempoEvmConfig::new(test_chainspec());
396
397 let parent = TempoHeader {
398 inner: alloy_consensus::Header {
399 number: 99,
400 timestamp: 900,
401 gas_limit: 30_000_000,
402 base_fee_per_gas: Some(1000),
403 ..Default::default()
404 },
405 general_gas_limit: 10_000_000,
406 timestamp_millis_part: 0,
407 shared_gas_limit: 3_000_000,
408 ..Default::default()
409 };
410
411 let attributes = TempoNextBlockEnvAttributes {
412 inner: NextBlockEnvAttributes {
413 timestamp: 1000,
414 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x02),
415 prev_randao: B256::repeat_byte(0x03),
416 gas_limit: 30_000_000,
417 parent_beacon_block_root: Some(B256::ZERO),
418 withdrawals: None,
419 extra_data: Default::default(),
420 slot_number: None,
421 },
422 general_gas_limit: 10_000_000,
423 shared_gas_limit: 3_000_000,
424 timestamp_millis_part: 750,
425 consensus_context: None,
426 subblock_fee_recipients: HashMap::new(),
427 };
428
429 let result = evm_config.next_evm_env(&parent, &attributes);
430 assert!(result.is_ok());
431
432 let evm_env = result.unwrap();
433
434 assert_eq!(evm_env.block_env.inner.number, U256::from(100));
437 assert_eq!(evm_env.block_env.inner.timestamp, U256::from(1000));
438 assert_eq!(
439 evm_env.block_env.inner.beneficiary,
440 Address::repeat_byte(0x02)
441 );
442 assert_eq!(evm_env.block_env.inner.gas_limit, 30_000_000);
443
444 assert_eq!(evm_env.block_env.timestamp_millis_part, 750);
446 }
447
448 #[test]
449 fn test_context_for_block() {
450 let chainspec = test_chainspec();
451 let evm_config = TempoEvmConfig::new(chainspec.clone());
452
453 let validator_key = B256::repeat_byte(0x01);
455 let fee_recipient = alloy_primitives::Address::repeat_byte(0x02);
456 let metadata = vec![SubBlockMetadata {
457 version: SubBlockVersion::V1,
458 validator: validator_key,
459 fee_recipient,
460 signature: Bytes::from_static(&[0; 64]),
461 }];
462
463 let block_number = 1u64;
465 let mut input = BytesMut::new();
466 metadata.encode(&mut input);
467 input.extend_from_slice(&U256::from(block_number).to_be_bytes::<32>());
468
469 let system_tx = TempoTxEnvelope::Legacy(Signed::new_unhashed(
470 TxLegacy {
471 chain_id: Some(reth_chainspec::EthChainSpec::chain(&*chainspec).id()),
472 nonce: 0,
473 gas_price: 0,
474 gas_limit: 0,
475 to: TxKind::Call(alloy_primitives::Address::ZERO),
476 value: U256::ZERO,
477 input: input.freeze().into(),
478 },
479 TEMPO_SYSTEM_TX_SIGNATURE,
480 ));
481
482 let header = TempoHeader {
483 inner: alloy_consensus::Header {
484 number: block_number,
485 timestamp: 1000,
486 gas_limit: 30_000_000,
487 parent_beacon_block_root: Some(B256::ZERO),
488 ..Default::default()
489 },
490 general_gas_limit: 10_000_000,
491 timestamp_millis_part: 500,
492 shared_gas_limit: 3_000_000,
493 ..Default::default()
494 };
495
496 let body = BlockBody {
497 transactions: vec![system_tx],
498 ommers: vec![],
499 withdrawals: None,
500 };
501
502 let block = Block { header, body };
503 let sealed_block = SealedBlock::seal_slow(block);
504
505 let result = evm_config.context_for_block(&sealed_block);
506 assert!(result.is_ok());
507
508 let context = result.unwrap();
509
510 assert_eq!(context.general_gas_limit, 10_000_000);
512 assert_eq!(context.shared_gas_limit, 3_000_000);
513 assert!(context.validator_set.is_none());
514
515 let partial_key = PartialValidatorKey::from_slice(&validator_key[..15]);
517 assert_eq!(
518 context.subblock_fee_recipients.get(&partial_key),
519 Some(&fee_recipient)
520 );
521 }
522
523 #[test]
524 fn test_context_for_block_t4_without_metadata_has_empty_fee_recipients() {
525 use tempo_chainspec::spec::DEV;
526
527 let chainspec = DEV.clone();
528 let evm_config = TempoEvmConfig::new(chainspec);
529
530 let header = TempoHeader {
531 inner: alloy_consensus::Header {
532 number: 1,
533 timestamp: 1000,
534 gas_limit: 30_000_000,
535 parent_beacon_block_root: Some(B256::ZERO),
536 ..Default::default()
537 },
538 general_gas_limit: 10_000_000,
539 timestamp_millis_part: 500,
540 shared_gas_limit: 3_000_000,
541 ..Default::default()
542 };
543
544 let body = BlockBody {
545 transactions: vec![],
546 ommers: vec![],
547 withdrawals: None,
548 };
549
550 let block = Block { header, body };
551 let sealed_block = SealedBlock::seal_slow(block);
552
553 let context = evm_config.context_for_block(&sealed_block).unwrap();
554 assert!(context.subblock_fee_recipients.is_empty());
555 }
556
557 #[test]
558 fn test_context_for_next_block() {
559 let evm_config = TempoEvmConfig::new(test_chainspec());
560
561 let parent_header = TempoHeader {
562 inner: alloy_consensus::Header {
563 number: 99,
564 timestamp: 900,
565 gas_limit: 30_000_000,
566 ..Default::default()
567 },
568 general_gas_limit: 10_000_000,
569 timestamp_millis_part: 0,
570 shared_gas_limit: 3_000_000,
571 ..Default::default()
572 };
573 let parent = SealedHeader::seal_slow(parent_header);
574
575 let fee_recipient = Address::repeat_byte(0x02);
576 let mut subblock_fee_recipients = HashMap::new();
577 let partial_key = PartialValidatorKey::from_slice(&[0x01; 15]);
578 subblock_fee_recipients.insert(partial_key, fee_recipient);
579
580 let attributes = TempoNextBlockEnvAttributes {
581 inner: NextBlockEnvAttributes {
582 timestamp: 1000,
583 suggested_fee_recipient: alloy_primitives::Address::repeat_byte(0x03),
584 prev_randao: B256::repeat_byte(0x04),
585 gas_limit: 30_000_000,
586 parent_beacon_block_root: Some(B256::repeat_byte(0x05)),
587 withdrawals: None,
588 extra_data: Default::default(),
589 slot_number: None,
590 },
591 general_gas_limit: 12_000_000,
592 shared_gas_limit: 4_000_000,
593 timestamp_millis_part: 999,
594 consensus_context: None,
595 subblock_fee_recipients: subblock_fee_recipients.clone(),
596 };
597
598 let result = evm_config.context_for_next_block(&parent, attributes);
599 assert!(result.is_ok());
600
601 let context = result.unwrap();
602
603 assert_eq!(context.general_gas_limit, 12_000_000);
605 assert_eq!(context.shared_gas_limit, 3_000_000);
606 assert!(context.validator_set.is_none());
607 assert_eq!(context.inner.parent_hash, parent.hash());
608 assert_eq!(
609 context.inner.parent_beacon_block_root,
610 Some(B256::repeat_byte(0x05))
611 );
612
613 assert_eq!(
615 context.subblock_fee_recipients.get(&partial_key),
616 Some(&fee_recipient)
617 );
618 }
619}