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#[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, #[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 #[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
160impl 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#[derive(
308 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, RSerialize, RDeserialize, Archive,
309)]
310pub struct BlockBody {
311 pub transactions: Vec<Transaction>,
312 #[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 #[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 (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
367pub 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
388pub 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
483fn 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
492pub 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
507pub 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 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 output = output
542 .checked_add(numerator_accum)
543 .ok_or(FakeExponentialError::CheckedAdd)?;
544
545 numerator_accum = numerator_accum
547 .checked_mul(numerator)
548 .ok_or(FakeExponentialError::CheckedMul)?
549 / denominator_by_i;
550
551 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
573pub 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 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 #[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 #[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
684pub 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
741pub fn validate_block_body(
743 block_header: &BlockHeader,
744 block_body: &BlockBody,
745 crypto: &dyn Crypto,
746) -> Result<(), InvalidBlockBodyError> {
747 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 (None, None) => {}
772 _ => return Err(InvalidBlockBodyError::WithdrawalsRootNotMatch),
773 }
774
775 Ok(())
776}
777
778pub 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
799pub 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 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
841pub 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
870pub 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 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, ðrex_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, ðrex_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 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 assert!(fake_exponential(57532635.into(), 3145728.into(), 3338477).is_ok());
1175 }
1176
1177 #[test]
1178 fn test_fake_exponential_bounds_overflow() {
1179 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 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 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 assert!(validate_block_body(&header, &BlockBody::empty(), &crypto).is_ok());
1215
1216 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 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 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 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 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 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}