Skip to main content

reth_evm_ethereum/
convert.rs

1//! Conversion helpers for Ethereum execution.
2
3use alloy_consensus::{transaction::Recovered, BlockHeader};
4use alloy_eips::eip7840::BlobParams;
5use alloy_primitives::{Address, BlockNumber, BlockTimestamp, U256};
6use alloy_rpc_types_engine::ExecutionData;
7use evm2::{
8    env::{BlockEnv, BlockEnvExt},
9    ethereum::RecoveredTxEnvelope,
10    SpecId,
11};
12use reth_chainspec::EthereumHardforks;
13use reth_ethereum_primitives::TransactionSigned;
14use reth_evm::{ExecutableTxParts, RecoveredTx};
15
16#[cfg(feature = "std")]
17const PARALLEL_AUTHORIZATION_THRESHOLD: usize = 16;
18
19/// Map the latest active Ethereum hardfork at `timestamp` or `block_number` to a [`SpecId`].
20pub fn spec_id_by_timestamp_and_block_number<C>(
21    chain_spec: &C,
22    timestamp: BlockTimestamp,
23    block_number: BlockNumber,
24) -> SpecId
25where
26    C: EthereumHardforks,
27{
28    if chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
29        SpecId::AMSTERDAM
30    } else if chain_spec.is_osaka_active_at_timestamp(timestamp) {
31        SpecId::OSAKA
32    } else if chain_spec.is_prague_active_at_timestamp(timestamp) {
33        SpecId::PRAGUE
34    } else if chain_spec.is_cancun_active_at_timestamp(timestamp) {
35        SpecId::CANCUN
36    } else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
37        SpecId::SHANGHAI
38    } else if chain_spec.is_paris_active_at_block(block_number) {
39        SpecId::MERGE
40    } else if chain_spec.is_london_active_at_block(block_number) {
41        SpecId::LONDON
42    } else if chain_spec.is_berlin_active_at_block(block_number) {
43        SpecId::BERLIN
44    } else if chain_spec.is_istanbul_active_at_block(block_number) {
45        SpecId::ISTANBUL
46    } else if chain_spec.is_petersburg_active_at_block(block_number) {
47        SpecId::PETERSBURG
48    } else if chain_spec.is_byzantium_active_at_block(block_number) {
49        SpecId::BYZANTIUM
50    } else if chain_spec.is_spurious_dragon_active_at_block(block_number) {
51        SpecId::SPURIOUS_DRAGON
52    } else if chain_spec.is_tangerine_whistle_active_at_block(block_number) {
53        SpecId::TANGERINE
54    } else if chain_spec.is_homestead_active_at_block(block_number) {
55        SpecId::HOMESTEAD
56    } else {
57        SpecId::FRONTIER
58    }
59}
60
61/// Map the latest active hardfork at `header` to an [`SpecId`].
62pub(crate) fn spec_id<C, H>(chain_spec: &C, header: &H) -> SpecId
63where
64    C: EthereumHardforks,
65    H: BlockHeader,
66{
67    spec_id_by_timestamp_and_block_number(chain_spec, header.timestamp(), header.number())
68}
69
70/// Converts an Ethereum header into the block environment with chain blob parameters.
71pub(crate) fn block_env_with_blob_params<H: BlockHeader>(
72    header: &H,
73    blob_params: Option<BlobParams>,
74) -> BlockEnv {
75    BlockEnvExt {
76        number: U256::from(header.number()),
77        beneficiary: header.beneficiary(),
78        timestamp: U256::from(header.timestamp()),
79        gas_limit: U256::from(header.gas_limit()),
80        basefee: U256::from(header.base_fee_per_gas().unwrap_or_default()),
81        difficulty: header.difficulty(),
82        prevrandao: header.mix_hash().map(|hash| U256::from_be_bytes(hash.0)).unwrap_or_default(),
83        blob_basefee: blob_basefee(header.excess_blob_gas(), blob_params),
84        slot_num: U256::from(header.slot_number().unwrap_or_default()),
85        ext: (),
86        _non_exhaustive: (),
87    }
88}
89
90/// Converts engine execution payload data into the block environment.
91#[cfg_attr(not(feature = "std"), allow(dead_code))]
92pub(crate) fn payload_block_env(
93    payload: &ExecutionData,
94    spec: SpecId,
95    blob_params: Option<BlobParams>,
96) -> BlockEnv {
97    let payload = &payload.payload;
98    BlockEnvExt {
99        number: U256::from(payload.block_number()),
100        beneficiary: payload.fee_recipient(),
101        timestamp: U256::from(payload.timestamp()),
102        gas_limit: U256::from(payload.gas_limit()),
103        basefee: U256::from(payload.saturated_base_fee_per_gas()),
104        // Preserve the payload randao as difficulty for custom pre-Merge configurations.
105        difficulty: if spec >= SpecId::MERGE {
106            U256::ZERO
107        } else {
108            U256::from_be_bytes(payload.as_v1().prev_randao.0)
109        },
110        prevrandao: U256::from_be_bytes(payload.as_v1().prev_randao.0),
111        blob_basefee: blob_basefee(payload.excess_blob_gas(), blob_params),
112        slot_num: U256::from(payload.as_v4().map(|v4| v4.slot_number).unwrap_or_default()),
113        ext: (),
114        _non_exhaustive: (),
115    }
116}
117
118fn blob_basefee(excess_blob_gas: Option<u64>, blob_params: Option<BlobParams>) -> U256 {
119    excess_blob_gas
120        .zip(blob_params)
121        .map(|(excess_blob_gas, params)| U256::from(params.calc_blob_fee(excess_blob_gas)))
122        .unwrap_or_default()
123}
124
125/// Recovered Ethereum transaction paired with its cached transaction environment.
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct ExecutableRecoveredTx {
128    tx_env: RecoveredTxEnvelope,
129    tx: Recovered<TransactionSigned>,
130}
131
132impl ExecutableRecoveredTx {
133    /// Creates a transaction wrapper and precomputes the transaction environment.
134    pub fn new(tx: Recovered<TransactionSigned>) -> Self {
135        // Cache the hash before cloning so neither envelope hashes on the execution thread.
136        tx.inner().tx_hash();
137        let tx_env = tx.clone().map(|signed| match signed {
138            TransactionSigned::Eip7702(signed) => {
139                let body = signed.strip_signature();
140                evm2::ethereum::TxEnvelope::Eip7702(recover_authorizations(body))
141            }
142            signed => signed.into(),
143        });
144        Self { tx_env, tx }
145    }
146}
147
148/// Recovers EIP-7702 authorizations, using parallel recovery for large lists.
149pub fn recover_authorizations(tx: alloy_consensus::TxEip7702) -> evm2::ethereum::LazyTxEip7702 {
150    #[cfg(feature = "std")]
151    if tx.authorization_list.len() >= PARALLEL_AUTHORIZATION_THRESHOLD {
152        use rayon::prelude::*;
153
154        let authorities = tx
155            .authorization_list
156            .par_iter()
157            .map(|auth| evm2::ethereum::LazyAuthorization::Signed(auth).authority())
158            .collect();
159        return evm2::ethereum::LazyTxEip7702::from_cached_recovered_authorizations(tx, authorities);
160    }
161
162    evm2::ethereum::LazyTxEip7702::from_recovered_authorizations(tx)
163}
164
165impl RecoveredTx<TransactionSigned> for ExecutableRecoveredTx {
166    fn tx(&self) -> &TransactionSigned {
167        self.tx.inner()
168    }
169
170    fn signer(&self) -> &Address {
171        self.tx.signer_ref()
172    }
173}
174
175impl ExecutableTxParts<RecoveredTxEnvelope, TransactionSigned> for ExecutableRecoveredTx {
176    type Recovered = Recovered<TransactionSigned>;
177
178    fn into_parts(self) -> (RecoveredTxEnvelope, Self::Recovered) {
179        (self.tx_env, self.tx)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use alloy_consensus::Header;
187
188    #[test]
189    fn block_env_uses_header_slot_number() {
190        let header = Header { slot_number: Some(42), ..Default::default() };
191
192        let env = block_env_with_blob_params(&header, None);
193
194        assert_eq!(env.slot_num, U256::from(42));
195    }
196}