Skip to main content

ethrex_common/types/
block.rs

1use super::{
2    BASE_FEE_MAX_CHANGE_DENOMINATOR, ChainConfig, Fork, ForkBlobSchedule,
3    GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM, INITIAL_BASE_FEE,
4};
5use crate::{
6    Address, H256, U256,
7    constants::{
8        BLOB_BASE_COST, DEFAULT_OMMERS_HASH, EMPTY_WITHDRAWALS_HASH, GAS_PER_BLOB,
9        MIN_BASE_FEE_PER_BLOB_GAS,
10    },
11    types::{Receipt, Transaction},
12};
13use bytes::Bytes;
14use ethereum_types::Bloom;
15use ethrex_crypto::{Crypto, CryptoError, NativeCrypto};
16use ethrex_rlp::{
17    decode::RLPDecode,
18    encode::RLPEncode,
19    error::RLPDecodeError,
20    structs::{Decoder, Encoder},
21};
22use ethrex_trie::Trie;
23#[cfg(all(not(feature = "eip-8025"), feature = "rayon"))]
24use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
25use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize};
26use serde::{Deserialize, Serialize};
27
28use std::cmp::{Ordering, max};
29
30pub type BlockNumber = u64;
31pub type BlockHash = H256;
32
33#[cfg(all(feature = "eip-8025", target_arch = "riscv64"))]
34use super::eip8025_cell::OnceCell;
35#[cfg(not(all(feature = "eip-8025", target_arch = "riscv64")))]
36use once_cell::sync::OnceCell;
37
38#[derive(
39    PartialEq, Eq, Debug, Clone, Deserialize, Serialize, Default, RSerialize, RDeserialize, Archive,
40)]
41pub struct Block {
42    pub header: BlockHeader,
43    pub body: BlockBody,
44}
45
46impl Block {
47    pub fn new(header: BlockHeader, body: BlockBody) -> Block {
48        Block { header, body }
49    }
50
51    pub fn hash(&self) -> BlockHash {
52        self.header.hash()
53    }
54}
55
56impl RLPEncode for Block {
57    fn encode(&self, buf: &mut dyn bytes::BufMut) {
58        Encoder::new(buf)
59            .encode_field(&self.header)
60            .encode_field(&self.body.transactions)
61            .encode_field(&self.body.ommers)
62            .encode_optional_field(&self.body.withdrawals)
63            .finish();
64    }
65}
66
67impl RLPDecode for Block {
68    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
69        let decoder = Decoder::new(rlp)?;
70        let (header, decoder) = decoder.decode_field("header")?;
71        let (transactions, decoder) = decoder.decode_field("transactions")?;
72        let (ommers, decoder) = decoder.decode_field("ommers")?;
73        let (withdrawals, decoder) = decoder.decode_optional_field();
74        let remaining = decoder.finish()?;
75        let body = BlockBody {
76            transactions,
77            ommers,
78            withdrawals,
79        };
80        let block = Block::new(header, body);
81        Ok((block, remaining))
82    }
83}
84
85/// Header part of a block on the chain.
86#[derive(Clone, Debug, Serialize, Default, Deserialize, RSerialize, RDeserialize, Archive, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct BlockHeader {
89    #[serde(skip)]
90    #[rkyv(with=rkyv::with::Skip)]
91    pub hash: OnceCell<BlockHash>,
92    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
93    pub parent_hash: H256,
94    #[serde(rename = "sha3Uncles")]
95    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
96    pub ommers_hash: H256, // ommer = uncle
97    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
98    #[serde(rename = "miner")]
99    pub coinbase: Address,
100    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
101    pub state_root: H256,
102    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
103    pub transactions_root: H256,
104    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
105    pub receipts_root: H256,
106    #[rkyv(with=crate::rkyv_utils::BloomWrapper)]
107    pub logs_bloom: Bloom,
108    #[serde(default)]
109    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
110    pub difficulty: U256,
111    #[serde(with = "crate::serde_utils::u64::hex_str")]
112    pub number: BlockNumber,
113    #[serde(with = "crate::serde_utils::u64::hex_str")]
114    pub gas_limit: u64,
115    #[serde(with = "crate::serde_utils::u64::hex_str")]
116    pub gas_used: u64,
117    #[serde(with = "crate::serde_utils::u64::hex_str")]
118    pub timestamp: u64,
119    #[serde(with = "crate::serde_utils::bytes")]
120    #[rkyv(with= crate::rkyv_utils::BytesWrapper)]
121    pub extra_data: Bytes,
122    #[serde(rename = "mixHash")]
123    #[rkyv(with=crate::rkyv_utils::H256Wrapper)]
124    pub prev_randao: H256,
125    #[serde(with = "crate::serde_utils::u64::hex_str_padding")]
126    pub nonce: u64,
127    #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
128    pub base_fee_per_gas: Option<u64>,
129    #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
130    pub withdrawals_root: Option<H256>,
131    #[serde(
132        skip_serializing_if = "Option::is_none",
133        with = "crate::serde_utils::u64::hex_str_opt",
134        default = "Option::default"
135    )]
136    pub blob_gas_used: Option<u64>,
137    #[serde(
138        skip_serializing_if = "Option::is_none",
139        with = "crate::serde_utils::u64::hex_str_opt",
140        default = "Option::default"
141    )]
142    pub excess_blob_gas: Option<u64>,
143    #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
144    pub parent_beacon_block_root: Option<H256>,
145    #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
146    #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
147    pub requests_hash: Option<H256>,
148    // Amsterdam fork fields (EIP-7928)
149    #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
150    #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
151    pub block_access_list_hash: Option<H256>,
152    #[serde(
153        skip_serializing_if = "Option::is_none",
154        with = "crate::serde_utils::u64::hex_str_opt",
155        default = "Option::default"
156    )]
157    pub slot_number: Option<u64>,
158}
159
160// Needs a explicit impl due to the hash OnceLock.
161impl PartialEq for BlockHeader {
162    fn eq(&self, other: &Self) -> bool {
163        let BlockHeader {
164            hash: _,
165            parent_hash,
166            ommers_hash,
167            coinbase,
168            state_root,
169            transactions_root,
170            receipts_root,
171            logs_bloom,
172            difficulty,
173            number,
174            gas_limit,
175            gas_used,
176            timestamp,
177            extra_data,
178            prev_randao,
179            nonce,
180            base_fee_per_gas,
181            withdrawals_root,
182            blob_gas_used,
183            excess_blob_gas,
184            parent_beacon_block_root,
185            requests_hash,
186            block_access_list_hash,
187            slot_number,
188        } = self;
189
190        parent_hash == &other.parent_hash
191            && number == &other.number
192            && timestamp == &other.timestamp
193            && nonce == &other.nonce
194            && gas_used == &other.gas_used
195            && gas_limit == &other.gas_limit
196            && base_fee_per_gas == &other.base_fee_per_gas
197            && blob_gas_used == &other.blob_gas_used
198            && excess_blob_gas == &other.excess_blob_gas
199            && parent_beacon_block_root == &other.parent_beacon_block_root
200            && prev_randao == &other.prev_randao
201            && coinbase == &other.coinbase
202            && state_root == &other.state_root
203            && transactions_root == &other.transactions_root
204            && receipts_root == &other.receipts_root
205            && withdrawals_root == &other.withdrawals_root
206            && difficulty == &other.difficulty
207            && ommers_hash == &other.ommers_hash
208            && requests_hash == &other.requests_hash
209            && block_access_list_hash == &other.block_access_list_hash
210            && slot_number == &other.slot_number
211            && logs_bloom == &other.logs_bloom
212            && extra_data == &other.extra_data
213    }
214}
215
216impl RLPEncode for BlockHeader {
217    fn encode(&self, buf: &mut dyn bytes::BufMut) {
218        Encoder::new(buf)
219            .encode_field(&self.parent_hash)
220            .encode_field(&self.ommers_hash)
221            .encode_field(&self.coinbase)
222            .encode_field(&self.state_root)
223            .encode_field(&self.transactions_root)
224            .encode_field(&self.receipts_root)
225            .encode_field(&self.logs_bloom)
226            .encode_field(&self.difficulty)
227            .encode_field(&self.number)
228            .encode_field(&self.gas_limit)
229            .encode_field(&self.gas_used)
230            .encode_field(&self.timestamp)
231            .encode_field(&self.extra_data)
232            .encode_field(&self.prev_randao)
233            .encode_field(&self.nonce.to_be_bytes())
234            .encode_optional_field(&self.base_fee_per_gas)
235            .encode_optional_field(&self.withdrawals_root)
236            .encode_optional_field(&self.blob_gas_used)
237            .encode_optional_field(&self.excess_blob_gas)
238            .encode_optional_field(&self.parent_beacon_block_root)
239            .encode_optional_field(&self.requests_hash)
240            .encode_optional_field(&self.block_access_list_hash)
241            .encode_optional_field(&self.slot_number)
242            .finish();
243    }
244}
245
246impl RLPDecode for BlockHeader {
247    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
248        let decoder = Decoder::new(rlp)?;
249        let (parent_hash, decoder) = decoder.decode_field("parent_hash")?;
250        let (ommers_hash, decoder) = decoder.decode_field("ommers_hash")?;
251        let (coinbase, decoder) = decoder.decode_field("coinbase")?;
252        let (state_root, decoder) = decoder.decode_field("state_root")?;
253        let (transactions_root, decoder) = decoder.decode_field("transactions_root")?;
254        let (receipts_root, decoder) = decoder.decode_field("receipts_root")?;
255        let (logs_bloom, decoder) = decoder.decode_field("logs_bloom")?;
256        let (difficulty, decoder) = decoder.decode_field("difficulty")?;
257        let (number, decoder) = decoder.decode_field("number")?;
258        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
259        let (gas_used, decoder) = decoder.decode_field("gas_used")?;
260        let (timestamp, decoder) = decoder.decode_field("timestamp")?;
261        let (extra_data, decoder) = decoder.decode_field("extra_data")?;
262        let (prev_randao, decoder) = decoder.decode_field("prev_randao")?;
263        let (nonce, decoder) = decoder.decode_field("nonce")?;
264        let nonce = u64::from_be_bytes(nonce);
265        let (base_fee_per_gas, decoder) = decoder.decode_optional_field();
266        let (withdrawals_root, decoder) = decoder.decode_optional_field();
267        let (blob_gas_used, decoder) = decoder.decode_optional_field();
268        let (excess_blob_gas, decoder) = decoder.decode_optional_field();
269        let (parent_beacon_block_root, decoder) = decoder.decode_optional_field();
270        let (requests_hash, decoder) = decoder.decode_optional_field();
271        let (block_access_list_hash, decoder) = decoder.decode_optional_field();
272        let (slot_number, decoder) = decoder.decode_optional_field();
273
274        Ok((
275            BlockHeader {
276                hash: OnceCell::new(),
277                parent_hash,
278                ommers_hash,
279                coinbase,
280                state_root,
281                transactions_root,
282                receipts_root,
283                logs_bloom,
284                difficulty,
285                number,
286                gas_limit,
287                gas_used,
288                timestamp,
289                extra_data,
290                prev_randao,
291                nonce,
292                base_fee_per_gas,
293                withdrawals_root,
294                blob_gas_used,
295                excess_blob_gas,
296                parent_beacon_block_root,
297                requests_hash,
298                block_access_list_hash,
299                slot_number,
300            },
301            decoder.finish()?,
302        ))
303    }
304}
305
306// The body of a block on the chain
307#[derive(
308    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, RSerialize, RDeserialize, Archive,
309)]
310pub struct BlockBody {
311    pub transactions: Vec<Transaction>,
312    // TODO: ommers list is always empty, so we can remove it
313    #[serde(rename = "uncles")]
314    pub ommers: Vec<BlockHeader>,
315    pub withdrawals: Option<Vec<Withdrawal>>,
316}
317
318impl BlockBody {
319    pub const fn empty() -> Self {
320        Self {
321            transactions: Vec::new(),
322            ommers: Vec::new(),
323            withdrawals: Some(Vec::new()),
324        }
325    }
326
327    pub fn get_transactions_with_sender(
328        &self,
329        crypto: &dyn Crypto,
330    ) -> Result<Vec<(&Transaction, Address)>, CryptoError> {
331        // Recovering addresses is computationally expensive.
332        // Computing them in parallel greatly reduces execution time.
333        // In eip-8025 builds, use sequential iteration
334        #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
335        return self
336            .transactions
337            .par_iter()
338            .map(|tx| Ok((tx, tx.sender(crypto)?)))
339            .collect::<Result<Vec<(&Transaction, Address)>, CryptoError>>();
340
341        #[cfg(any(feature = "eip-8025", not(feature = "rayon")))]
342        self.transactions
343            .iter()
344            .map(|tx| Ok((tx, tx.sender(crypto)?)))
345            .collect::<Result<Vec<(&Transaction, Address)>, CryptoError>>()
346    }
347}
348
349pub fn compute_transactions_root(transactions: &[Transaction], crypto: &dyn Crypto) -> H256 {
350    let iter = transactions.iter().enumerate().map(|(idx, tx)| {
351        // Key: RLP(tx_index)
352        // Value: tx_type || RLP(tx)  if tx_type != 0
353        //                   RLP(tx)  else
354        (idx.encode_to_vec(), tx.encode_canonical_to_vec())
355    });
356    Trie::compute_hash_from_unsorted_iter(iter, crypto)
357}
358
359pub fn compute_receipts_root(receipts: &[Receipt], crypto: &dyn Crypto) -> H256 {
360    let iter = receipts
361        .iter()
362        .enumerate()
363        .map(|(idx, receipt)| (idx.encode_to_vec(), receipt.encode_inner_with_bloom(crypto)));
364    Trie::compute_hash_from_unsorted_iter(iter, crypto)
365}
366
367/// Computes the receipts root and the aggregate header `logs_bloom` in a single pass,
368/// hashing each receipt's bloom only once (it feeds both the receipts trie and the
369/// OR-ed header bloom). Validation paths need both, so this avoids the duplicate
370/// `bloom_from_logs` keccak work — relevant in the zkVM guest where it is cycle-counted.
371pub fn compute_receipts_root_and_logs_bloom(
372    receipts: &[Receipt],
373    crypto: &dyn Crypto,
374) -> (H256, Bloom) {
375    let mut logs_bloom = Bloom::zero();
376    let iter = receipts.iter().enumerate().map(|(idx, receipt)| {
377        let bloom = crate::types::bloom_from_logs(&receipt.logs, crypto);
378        logs_bloom |= bloom;
379        (
380            idx.encode_to_vec(),
381            receipt.encode_inner_with_precomputed_bloom(bloom),
382        )
383    });
384    let receipts_root = Trie::compute_hash_from_unsorted_iter(iter, crypto);
385    (receipts_root, logs_bloom)
386}
387
388// See [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895)
389pub fn compute_withdrawals_root(withdrawals: &[Withdrawal], crypto: &dyn Crypto) -> H256 {
390    let iter = withdrawals
391        .iter()
392        .enumerate()
393        .map(|(idx, withdrawal)| (idx.encode_to_vec(), withdrawal.encode_to_vec()));
394    Trie::compute_hash_from_unsorted_iter(iter, crypto)
395}
396
397impl RLPEncode for BlockBody {
398    fn encode(&self, buf: &mut dyn bytes::BufMut) {
399        Encoder::new(buf)
400            .encode_field(&self.transactions)
401            .encode_field(&self.ommers)
402            .encode_optional_field(&self.withdrawals)
403            .finish();
404    }
405}
406
407impl RLPDecode for BlockBody {
408    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
409        let decoder = Decoder::new(rlp)?;
410        let (transactions, decoder) = decoder.decode_field("transactions")?;
411        let (ommers, decoder) = decoder.decode_field("ommers")?;
412        let (withdrawals, decoder) = decoder.decode_optional_field();
413        Ok((
414            BlockBody {
415                transactions,
416                ommers,
417                withdrawals,
418            },
419            decoder.finish()?,
420        ))
421    }
422}
423
424impl BlockHeader {
425    pub fn compute_block_hash(&self, crypto: &dyn Crypto) -> H256 {
426        let mut buf = vec![];
427        self.encode(&mut buf);
428        H256(crypto.keccak256(&buf))
429    }
430
431    pub fn hash(&self) -> H256 {
432        *self
433            .hash
434            .get_or_init(|| self.compute_block_hash(&NativeCrypto))
435    }
436}
437
438#[derive(
439    Clone, Debug, PartialEq, Eq, Deserialize, Serialize, RSerialize, RDeserialize, Archive,
440)]
441#[serde(rename_all = "camelCase")]
442pub struct Withdrawal {
443    #[serde(with = "crate::serde_utils::u64::hex_str")]
444    pub index: u64,
445    #[serde(with = "crate::serde_utils::u64::hex_str")]
446    pub validator_index: u64,
447    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
448    pub address: Address,
449    #[serde(with = "crate::serde_utils::u64::hex_str")]
450    pub amount: u64,
451}
452
453impl RLPEncode for Withdrawal {
454    fn encode(&self, buf: &mut dyn bytes::BufMut) {
455        Encoder::new(buf)
456            .encode_field(&self.index)
457            .encode_field(&self.validator_index)
458            .encode_field(&self.address)
459            .encode_field(&self.amount)
460            .finish();
461    }
462}
463
464impl RLPDecode for Withdrawal {
465    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
466        let decoder = Decoder::new(rlp)?;
467        let (index, decoder) = decoder.decode_field("index")?;
468        let (validator_index, decoder) = decoder.decode_field("validator_index")?;
469        let (address, decoder) = decoder.decode_field("address")?;
470        let (amount, decoder) = decoder.decode_field("amount")?;
471        Ok((
472            Withdrawal {
473                index,
474                validator_index,
475                address,
476                amount,
477            },
478            decoder.finish()?,
479        ))
480    }
481}
482
483// Checks that the gas_limit fits the gas bounds set by its parent block
484fn check_gas_limit(gas_limit: u64, parent_gas_limit: u64) -> bool {
485    let max_adjustment_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR;
486
487    gas_limit < parent_gas_limit + max_adjustment_delta
488        && gas_limit > parent_gas_limit - max_adjustment_delta
489        && gas_limit >= GAS_LIMIT_MINIMUM
490}
491
492/// Calculates the base fee per blob gas for the current block based on
493/// it's parent excess blob gas and the update fraction, which depends on the fork.
494pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> U256 {
495    if update_fraction == 0 {
496        return U256::zero();
497    }
498
499    fake_exponential(
500        U256::from(MIN_BASE_FEE_PER_BLOB_GAS),
501        U256::from(parent_excess_blob_gas),
502        update_fraction,
503    )
504    .unwrap_or_default()
505}
506
507/// Approximates factor * e ** (numerator / denominator) using Taylor expansion
508/// https://eips.ethereum.org/EIPS/eip-4844#helpers
509/// 400_000_000 numerator is the limit for this operation to work with U256,
510/// it will overflow with a larger numerator
511pub fn fake_exponential(
512    factor: U256,
513    numerator: U256,
514    denominator: u64,
515) -> Result<U256, FakeExponentialError> {
516    if denominator == 0 {
517        return Err(FakeExponentialError::DenominatorIsZero);
518    }
519
520    if numerator.is_zero() {
521        return Ok(factor);
522    }
523
524    let mut output: U256 = U256::zero();
525    let denominator_u256: U256 = denominator.into();
526
527    // Initial multiplication: factor * denominator
528    let mut numerator_accum = factor
529        .checked_mul(denominator_u256)
530        .ok_or(FakeExponentialError::CheckedMul)?;
531
532    let mut denominator_by_i = denominator_u256;
533
534    #[expect(
535        clippy::arithmetic_side_effects,
536        reason = "division can't overflow since denominator is not 0"
537    )]
538    {
539        while !numerator_accum.is_zero() {
540            // Safe addition to output
541            output = output
542                .checked_add(numerator_accum)
543                .ok_or(FakeExponentialError::CheckedAdd)?;
544
545            // Safe multiplication and division within loop
546            numerator_accum = numerator_accum
547                .checked_mul(numerator)
548                .ok_or(FakeExponentialError::CheckedMul)?
549                / denominator_by_i;
550
551            // denominator comes from a u64 value, will never overflow before other variables.
552            denominator_by_i += denominator_u256;
553        }
554
555        output
556            .checked_div(denominator.into())
557            .ok_or(FakeExponentialError::CheckedDiv)
558    }
559}
560
561#[derive(Debug, thiserror::Error, Serialize, Clone, PartialEq, Deserialize, Eq)]
562pub enum FakeExponentialError {
563    #[error("FakeExponentialError: Denominator cannot be zero.")]
564    DenominatorIsZero,
565    #[error("FakeExponentialError: Checked div failed is None.")]
566    CheckedDiv,
567    #[error("FakeExponentialError: Checked mul failed is None.")]
568    CheckedMul,
569    #[error("FakeExponentialError: Checked add failed is None.")]
570    CheckedAdd,
571}
572
573// Calculates the base fee for the current block based on its gas_limit and parent's gas and fee
574// Returns None if the block gas limit is not valid in relation to its parent's gas limit
575pub fn calculate_base_fee_per_gas(
576    block_gas_limit: u64,
577    parent_gas_limit: u64,
578    parent_gas_used: u64,
579    parent_base_fee_per_gas: u64,
580    elasticity_multiplier: u64,
581) -> Option<u64> {
582    // Check gas limit, if the check passes we can also rest assured that none of the
583    // following divisions will have zero as a divider
584    if !check_gas_limit(block_gas_limit, parent_gas_limit) {
585        return None;
586    }
587
588    let parent_gas_target = parent_gas_limit / elasticity_multiplier;
589
590    match parent_gas_used.cmp(&parent_gas_target) {
591        Ordering::Equal => Some(parent_base_fee_per_gas),
592        Ordering::Greater => {
593            let gas_used_delta = parent_gas_used - parent_gas_target;
594
595            let parent_fee_gas_delta =
596                u128::from(parent_base_fee_per_gas) * u128::from(gas_used_delta);
597            let target_fee_gas_delta = parent_fee_gas_delta / u128::from(parent_gas_target);
598
599            let base_fee_per_gas_delta =
600                max(target_fee_gas_delta / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1);
601
602            (u128::from(parent_base_fee_per_gas) + base_fee_per_gas_delta)
603                .try_into()
604                .ok()
605        }
606        Ordering::Less => {
607            let gas_used_delta = parent_gas_target - parent_gas_used;
608
609            let parent_fee_gas_delta =
610                u128::from(parent_base_fee_per_gas) * u128::from(gas_used_delta);
611            let target_fee_gas_delta = parent_fee_gas_delta / u128::from(parent_gas_target);
612
613            let base_fee_per_gas_delta = target_fee_gas_delta / BASE_FEE_MAX_CHANGE_DENOMINATOR;
614
615            (u128::from(parent_base_fee_per_gas) - base_fee_per_gas_delta)
616                .try_into()
617                .ok()
618        }
619    }
620}
621
622#[derive(Debug, thiserror::Error)]
623pub enum InvalidBlockHeaderError {
624    #[error("Gas used is greater than gas limit")]
625    GasUsedGreaterThanGasLimit,
626    #[error("Gas limit changed more than allowed from the parent")]
627    GasLimitTooFarFromParent,
628    #[error("Base fee per gas is incorrect")]
629    BaseFeePerGasIncorrect,
630    #[error("Timestamp is not greater than parent timestamp")]
631    TimestampNotGreaterThanParent,
632    #[error("Block number is not one greater than parent number")]
633    BlockNumberNotOneGreater,
634    #[error("Extra data is too long")]
635    ExtraDataTooLong,
636    #[error("Difficulty is not zero")]
637    DifficultyNotZero,
638    #[error("Nonce is not zero")]
639    NonceNotZero,
640    #[error("Ommers hash is not the default")]
641    OmmersHashNotDefault,
642    #[error("Parent hash is incorrect")]
643    ParentHashIncorrect,
644    // Cancun fork errors
645    #[error("Excess blob gas is not present")]
646    ExcessBlobGasNotPresent,
647    #[error("Blob gas used is not present")]
648    BlobGasUsedNotPresent,
649    #[error("Excess blob gas is incorrect")]
650    ExcessBlobGasIncorrect,
651    #[error("Parent beacon block root is not present")]
652    ParentBeaconBlockRootNotPresent,
653    #[error("Requests hash is not present")]
654    RequestsHashNotPresent,
655    // Other fork errors
656    #[error("Excess blob gas is present")]
657    ExcessBlobGasPresent,
658    #[error("Blob gas used is present")]
659    BlobGasUsedPresent,
660    #[error("Parent beacon block root is present")]
661    ParentBeaconBlockRootPresent,
662    #[error("Requests hash is present")]
663    RequestsHashPresent,
664    #[error("Block access list hash is not present")]
665    BlockAccessListHashNotPresent,
666    #[error("Block access list hash is present")]
667    BlockAccessListHashPresent,
668    #[error("Slot number is not present")]
669    SlotNumberNotPresent,
670    #[error("Slot number is present")]
671    SlotNumberPresent,
672}
673
674#[derive(Debug, thiserror::Error)]
675pub enum InvalidBlockBodyError {
676    #[error("Withdrawals root does not match")]
677    WithdrawalsRootNotMatch,
678    #[error("Transactions root does not match")]
679    TransactionsRootNotMatch,
680    #[error("Ommers is not empty")]
681    OmmersIsNotEmpty,
682}
683
684/// Validates that the header fields are correct in reference to the parent_header
685pub fn validate_block_header(
686    header: &BlockHeader,
687    parent_header: &BlockHeader,
688    elasticity_multiplier: u64,
689) -> Result<(), InvalidBlockHeaderError> {
690    if header.gas_used > header.gas_limit {
691        return Err(InvalidBlockHeaderError::GasUsedGreaterThanGasLimit);
692    }
693
694    let expected_base_fee_per_gas = if let Some(base_fee) = calculate_base_fee_per_gas(
695        header.gas_limit,
696        parent_header.gas_limit,
697        parent_header.gas_used,
698        parent_header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE),
699        elasticity_multiplier,
700    ) {
701        base_fee
702    } else {
703        return Err(InvalidBlockHeaderError::GasLimitTooFarFromParent);
704    };
705
706    if expected_base_fee_per_gas != header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE) {
707        return Err(InvalidBlockHeaderError::BaseFeePerGasIncorrect);
708    }
709
710    if header.timestamp <= parent_header.timestamp {
711        return Err(InvalidBlockHeaderError::TimestampNotGreaterThanParent);
712    }
713
714    if header.number != parent_header.number + 1 {
715        return Err(InvalidBlockHeaderError::BlockNumberNotOneGreater);
716    }
717
718    if header.extra_data.len() > 32 {
719        return Err(InvalidBlockHeaderError::ExtraDataTooLong);
720    }
721
722    if !header.difficulty.is_zero() {
723        return Err(InvalidBlockHeaderError::DifficultyNotZero);
724    }
725
726    if header.nonce != 0 {
727        return Err(InvalidBlockHeaderError::NonceNotZero);
728    }
729
730    if header.ommers_hash != *DEFAULT_OMMERS_HASH {
731        return Err(InvalidBlockHeaderError::OmmersHashNotDefault);
732    }
733
734    if header.parent_hash != parent_header.hash() {
735        return Err(InvalidBlockHeaderError::ParentHashIncorrect);
736    }
737
738    Ok(())
739}
740
741/// Validates that the body matches with the header
742pub fn validate_block_body(
743    block_header: &BlockHeader,
744    block_body: &BlockBody,
745    crypto: &dyn Crypto,
746) -> Result<(), InvalidBlockBodyError> {
747    // Validates that:
748    //  - Transactions root and withdrawals root matches with the header
749    //  - Ommers is empty -> https://eips.ethereum.org/EIPS/eip-3675
750    let computed_tx_root = compute_transactions_root(&block_body.transactions, crypto);
751
752    if block_header.transactions_root != computed_tx_root {
753        return Err(InvalidBlockBodyError::TransactionsRootNotMatch);
754    }
755
756    if !block_body.ommers.is_empty() {
757        return Err(InvalidBlockBodyError::OmmersIsNotEmpty);
758    }
759
760    match (block_header.withdrawals_root, &block_body.withdrawals) {
761        (Some(withdrawals_root), Some(withdrawals)) => {
762            let computed_withdrawals_root = compute_withdrawals_root(withdrawals, crypto);
763            if withdrawals_root != computed_withdrawals_root {
764                return Err(InvalidBlockBodyError::WithdrawalsRootNotMatch);
765            }
766        }
767        // Post-Shanghai, the withdrawals field must be present in the body even when
768        // empty. A body that omits it is malformed, regardless of the header's
769        // withdrawals_root — geth ("missing withdrawals in block body"), reth,
770        // nethermind, erigon and besu all reject this shape.
771        (None, None) => {}
772        _ => return Err(InvalidBlockBodyError::WithdrawalsRootNotMatch),
773    }
774
775    Ok(())
776}
777
778/// Normalizes a legacy post-Shanghai body shape produced by older ethrex versions,
779/// which omitted the `withdrawals` field while the header committed to the
780/// empty-withdrawals root. `validate_block_body` rejects that shape (reference
781/// clients treat it as malformed on the wire), but blocks already sitting in a
782/// node's store or in batch blobs published to L1 are immutable history. When the
783/// header's withdrawals_root is exactly the empty-list root, `Some(vec![])` is the
784/// provably-equivalent canonical body; any other root leaves the body untouched, and
785/// validation rejects it exactly as before.
786///
787/// Only call this on bodies read from trusted local history (store re-execution,
788/// blob reconstruction) — never on bodies received from the network.
789pub fn normalize_legacy_withdrawals(header: &BlockHeader, body: &mut BlockBody) {
790    if body.withdrawals.is_none() && header.withdrawals_root == Some(*EMPTY_WITHDRAWALS_HASH) {
791        tracing::debug!(
792            block = header.number,
793            "Normalizing legacy omitted-withdrawals block body from trusted history"
794        );
795        body.withdrawals = Some(Vec::new());
796    }
797}
798
799/// Validates that only the required field are present for a Prague block
800/// Also validates excess_blob_gas value against parent's header
801pub fn validate_prague_header_fields(
802    header: &BlockHeader,
803    parent_header: &BlockHeader,
804    chain_config: &ChainConfig,
805) -> Result<(), InvalidBlockHeaderError> {
806    if header.excess_blob_gas.is_none() {
807        return Err(InvalidBlockHeaderError::ExcessBlobGasNotPresent);
808    }
809    if header.blob_gas_used.is_none() {
810        return Err(InvalidBlockHeaderError::BlobGasUsedNotPresent);
811    }
812    validate_excess_blob_gas(header, parent_header, chain_config)?;
813
814    if header.parent_beacon_block_root.is_none() {
815        return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
816    }
817    if header.requests_hash.is_none() {
818        return Err(InvalidBlockHeaderError::RequestsHashNotPresent);
819    }
820    if chain_config.is_amsterdam_activated(header.timestamp) {
821        // EIP-7928: BAL hash and EIP-7843: slot_number are both mandatory
822        // trailing header fields on Amsterdam. A header omitting either is
823        // schema-invalid and must be rejected to match conformant clients.
824        if header.block_access_list_hash.is_none() {
825            return Err(InvalidBlockHeaderError::BlockAccessListHashNotPresent);
826        }
827        if header.slot_number.is_none() {
828            return Err(InvalidBlockHeaderError::SlotNumberNotPresent);
829        }
830    } else {
831        if header.block_access_list_hash.is_some() {
832            return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
833        }
834        if header.slot_number.is_some() {
835            return Err(InvalidBlockHeaderError::SlotNumberPresent);
836        }
837    }
838    Ok(())
839}
840
841/// Validates that only the required field are present for a Cancun block
842/// Also validates excess_blob_gas value against parent's header
843pub fn validate_cancun_header_fields(
844    header: &BlockHeader,
845    parent_header: &BlockHeader,
846    chain_config: &ChainConfig,
847) -> Result<(), InvalidBlockHeaderError> {
848    if header.excess_blob_gas.is_none() {
849        return Err(InvalidBlockHeaderError::ExcessBlobGasNotPresent);
850    }
851    if header.blob_gas_used.is_none() {
852        return Err(InvalidBlockHeaderError::BlobGasUsedNotPresent);
853    }
854    validate_excess_blob_gas(header, parent_header, chain_config)?;
855    if header.parent_beacon_block_root.is_none() {
856        return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
857    }
858    if header.requests_hash.is_some() {
859        return Err(InvalidBlockHeaderError::RequestsHashPresent);
860    }
861    if header.block_access_list_hash.is_some() {
862        return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
863    }
864    if header.slot_number.is_some() {
865        return Err(InvalidBlockHeaderError::SlotNumberPresent);
866    }
867    Ok(())
868}
869
870/// Validates that only the required field are present for a pre Cancun block
871/// Also validates excess_blob_gas value against parent's header
872pub fn validate_pre_cancun_header_fields(
873    header: &BlockHeader,
874) -> Result<(), InvalidBlockHeaderError> {
875    if header.excess_blob_gas.is_some() {
876        return Err(InvalidBlockHeaderError::ExcessBlobGasPresent);
877    }
878    if header.blob_gas_used.is_some() {
879        return Err(InvalidBlockHeaderError::BlobGasUsedPresent);
880    }
881    if header.parent_beacon_block_root.is_some() {
882        return Err(InvalidBlockHeaderError::ParentBeaconBlockRootPresent);
883    }
884    if header.requests_hash.is_some() {
885        return Err(InvalidBlockHeaderError::RequestsHashPresent);
886    }
887    if header.block_access_list_hash.is_some() {
888        return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
889    }
890    if header.slot_number.is_some() {
891        return Err(InvalidBlockHeaderError::SlotNumberPresent);
892    }
893    Ok(())
894}
895
896fn validate_excess_blob_gas(
897    header: &BlockHeader,
898    parent_header: &BlockHeader,
899    chain_config: &ChainConfig,
900) -> Result<(), InvalidBlockHeaderError> {
901    let expected_excess_blob_gas = chain_config
902        .get_fork_blob_schedule(header.timestamp)
903        .map(|schedule| {
904            calc_excess_blob_gas(parent_header, schedule, chain_config.fork(header.timestamp))
905        })
906        .unwrap_or_default();
907    if header
908        .excess_blob_gas
909        .is_none_or(|header_excess_blob_gas| header_excess_blob_gas != expected_excess_blob_gas)
910    {
911        return Err(InvalidBlockHeaderError::ExcessBlobGasIncorrect);
912    }
913    Ok(())
914}
915
916pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fork: Fork) -> u64 {
917    let parent_blob_gas_used = parent.blob_gas_used.unwrap_or_default();
918    let parent_base_fee_per_gas = parent.base_fee_per_gas.unwrap_or_default();
919    let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or_default();
920
921    let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
922    let target_blob_gas_per_block = (schedule.target * GAS_PER_BLOB) as u64;
923    if excess_blob_gas < target_blob_gas_per_block {
924        return 0;
925    }
926
927    if fork >= Fork::Osaka
928        && U256::from(BLOB_BASE_COST) * U256::from(parent_base_fee_per_gas)
929            > (U256::from(GAS_PER_BLOB))
930                * calculate_base_fee_per_blob_gas(
931                    parent_excess_blob_gas,
932                    schedule.base_fee_update_fraction,
933                )
934    {
935        return parent_excess_blob_gas
936            + parent_blob_gas_used * (schedule.max as u64 - schedule.target as u64)
937                / schedule.max as u64;
938    }
939
940    excess_blob_gas - target_blob_gas_per_block
941}
942
943#[cfg(test)]
944mod test {
945    use super::*;
946    use crate::constants::EMPTY_KECCAK_HASH;
947    use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER};
948    use ethereum_types::H160;
949    use hex_literal::hex;
950    use std::str::FromStr;
951
952    #[test]
953    fn test_compute_withdrawals_root() {
954        // Source: https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json
955        // "withdrawals" : [
956        //             {
957        //                 "address" : "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
958        //                 "amount" : "0x00",
959        //                 "index" : "0x00",
960        //                 "validatorIndex" : "0x00"
961        //             }
962        //         ]
963        // "withdrawalsRoot" : "0x48a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95"
964        let withdrawals = vec![Withdrawal {
965            index: 0x00,
966            validator_index: 0x00,
967            address: H160::from_slice(&hex!("c94f5374fce5edbc8e2a8697c15331677e6ebf0b")),
968            amount: 0x00_u64,
969        }];
970        let expected_root = H256::from_slice(&hex!(
971            "48a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95"
972        ));
973        let root = compute_withdrawals_root(&withdrawals, &ethrex_crypto::NativeCrypto);
974        assert_eq!(root, expected_root);
975    }
976
977    #[test]
978    fn test_validate_block_header() {
979        let parent_block = BlockHeader {
980            parent_hash: H256::from_str(
981                "0x0000000000000000000000000000000000000000000000000000000000000000",
982            )
983            .unwrap(),
984            ommers_hash: H256::from_str(
985                "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
986            )
987            .unwrap(),
988            coinbase: Address::zero(),
989            state_root: H256::from_str(
990                "0x590245a249decc317041b8dc7141cec0559c533efb82221e4e0a30a6456acf8b",
991            )
992            .unwrap(),
993            transactions_root: H256::from_str(
994                "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
995            )
996            .unwrap(),
997            receipts_root: H256::from_str(
998                "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
999            )
1000            .unwrap(),
1001            logs_bloom: Bloom::from([0; 256]),
1002            difficulty: U256::zero(),
1003            number: 0,
1004            gas_limit: 0x016345785d8a0000,
1005            gas_used: 0,
1006            timestamp: 0,
1007            extra_data: Bytes::new(),
1008            prev_randao: H256::zero(),
1009            nonce: 0x0000000000000000,
1010            base_fee_per_gas: Some(0x07),
1011            withdrawals_root: Some(
1012                H256::from_str(
1013                    "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1014                )
1015                .unwrap(),
1016            ),
1017            blob_gas_used: Some(0x00),
1018            excess_blob_gas: Some(0x00),
1019            parent_beacon_block_root: Some(H256::zero()),
1020            requests_hash: Some(*EMPTY_KECCAK_HASH),
1021            ..Default::default()
1022        };
1023        let block = BlockHeader {
1024            parent_hash: H256::from_str(
1025                "0x48e29e7357408113a4166e04e9f1aeff0680daa2b97ba93df6512a73ddf7a154",
1026            )
1027            .unwrap(),
1028            ommers_hash: H256::from_str(
1029                "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1030            )
1031            .unwrap(),
1032            coinbase: Address::from_str("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(),
1033            state_root: H256::from_str(
1034                "0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9",
1035            )
1036            .unwrap(),
1037            transactions_root: H256::from_str(
1038                "0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d",
1039            )
1040            .unwrap(),
1041            receipts_root: H256::from_str(
1042                "0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa",
1043            )
1044            .unwrap(),
1045            logs_bloom: Bloom::from([0; 256]),
1046            difficulty: U256::zero(),
1047            number: 1,
1048            gas_limit: 0x016345785d8a0000,
1049            gas_used: 0xa8de,
1050            timestamp: 0x03e8,
1051            extra_data: Bytes::new(),
1052            prev_randao: H256::zero(),
1053            nonce: 0x0000000000000000,
1054            base_fee_per_gas: Some(0x07),
1055            withdrawals_root: Some(
1056                H256::from_str(
1057                    "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1058                )
1059                .unwrap(),
1060            ),
1061            blob_gas_used: Some(0x00),
1062            excess_blob_gas: Some(0x00),
1063            parent_beacon_block_root: Some(H256::zero()),
1064            requests_hash: Some(*EMPTY_KECCAK_HASH),
1065            ..Default::default()
1066        };
1067        assert!(validate_block_header(&block, &parent_block, ELASTICITY_MULTIPLIER).is_ok());
1068        assert_eq!(parent_block.encode_to_vec().len(), parent_block.length());
1069        assert_eq!(block.encode_to_vec().len(), block.length());
1070    }
1071
1072    #[test]
1073    fn test_compute_transactions_root() {
1074        let encoded_transactions = [
1075            "0x01f8d68330182404842daf517a830186a08080b880c1597f3c842558e64df52c3e0f0973067577c030c0c6578dbb2eef63155a21106fd4426057527f296b2ecdfabc81e34ffc82e89dec20f6b7c41fa1969d3c3bc44262c86f08b5b76077527fb7ece918787c50c878052c30a8b1d4abc07331e6d14b8ded52bbc58a6e9992b76097527f0110937c38cc13b914f201fc09dc6f7a80c001a09930cb92b4a27dce971c697a8c47fa34c98d076abc7b36e1239d6abcfc7c8403a041b35118447fe77c38c0b3a92a2dd3ecba4a9e4b35cc6534cd787f56c0cf2e21",
1076            "0xf86e81fa843127403882f61894db8d964741c53e55df9c2d4e9414c6c96482874e870aa87bee538000808360306ca03aa421df67a101c45ff9cb06ce28f518a5d8d8dbb76a79361280071909650a27a05a447ff053c4ae601cfe81859b58d5603f2d0a73481c50f348089032feb0b073",
1077            "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6",
1078            "0x01f8ea8330182402842daf517a830186a094000000000000000000000000000000000000000080b880bdb30d976000604e557145600060a155d67fe7e473caf6e33cba341136268fc1189ba07837ef8a266570289ff53afc43436260c7527f333dfe837f4838f6053e5e46e4151aeec28f356ec39a2db9769f36ec92e3e3f660e7527f0b261608674300d4621eff679096a6ed786591aca69f2b22a3ea6949621daade610107527f3cc080a01f3f906540fb56b0576c51b3ffa86df213fd1f407378c9441cfdd9d5f3c1df3da035691b16c053b68ec74683ae020293cbc6a47ac773dc8defb96cb680c576e5a3",
1079        ];
1080        let transactions: Vec<Transaction> = encoded_transactions
1081            .iter()
1082            .map(|hex| {
1083                Transaction::decode_canonical(&hex::decode(hex.trim_start_matches("0x")).unwrap())
1084                    .unwrap()
1085            })
1086            .collect();
1087        let transactions_root =
1088            compute_transactions_root(&transactions, &ethrex_crypto::NativeCrypto);
1089        let expected_root = H256::from_slice(
1090            &hex::decode("adf0387d2303fe80aeca23bf6828c979b44d8a8fe4a1ba1d3511bc1567ca80de")
1091                .unwrap(),
1092        );
1093        assert_eq!(transactions_root, expected_root);
1094    }
1095
1096    #[test]
1097    // The values for this test were taken from sepolia testnet block number 6029872
1098    // Where a silent overflow within base fee calculations led to the wrong expected base fee
1099    fn test_calculate_base_fee_per_gas_big_numbers() {
1100        let expected_base_fee = Some(1317727380375);
1101        let block_gas_limit = 30000000;
1102        let parent_gas_limit = 30000000;
1103        let parent_gas_used = 1981764;
1104        let parent_base_fee_per_gas = 1478077008012;
1105        let calc_base_fee = calculate_base_fee_per_gas(
1106            block_gas_limit,
1107            parent_gas_limit,
1108            parent_gas_used,
1109            parent_base_fee_per_gas,
1110            ELASTICITY_MULTIPLIER,
1111        );
1112        assert_eq!(calc_base_fee, expected_base_fee)
1113    }
1114
1115    #[test]
1116    fn test_calc_blob_fee_post_osaka_bpo1() {
1117        let parent = BlockHeader {
1118            excess_blob_gas: Some(5149252),
1119            blob_gas_used: Some(1310720),
1120            base_fee_per_gas: Some(30),
1121            ..Default::default()
1122        };
1123        let schedule = ForkBlobSchedule {
1124            target: 9,
1125            max: 14,
1126            base_fee_update_fraction: 8832827,
1127        };
1128        let fork = Fork::Osaka;
1129
1130        let res = calc_excess_blob_gas(&parent, schedule, fork);
1131        assert_eq!(res, 5617366)
1132    }
1133
1134    #[test]
1135    fn test_calc_blob_fee_post_osaka_bpo3() {
1136        let parent = BlockHeader {
1137            excess_blob_gas: Some(19251039),
1138            blob_gas_used: Some(2490368),
1139            base_fee_per_gas: Some(50),
1140            ..Default::default()
1141        };
1142        let schedule = ForkBlobSchedule {
1143            target: 21,
1144            max: 32,
1145            base_fee_update_fraction: 20609697,
1146        };
1147        let fork = Fork::Osaka;
1148        let res = calc_excess_blob_gas(&parent, schedule, fork);
1149        assert_eq!(res, 20107103)
1150    }
1151
1152    #[test]
1153    fn test_calc_blob_fee_post_osaka_bpo1_ef() {
1154        let parent = BlockHeader {
1155            excess_blob_gas: Some(0x360000),
1156            blob_gas_used: Some(0),
1157            base_fee_per_gas: Some(0x11),
1158            ..Default::default()
1159        };
1160        let schedule = ForkBlobSchedule {
1161            target: 9,
1162            max: 14,
1163            base_fee_update_fraction: 0x86c73b,
1164        };
1165        let fork = Fork::Osaka;
1166
1167        let res = calc_excess_blob_gas(&parent, schedule, fork);
1168        assert_eq!(res, 3538944)
1169    }
1170
1171    #[test]
1172    fn test_fake_exponential_overflow() {
1173        // With u64 this overflows
1174        assert!(fake_exponential(57532635.into(), 3145728.into(), 3338477).is_ok());
1175    }
1176
1177    #[test]
1178    fn test_fake_exponential_bounds_overflow() {
1179        // Making sure the limit we state in the documentation of 400_000_000 works
1180        let thing = fake_exponential(
1181            MIN_BASE_FEE_PER_BLOB_GAS.into(),
1182            400_000_000.into(),
1183            BLOB_BASE_FEE_UPDATE_FRACTION,
1184        );
1185        // With u64 this overflows
1186        assert!(thing.is_ok());
1187    }
1188
1189    #[test]
1190    fn test_validate_block_body_rejects_missing_withdrawals_field() {
1191        use crate::constants::EMPTY_WITHDRAWALS_HASH;
1192
1193        let crypto = NativeCrypto;
1194        let header = BlockHeader {
1195            transactions_root: compute_transactions_root(&[], &crypto),
1196            withdrawals_root: Some(*EMPTY_WITHDRAWALS_HASH),
1197            ..Default::default()
1198        };
1199
1200        // A post-Shanghai body that OMITS the withdrawals field is malformed and
1201        // must be rejected even when the header commits to the empty-withdrawals
1202        // root (geth: "missing withdrawals in block body"; reth, nethermind,
1203        // erigon and besu reject this shape as well).
1204        let body_missing = BlockBody {
1205            withdrawals: None,
1206            ..BlockBody::empty()
1207        };
1208        assert!(matches!(
1209            validate_block_body(&header, &body_missing, &crypto),
1210            Err(InvalidBlockBodyError::WithdrawalsRootNotMatch)
1211        ));
1212
1213        // An explicit empty withdrawals list is valid for the same header.
1214        assert!(validate_block_body(&header, &BlockBody::empty(), &crypto).is_ok());
1215
1216        // A withdrawals field without a header root (pre-Shanghai shape) is rejected.
1217        let pre_shanghai_header = BlockHeader {
1218            transactions_root: compute_transactions_root(&[], &crypto),
1219            ..Default::default()
1220        };
1221        assert!(matches!(
1222            validate_block_body(&pre_shanghai_header, &BlockBody::empty(), &crypto),
1223            Err(InvalidBlockBodyError::WithdrawalsRootNotMatch)
1224        ));
1225        // Neither field present is valid.
1226        let body_no_withdrawals = BlockBody {
1227            withdrawals: None,
1228            ..BlockBody::empty()
1229        };
1230        assert!(validate_block_body(&pre_shanghai_header, &body_no_withdrawals, &crypto).is_ok());
1231    }
1232
1233    #[test]
1234    fn test_normalize_legacy_withdrawals() {
1235        use crate::constants::EMPTY_WITHDRAWALS_HASH;
1236
1237        let crypto = NativeCrypto;
1238
1239        // Legacy shape from trusted history: omitted field + empty-withdrawals
1240        // header root normalizes to the canonical Some(vec![]), and the
1241        // normalized body passes validation.
1242        let header = BlockHeader {
1243            transactions_root: compute_transactions_root(&[], &crypto),
1244            withdrawals_root: Some(*EMPTY_WITHDRAWALS_HASH),
1245            ..Default::default()
1246        };
1247        let mut body = BlockBody {
1248            withdrawals: None,
1249            ..BlockBody::empty()
1250        };
1251        normalize_legacy_withdrawals(&header, &mut body);
1252        assert_eq!(body.withdrawals, Some(Vec::new()));
1253        assert!(validate_block_body(&header, &body, &crypto).is_ok());
1254
1255        // An omitted field under a NON-empty withdrawals root is not the legacy
1256        // shape: the body stays untouched and validation still rejects it.
1257        let bad_header = BlockHeader {
1258            transactions_root: compute_transactions_root(&[], &crypto),
1259            withdrawals_root: Some(H256::repeat_byte(0xAB)),
1260            ..Default::default()
1261        };
1262        let mut bad_body = BlockBody {
1263            withdrawals: None,
1264            ..BlockBody::empty()
1265        };
1266        normalize_legacy_withdrawals(&bad_header, &mut bad_body);
1267        assert_eq!(bad_body.withdrawals, None);
1268        assert!(validate_block_body(&bad_header, &bad_body, &crypto).is_err());
1269
1270        // Pre-Shanghai shape (no header root, no field) stays untouched.
1271        let pre_shanghai_header = BlockHeader {
1272            transactions_root: compute_transactions_root(&[], &crypto),
1273            ..Default::default()
1274        };
1275        let mut pre_shanghai_body = BlockBody {
1276            withdrawals: None,
1277            ..BlockBody::empty()
1278        };
1279        normalize_legacy_withdrawals(&pre_shanghai_header, &mut pre_shanghai_body);
1280        assert_eq!(pre_shanghai_body.withdrawals, None);
1281
1282        // An already-canonical body is left as-is.
1283        let mut canonical = BlockBody::empty();
1284        let before = canonical.withdrawals.clone();
1285        normalize_legacy_withdrawals(&header, &mut canonical);
1286        assert_eq!(canonical.withdrawals, before);
1287    }
1288}