1#![doc(
10 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
11 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
12 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
13)]
14#![cfg_attr(not(test), warn(unused_crate_dependencies))]
15#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
16#![cfg_attr(not(feature = "std"), no_std)]
17
18extern crate alloc;
19
20use alloc::{borrow::Cow, sync::Arc};
21use alloy_consensus::{BlockHeader, Header};
22use alloy_eips::Decodable2718;
23pub use alloy_evm::EthEvm;
24use alloy_evm::{
25 eth::{EthBlockExecutionCtx, EthBlockExecutorFactory},
26 EthEvmFactory, FromRecoveredTx, FromTxWithEncoded,
27};
28use alloy_primitives::{Bytes, U256};
29use alloy_rpc_types_engine::ExecutionData;
30use core::{convert::Infallible, fmt::Debug};
31use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, MAINNET};
32use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
33use reth_evm::{
34 precompiles::PrecompilesMap, ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, EvmFactory,
35 ExecutableTxIterator, ExecutionCtxFor, NextBlockEnvAttributes, TransactionEnv,
36};
37use reth_primitives_traits::{
38 constants::MAX_TX_GAS_LIMIT_OSAKA, SealedBlock, SealedHeader, SignedTransaction, TxTy,
39};
40use reth_storage_errors::any::AnyError;
41use revm::{
42 context::{BlockEnv, CfgEnv},
43 context_interface::block::BlobExcessGasAndPrice,
44 primitives::hardfork::SpecId,
45};
46
47mod config;
48use alloy_eips::{eip1559::INITIAL_BASE_FEE, eip7840::BlobParams};
49use alloy_evm::eth::spec::EthExecutorSpec;
50pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number};
51use reth_ethereum_forks::{EthereumHardfork, Hardforks};
52
53#[doc(hidden)]
56pub mod execute {
57 use crate::EthEvmConfig;
58
59 #[deprecated(note = "Use `EthEvmConfig` instead")]
60 pub type EthExecutorProvider = EthEvmConfig;
61}
62
63mod build;
64pub use build::EthBlockAssembler;
65
66mod receipt;
67pub use receipt::RethReceiptBuilder;
68
69#[cfg(feature = "test-utils")]
70mod test_utils;
71#[cfg(feature = "test-utils")]
72pub use test_utils::*;
73
74#[derive(Debug, Clone)]
76pub struct EthEvmConfig<C = ChainSpec, EvmFactory = EthEvmFactory> {
77 pub executor_factory: EthBlockExecutorFactory<RethReceiptBuilder, Arc<C>, EvmFactory>,
79 pub block_assembler: EthBlockAssembler<C>,
81}
82
83impl EthEvmConfig {
84 pub fn mainnet() -> Self {
86 Self::ethereum(MAINNET.clone())
87 }
88}
89
90impl<ChainSpec> EthEvmConfig<ChainSpec> {
91 pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
93 Self::ethereum(chain_spec)
94 }
95
96 pub fn ethereum(chain_spec: Arc<ChainSpec>) -> Self {
98 Self::new_with_evm_factory(chain_spec, EthEvmFactory::default())
99 }
100}
101
102impl<ChainSpec, EvmFactory> EthEvmConfig<ChainSpec, EvmFactory> {
103 pub fn new_with_evm_factory(chain_spec: Arc<ChainSpec>, evm_factory: EvmFactory) -> Self {
105 Self {
106 block_assembler: EthBlockAssembler::new(chain_spec.clone()),
107 executor_factory: EthBlockExecutorFactory::new(
108 RethReceiptBuilder::default(),
109 chain_spec,
110 evm_factory,
111 ),
112 }
113 }
114
115 pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
117 self.executor_factory.spec()
118 }
119
120 pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
122 self.block_assembler.extra_data = extra_data;
123 self
124 }
125}
126
127impl<ChainSpec, EvmF> ConfigureEvm for EthEvmConfig<ChainSpec, EvmF>
128where
129 ChainSpec: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
130 EvmF: EvmFactory<
131 Tx: TransactionEnv
132 + FromRecoveredTx<TransactionSigned>
133 + FromTxWithEncoded<TransactionSigned>,
134 Spec = SpecId,
135 Precompiles = PrecompilesMap,
136 > + Clone
137 + Debug
138 + Send
139 + Sync
140 + Unpin
141 + 'static,
142{
143 type Primitives = EthPrimitives;
144 type Error = Infallible;
145 type NextBlockEnvCtx = NextBlockEnvAttributes;
146 type BlockExecutorFactory = EthBlockExecutorFactory<RethReceiptBuilder, Arc<ChainSpec>, EvmF>;
147 type BlockAssembler = EthBlockAssembler<ChainSpec>;
148
149 fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
150 &self.executor_factory
151 }
152
153 fn block_assembler(&self) -> &Self::BlockAssembler {
154 &self.block_assembler
155 }
156
157 fn evm_env(&self, header: &Header) -> EvmEnv {
158 let blob_params = self.chain_spec().blob_params_at_timestamp(header.timestamp);
159 let spec = config::revm_spec(self.chain_spec(), header);
160
161 let mut cfg_env =
163 CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec);
164
165 if let Some(blob_params) = &blob_params {
166 cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx);
167 }
168
169 if self.chain_spec().is_osaka_active_at_timestamp(header.timestamp) {
170 cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA);
171 }
172
173 let blob_excess_gas_and_price =
176 header.excess_blob_gas.zip(blob_params).map(|(excess_blob_gas, params)| {
177 let blob_gasprice = params.calc_blob_fee(excess_blob_gas);
178 BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
179 });
180
181 let block_env = BlockEnv {
182 number: U256::from(header.number()),
183 beneficiary: header.beneficiary(),
184 timestamp: U256::from(header.timestamp()),
185 difficulty: if spec >= SpecId::MERGE { U256::ZERO } else { header.difficulty() },
186 prevrandao: if spec >= SpecId::MERGE { header.mix_hash() } else { None },
187 gas_limit: header.gas_limit(),
188 basefee: header.base_fee_per_gas().unwrap_or_default(),
189 blob_excess_gas_and_price,
190 };
191
192 EvmEnv { cfg_env, block_env }
193 }
194
195 fn next_evm_env(
196 &self,
197 parent: &Header,
198 attributes: &NextBlockEnvAttributes,
199 ) -> Result<EvmEnv, Self::Error> {
200 let chain_spec = self.chain_spec();
202 let blob_params = chain_spec.blob_params_at_timestamp(attributes.timestamp);
203 let spec_id = revm_spec_by_timestamp_and_block_number(
204 chain_spec,
205 attributes.timestamp,
206 parent.number() + 1,
207 );
208
209 let mut cfg =
211 CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id);
212
213 if let Some(blob_params) = &blob_params {
214 cfg.set_max_blobs_per_tx(blob_params.max_blobs_per_tx);
215 }
216
217 if self.chain_spec().is_osaka_active_at_timestamp(attributes.timestamp) {
218 cfg.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA);
219 }
220
221 let blob_excess_gas_and_price = parent
224 .maybe_next_block_excess_blob_gas(blob_params)
225 .or_else(|| (spec_id == SpecId::CANCUN).then_some(0))
226 .map(|excess_blob_gas| {
227 let blob_gasprice =
228 blob_params.unwrap_or_else(BlobParams::cancun).calc_blob_fee(excess_blob_gas);
229 BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
230 });
231
232 let mut basefee = chain_spec.next_block_base_fee(parent, attributes.timestamp);
233
234 let mut gas_limit = attributes.gas_limit;
235
236 if self.chain_spec().fork(EthereumHardfork::London).transitions_at_block(parent.number + 1)
239 {
240 let elasticity_multiplier = self
241 .chain_spec()
242 .base_fee_params_at_timestamp(attributes.timestamp)
243 .elasticity_multiplier;
244
245 gas_limit *= elasticity_multiplier as u64;
247
248 basefee = Some(INITIAL_BASE_FEE)
250 }
251
252 let block_env = BlockEnv {
253 number: U256::from(parent.number + 1),
254 beneficiary: attributes.suggested_fee_recipient,
255 timestamp: U256::from(attributes.timestamp),
256 difficulty: U256::ZERO,
257 prevrandao: Some(attributes.prev_randao),
258 gas_limit,
259 basefee: basefee.unwrap_or_default(),
261 blob_excess_gas_and_price,
263 };
264
265 Ok((cfg, block_env).into())
266 }
267
268 fn context_for_block<'a>(&self, block: &'a SealedBlock<Block>) -> EthBlockExecutionCtx<'a> {
269 EthBlockExecutionCtx {
270 parent_hash: block.header().parent_hash,
271 parent_beacon_block_root: block.header().parent_beacon_block_root,
272 ommers: &block.body().ommers,
273 withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
274 }
275 }
276
277 fn context_for_next_block(
278 &self,
279 parent: &SealedHeader,
280 attributes: Self::NextBlockEnvCtx,
281 ) -> EthBlockExecutionCtx<'_> {
282 EthBlockExecutionCtx {
283 parent_hash: parent.hash(),
284 parent_beacon_block_root: attributes.parent_beacon_block_root,
285 ommers: &[],
286 withdrawals: attributes.withdrawals.map(Cow::Owned),
287 }
288 }
289}
290
291impl<ChainSpec, EvmF> ConfigureEngineEvm<ExecutionData> for EthEvmConfig<ChainSpec, EvmF>
292where
293 ChainSpec: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
294 EvmF: EvmFactory<
295 Tx: TransactionEnv
296 + FromRecoveredTx<TransactionSigned>
297 + FromTxWithEncoded<TransactionSigned>,
298 Spec = SpecId,
299 Precompiles = PrecompilesMap,
300 > + Clone
301 + Debug
302 + Send
303 + Sync
304 + Unpin
305 + 'static,
306{
307 fn evm_env_for_payload(&self, payload: &ExecutionData) -> EvmEnvFor<Self> {
308 let timestamp = payload.payload.timestamp();
309 let block_number = payload.payload.block_number();
310
311 let blob_params = self.chain_spec().blob_params_at_timestamp(timestamp);
312 let spec =
313 revm_spec_by_timestamp_and_block_number(self.chain_spec(), timestamp, block_number);
314
315 let mut cfg_env =
317 CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec);
318
319 if let Some(blob_params) = &blob_params {
320 cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx);
321 }
322
323 if self.chain_spec().is_osaka_active_at_timestamp(timestamp) {
324 cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA);
325 }
326
327 let blob_excess_gas_and_price =
330 payload.payload.excess_blob_gas().zip(blob_params).map(|(excess_blob_gas, params)| {
331 let blob_gasprice = params.calc_blob_fee(excess_blob_gas);
332 BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
333 });
334
335 let block_env = BlockEnv {
336 number: U256::from(block_number),
337 beneficiary: payload.payload.fee_recipient(),
338 timestamp: U256::from(timestamp),
339 difficulty: if spec >= SpecId::MERGE {
340 U256::ZERO
341 } else {
342 payload.payload.as_v1().prev_randao.into()
343 },
344 prevrandao: (spec >= SpecId::MERGE).then(|| payload.payload.as_v1().prev_randao),
345 gas_limit: payload.payload.gas_limit(),
346 basefee: payload.payload.saturated_base_fee_per_gas(),
347 blob_excess_gas_and_price,
348 };
349
350 EvmEnv { cfg_env, block_env }
351 }
352
353 fn context_for_payload<'a>(&self, payload: &'a ExecutionData) -> ExecutionCtxFor<'a, Self> {
354 EthBlockExecutionCtx {
355 parent_hash: payload.parent_hash(),
356 parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(),
357 ommers: &[],
358 withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())),
359 }
360 }
361
362 fn tx_iterator_for_payload(&self, payload: &ExecutionData) -> impl ExecutableTxIterator<Self> {
363 payload.payload.transactions().clone().into_iter().map(|tx| {
364 let tx =
365 TxTy::<Self::Primitives>::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;
366 let signer = tx.try_recover().map_err(AnyError::new)?;
367 Ok::<_, AnyError>(tx.with_signer(signer))
368 })
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use alloy_consensus::Header;
376 use alloy_genesis::Genesis;
377 use reth_chainspec::{Chain, ChainSpec};
378 use reth_evm::{execute::ProviderError, EvmEnv};
379 use revm::{
380 context::{BlockEnv, CfgEnv},
381 database::CacheDB,
382 database_interface::EmptyDBTyped,
383 inspector::NoOpInspector,
384 };
385
386 #[test]
387 fn test_fill_cfg_and_block_env() {
388 let header = Header::default();
390
391 let chain_spec = ChainSpec::builder()
394 .chain(Chain::mainnet())
395 .genesis(Genesis::default())
396 .london_activated()
397 .paris_activated()
398 .shanghai_activated()
399 .build();
400
401 let EvmEnv { cfg_env, .. } =
404 EthEvmConfig::new(Arc::new(chain_spec.clone())).evm_env(&header);
405
406 assert_eq!(cfg_env.chain_id, chain_spec.chain().id());
409 }
410
411 #[test]
412 fn test_evm_with_env_default_spec() {
413 let evm_config = EthEvmConfig::mainnet();
414
415 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
416
417 let evm_env = EvmEnv::default();
418
419 let evm = evm_config.evm_with_env(db, evm_env.clone());
420
421 assert_eq!(evm.block, evm_env.block_env);
423 assert_eq!(evm.cfg, evm_env.cfg_env);
424 }
425
426 #[test]
427 fn test_evm_with_env_custom_cfg() {
428 let evm_config = EthEvmConfig::mainnet();
429
430 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
431
432 let cfg = CfgEnv::default().with_chain_id(111);
434
435 let evm_env = EvmEnv { cfg_env: cfg.clone(), ..Default::default() };
436
437 let evm = evm_config.evm_with_env(db, evm_env);
438
439 assert_eq!(evm.cfg, cfg);
441 }
442
443 #[test]
444 fn test_evm_with_env_custom_block_and_tx() {
445 let evm_config = EthEvmConfig::mainnet();
446
447 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
448
449 let block = BlockEnv {
451 basefee: 1000,
452 gas_limit: 10_000_000,
453 number: U256::from(42),
454 ..Default::default()
455 };
456
457 let evm_env = EvmEnv { block_env: block, ..Default::default() };
458
459 let evm = evm_config.evm_with_env(db, evm_env.clone());
460
461 assert_eq!(evm.block, evm_env.block_env);
463
464 assert_eq!(evm.cfg.spec, SpecId::default());
466 }
467
468 #[test]
469 fn test_evm_with_spec_id() {
470 let evm_config = EthEvmConfig::mainnet();
471
472 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
473
474 let evm_env = EvmEnv {
475 cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
476 ..Default::default()
477 };
478
479 let evm = evm_config.evm_with_env(db, evm_env);
480
481 assert_eq!(evm.cfg.spec, SpecId::CONSTANTINOPLE);
483 }
484
485 #[test]
486 fn test_evm_with_env_and_default_inspector() {
487 let evm_config = EthEvmConfig::mainnet();
488 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
489
490 let evm_env = EvmEnv::default();
491
492 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
493
494 assert_eq!(evm.block, evm_env.block_env);
496 assert_eq!(evm.cfg, evm_env.cfg_env);
497 }
498
499 #[test]
500 fn test_evm_with_env_inspector_and_custom_cfg() {
501 let evm_config = EthEvmConfig::mainnet();
502 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
503
504 let cfg_env = CfgEnv::default().with_chain_id(111);
505 let block = BlockEnv::default();
506 let evm_env = EvmEnv { cfg_env: cfg_env.clone(), block_env: block };
507
508 let evm = evm_config.evm_with_env_and_inspector(db, evm_env, NoOpInspector {});
509
510 assert_eq!(evm.cfg, cfg_env);
512 assert_eq!(evm.cfg.spec, SpecId::default());
513 }
514
515 #[test]
516 fn test_evm_with_env_inspector_and_custom_block_tx() {
517 let evm_config = EthEvmConfig::mainnet();
518 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
519
520 let block = BlockEnv {
522 basefee: 1000,
523 gas_limit: 10_000_000,
524 number: U256::from(42),
525 ..Default::default()
526 };
527 let evm_env = EvmEnv { block_env: block, ..Default::default() };
528
529 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
530
531 assert_eq!(evm.block, evm_env.block_env);
533 assert_eq!(evm.cfg.spec, SpecId::default());
534 }
535
536 #[test]
537 fn test_evm_with_env_inspector_and_spec_id() {
538 let evm_config = EthEvmConfig::mainnet();
539 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
540
541 let evm_env = EvmEnv {
542 cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
543 ..Default::default()
544 };
545
546 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
547
548 assert_eq!(evm.block, evm_env.block_env);
550 assert_eq!(evm.cfg, evm_env.cfg_env);
551 assert_eq!(evm.tx, Default::default());
552 }
553}