reth_evm_ethereum/
convert.rs1use 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
19pub 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
61pub(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
70pub(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#[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 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#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct ExecutableRecoveredTx {
128 tx_env: RecoveredTxEnvelope,
129 tx: Recovered<TransactionSigned>,
130}
131
132impl ExecutableRecoveredTx {
133 pub fn new(tx: Recovered<TransactionSigned>) -> Self {
135 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
148pub 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}