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(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 = "zisk", target_arch = "riscv64"))]
34use super::unsync_cell::OnceCell;
35#[cfg(not(all(feature = "zisk", 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 #[serde(
160 skip_serializing_if = "Option::is_none",
161 with = "crate::serde_utils::u64::hex_str_opt",
162 default = "Option::default"
163 )]
164 pub burned_fees: Option<u64>,
165}
166
167impl PartialEq for BlockHeader {
169 fn eq(&self, other: &Self) -> bool {
170 let BlockHeader {
171 hash: _,
172 parent_hash,
173 ommers_hash,
174 coinbase,
175 state_root,
176 transactions_root,
177 receipts_root,
178 logs_bloom,
179 difficulty,
180 number,
181 gas_limit,
182 gas_used,
183 timestamp,
184 extra_data,
185 prev_randao,
186 nonce,
187 base_fee_per_gas,
188 withdrawals_root,
189 blob_gas_used,
190 excess_blob_gas,
191 parent_beacon_block_root,
192 requests_hash,
193 block_access_list_hash,
194 slot_number,
195 burned_fees,
196 } = self;
197
198 parent_hash == &other.parent_hash
199 && number == &other.number
200 && timestamp == &other.timestamp
201 && nonce == &other.nonce
202 && gas_used == &other.gas_used
203 && gas_limit == &other.gas_limit
204 && base_fee_per_gas == &other.base_fee_per_gas
205 && blob_gas_used == &other.blob_gas_used
206 && excess_blob_gas == &other.excess_blob_gas
207 && parent_beacon_block_root == &other.parent_beacon_block_root
208 && prev_randao == &other.prev_randao
209 && coinbase == &other.coinbase
210 && state_root == &other.state_root
211 && transactions_root == &other.transactions_root
212 && receipts_root == &other.receipts_root
213 && withdrawals_root == &other.withdrawals_root
214 && difficulty == &other.difficulty
215 && ommers_hash == &other.ommers_hash
216 && requests_hash == &other.requests_hash
217 && block_access_list_hash == &other.block_access_list_hash
218 && slot_number == &other.slot_number
219 && burned_fees == &other.burned_fees
220 && logs_bloom == &other.logs_bloom
221 && extra_data == &other.extra_data
222 }
223}
224
225impl RLPEncode for BlockHeader {
226 fn encode(&self, buf: &mut dyn bytes::BufMut) {
227 Encoder::new(buf)
228 .encode_field(&self.parent_hash)
229 .encode_field(&self.ommers_hash)
230 .encode_field(&self.coinbase)
231 .encode_field(&self.state_root)
232 .encode_field(&self.transactions_root)
233 .encode_field(&self.receipts_root)
234 .encode_field(&self.logs_bloom)
235 .encode_field(&self.difficulty)
236 .encode_field(&self.number)
237 .encode_field(&self.gas_limit)
238 .encode_field(&self.gas_used)
239 .encode_field(&self.timestamp)
240 .encode_field(&self.extra_data)
241 .encode_field(&self.prev_randao)
242 .encode_field(&self.nonce.to_be_bytes())
243 .encode_optional_field(&self.base_fee_per_gas)
244 .encode_optional_field(&self.withdrawals_root)
245 .encode_optional_field(&self.blob_gas_used)
246 .encode_optional_field(&self.excess_blob_gas)
247 .encode_optional_field(&self.parent_beacon_block_root)
248 .encode_optional_field(&self.requests_hash)
249 .encode_optional_field(&self.block_access_list_hash)
250 .encode_optional_field(&self.slot_number)
251 .encode_optional_field(&self.burned_fees)
252 .finish();
253 }
254}
255
256impl RLPDecode for BlockHeader {
257 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
258 let decoder = Decoder::new(rlp)?;
259 let (parent_hash, decoder) = decoder.decode_field("parent_hash")?;
260 let (ommers_hash, decoder) = decoder.decode_field("ommers_hash")?;
261 let (coinbase, decoder) = decoder.decode_field("coinbase")?;
262 let (state_root, decoder) = decoder.decode_field("state_root")?;
263 let (transactions_root, decoder) = decoder.decode_field("transactions_root")?;
264 let (receipts_root, decoder) = decoder.decode_field("receipts_root")?;
265 let (logs_bloom, decoder) = decoder.decode_field("logs_bloom")?;
266 let (difficulty, decoder) = decoder.decode_field("difficulty")?;
267 let (number, decoder) = decoder.decode_field("number")?;
268 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
269 let (gas_used, decoder) = decoder.decode_field("gas_used")?;
270 let (timestamp, decoder) = decoder.decode_field("timestamp")?;
271 let (extra_data, decoder) = decoder.decode_field("extra_data")?;
272 let (prev_randao, decoder) = decoder.decode_field("prev_randao")?;
273 let (nonce, decoder) = decoder.decode_field("nonce")?;
274 let nonce = u64::from_be_bytes(nonce);
275 let (base_fee_per_gas, decoder) = decoder.decode_optional_field();
276 let (withdrawals_root, decoder) = decoder.decode_optional_field();
277 let (blob_gas_used, decoder) = decoder.decode_optional_field();
278 let (excess_blob_gas, decoder) = decoder.decode_optional_field();
279 let (parent_beacon_block_root, decoder) = decoder.decode_optional_field();
280 let (requests_hash, decoder) = decoder.decode_optional_field();
281 let (block_access_list_hash, decoder) = decoder.decode_optional_field();
282 let (slot_number, decoder) = decoder.decode_optional_field();
283 let (burned_fees, decoder) = decoder.decode_optional_field();
284
285 Ok((
286 BlockHeader {
287 hash: OnceCell::new(),
288 parent_hash,
289 ommers_hash,
290 coinbase,
291 state_root,
292 transactions_root,
293 receipts_root,
294 logs_bloom,
295 difficulty,
296 number,
297 gas_limit,
298 gas_used,
299 timestamp,
300 extra_data,
301 prev_randao,
302 nonce,
303 base_fee_per_gas,
304 withdrawals_root,
305 blob_gas_used,
306 excess_blob_gas,
307 parent_beacon_block_root,
308 requests_hash,
309 block_access_list_hash,
310 slot_number,
311 burned_fees,
312 },
313 decoder.finish()?,
314 ))
315 }
316}
317
318#[derive(
320 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, RSerialize, RDeserialize, Archive,
321)]
322pub struct BlockBody {
323 pub transactions: Vec<Transaction>,
324 #[serde(rename = "uncles")]
326 pub ommers: Vec<BlockHeader>,
327 pub withdrawals: Option<Vec<Withdrawal>>,
328}
329
330impl BlockBody {
331 pub const fn empty() -> Self {
332 Self {
333 transactions: Vec::new(),
334 ommers: Vec::new(),
335 withdrawals: Some(Vec::new()),
336 }
337 }
338
339 pub fn get_transactions_with_sender(
340 &self,
341 crypto: &dyn Crypto,
342 ) -> Result<Vec<(&Transaction, Address)>, CryptoError> {
343 #[cfg(feature = "rayon")]
347 return self
348 .transactions
349 .par_iter()
350 .map(|tx| Ok((tx, tx.sender(crypto)?)))
351 .collect::<Result<Vec<(&Transaction, Address)>, CryptoError>>();
352
353 #[cfg(not(feature = "rayon"))]
354 self.transactions
355 .iter()
356 .map(|tx| Ok((tx, tx.sender(crypto)?)))
357 .collect::<Result<Vec<(&Transaction, Address)>, CryptoError>>()
358 }
359}
360
361pub fn compute_transactions_root(transactions: &[Transaction], crypto: &dyn Crypto) -> H256 {
362 let iter = transactions.iter().enumerate().map(|(idx, tx)| {
363 (idx.encode_to_vec(), tx.encode_canonical_to_vec())
367 });
368 Trie::compute_hash_from_unsorted_iter(iter, crypto)
369}
370
371pub fn compute_receipts_root(receipts: &[Receipt], crypto: &dyn Crypto) -> H256 {
372 let iter = receipts
373 .iter()
374 .enumerate()
375 .map(|(idx, receipt)| (idx.encode_to_vec(), receipt.encode_inner_with_bloom(crypto)));
376 Trie::compute_hash_from_unsorted_iter(iter, crypto)
377}
378
379pub fn compute_receipts_root_and_logs_bloom(
384 receipts: &[Receipt],
385 crypto: &dyn Crypto,
386) -> (H256, Bloom) {
387 let mut logs_bloom = Bloom::zero();
388 let iter = receipts.iter().enumerate().map(|(idx, receipt)| {
389 let bloom = crate::types::bloom_from_logs(&receipt.logs, crypto);
390 logs_bloom |= bloom;
391 (
392 idx.encode_to_vec(),
393 receipt.encode_inner_with_precomputed_bloom(bloom),
394 )
395 });
396 let receipts_root = Trie::compute_hash_from_unsorted_iter(iter, crypto);
397 (receipts_root, logs_bloom)
398}
399
400pub fn compute_withdrawals_root(withdrawals: &[Withdrawal], crypto: &dyn Crypto) -> H256 {
402 let iter = withdrawals
403 .iter()
404 .enumerate()
405 .map(|(idx, withdrawal)| (idx.encode_to_vec(), withdrawal.encode_to_vec()));
406 Trie::compute_hash_from_unsorted_iter(iter, crypto)
407}
408
409impl RLPEncode for BlockBody {
410 fn encode(&self, buf: &mut dyn bytes::BufMut) {
411 Encoder::new(buf)
412 .encode_field(&self.transactions)
413 .encode_field(&self.ommers)
414 .encode_optional_field(&self.withdrawals)
415 .finish();
416 }
417}
418
419impl RLPDecode for BlockBody {
420 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
421 let decoder = Decoder::new(rlp)?;
422 let (transactions, decoder) = decoder.decode_field("transactions")?;
423 let (ommers, decoder) = decoder.decode_field("ommers")?;
424 let (withdrawals, decoder) = decoder.decode_optional_field();
425 Ok((
426 BlockBody {
427 transactions,
428 ommers,
429 withdrawals,
430 },
431 decoder.finish()?,
432 ))
433 }
434}
435
436impl BlockHeader {
437 pub fn compute_block_hash(&self, crypto: &dyn Crypto) -> H256 {
438 let mut buf = vec![];
439 self.encode(&mut buf);
440 H256(crypto.keccak256(&buf))
441 }
442
443 pub fn hash(&self) -> H256 {
444 *self
445 .hash
446 .get_or_init(|| self.compute_block_hash(&NativeCrypto))
447 }
448
449 pub fn into_with_burned_fees(mut self, burned_fees: Option<u64>) -> Self {
457 self.burned_fees = burned_fees;
458 let _ = self.hash.take(); self
460 }
461}
462
463#[derive(
464 Clone, Debug, PartialEq, Eq, Deserialize, Serialize, RSerialize, RDeserialize, Archive,
465)]
466#[serde(rename_all = "camelCase")]
467pub struct Withdrawal {
468 #[serde(with = "crate::serde_utils::u64::hex_str")]
469 pub index: u64,
470 #[serde(with = "crate::serde_utils::u64::hex_str")]
471 pub validator_index: u64,
472 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
473 pub address: Address,
474 #[serde(with = "crate::serde_utils::u64::hex_str")]
475 pub amount: u64,
476}
477
478impl RLPEncode for Withdrawal {
479 fn encode(&self, buf: &mut dyn bytes::BufMut) {
480 Encoder::new(buf)
481 .encode_field(&self.index)
482 .encode_field(&self.validator_index)
483 .encode_field(&self.address)
484 .encode_field(&self.amount)
485 .finish();
486 }
487}
488
489impl RLPDecode for Withdrawal {
490 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
491 let decoder = Decoder::new(rlp)?;
492 let (index, decoder) = decoder.decode_field("index")?;
493 let (validator_index, decoder) = decoder.decode_field("validator_index")?;
494 let (address, decoder) = decoder.decode_field("address")?;
495 let (amount, decoder) = decoder.decode_field("amount")?;
496 Ok((
497 Withdrawal {
498 index,
499 validator_index,
500 address,
501 amount,
502 },
503 decoder.finish()?,
504 ))
505 }
506}
507
508fn check_gas_limit(gas_limit: u64, parent_gas_limit: u64) -> bool {
510 let max_adjustment_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR;
511
512 gas_limit < parent_gas_limit + max_adjustment_delta
513 && gas_limit > parent_gas_limit - max_adjustment_delta
514 && gas_limit >= GAS_LIMIT_MINIMUM
515}
516
517pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> U256 {
520 if update_fraction == 0 {
521 return U256::zero();
522 }
523
524 fake_exponential(
525 U256::from(MIN_BASE_FEE_PER_BLOB_GAS),
526 U256::from(parent_excess_blob_gas),
527 update_fraction,
528 )
529 .unwrap_or_default()
530}
531
532pub fn fake_exponential(
537 factor: U256,
538 numerator: U256,
539 denominator: u64,
540) -> Result<U256, FakeExponentialError> {
541 if denominator == 0 {
542 return Err(FakeExponentialError::DenominatorIsZero);
543 }
544
545 if numerator.is_zero() {
546 return Ok(factor);
547 }
548
549 let mut output: U256 = U256::zero();
550 let denominator_u256: U256 = denominator.into();
551
552 let mut numerator_accum = factor
554 .checked_mul(denominator_u256)
555 .ok_or(FakeExponentialError::CheckedMul)?;
556
557 let mut denominator_by_i = denominator_u256;
558
559 #[expect(
560 clippy::arithmetic_side_effects,
561 reason = "division can't overflow since denominator is not 0"
562 )]
563 {
564 while !numerator_accum.is_zero() {
565 output = output
567 .checked_add(numerator_accum)
568 .ok_or(FakeExponentialError::CheckedAdd)?;
569
570 numerator_accum = numerator_accum
572 .checked_mul(numerator)
573 .ok_or(FakeExponentialError::CheckedMul)?
574 / denominator_by_i;
575
576 denominator_by_i += denominator_u256;
578 }
579
580 output
581 .checked_div(denominator.into())
582 .ok_or(FakeExponentialError::CheckedDiv)
583 }
584}
585
586#[derive(Debug, thiserror::Error, Serialize, Clone, PartialEq, Deserialize, Eq)]
587pub enum FakeExponentialError {
588 #[error("FakeExponentialError: Denominator cannot be zero.")]
589 DenominatorIsZero,
590 #[error("FakeExponentialError: Checked div failed is None.")]
591 CheckedDiv,
592 #[error("FakeExponentialError: Checked mul failed is None.")]
593 CheckedMul,
594 #[error("FakeExponentialError: Checked add failed is None.")]
595 CheckedAdd,
596}
597
598pub fn calculate_base_fee_per_gas(
601 block_gas_limit: u64,
602 parent_gas_limit: u64,
603 parent_gas_used: u64,
604 parent_base_fee_per_gas: u64,
605 elasticity_multiplier: u64,
606) -> Option<u64> {
607 if !check_gas_limit(block_gas_limit, parent_gas_limit) {
610 return None;
611 }
612
613 let parent_gas_target = parent_gas_limit / elasticity_multiplier;
614
615 match parent_gas_used.cmp(&parent_gas_target) {
616 Ordering::Equal => Some(parent_base_fee_per_gas),
617 Ordering::Greater => {
618 let gas_used_delta = parent_gas_used - parent_gas_target;
619
620 let parent_fee_gas_delta =
621 u128::from(parent_base_fee_per_gas) * u128::from(gas_used_delta);
622 let target_fee_gas_delta = parent_fee_gas_delta / u128::from(parent_gas_target);
623
624 let base_fee_per_gas_delta =
625 max(target_fee_gas_delta / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1);
626
627 (u128::from(parent_base_fee_per_gas) + base_fee_per_gas_delta)
628 .try_into()
629 .ok()
630 }
631 Ordering::Less => {
632 let gas_used_delta = parent_gas_target - parent_gas_used;
633
634 let parent_fee_gas_delta =
635 u128::from(parent_base_fee_per_gas) * u128::from(gas_used_delta);
636 let target_fee_gas_delta = parent_fee_gas_delta / u128::from(parent_gas_target);
637
638 let base_fee_per_gas_delta = target_fee_gas_delta / BASE_FEE_MAX_CHANGE_DENOMINATOR;
639
640 (u128::from(parent_base_fee_per_gas) - base_fee_per_gas_delta)
641 .try_into()
642 .ok()
643 }
644 }
645}
646
647#[derive(Debug, thiserror::Error)]
648pub enum InvalidBlockHeaderError {
649 #[error("Gas used is greater than gas limit")]
650 GasUsedGreaterThanGasLimit,
651 #[error("Gas limit changed more than allowed from the parent")]
652 GasLimitTooFarFromParent,
653 #[error("Base fee per gas is incorrect")]
654 BaseFeePerGasIncorrect,
655 #[error("Timestamp is not greater than parent timestamp")]
656 TimestampNotGreaterThanParent,
657 #[error("Block number is not one greater than parent number")]
658 BlockNumberNotOneGreater,
659 #[error("Extra data is too long")]
660 ExtraDataTooLong,
661 #[error("Difficulty is not zero")]
662 DifficultyNotZero,
663 #[error("Nonce is not zero")]
664 NonceNotZero,
665 #[error("Ommers hash is not the default")]
666 OmmersHashNotDefault,
667 #[error("Parent hash is incorrect")]
668 ParentHashIncorrect,
669 #[error("Excess blob gas is not present")]
671 ExcessBlobGasNotPresent,
672 #[error("Blob gas used is not present")]
673 BlobGasUsedNotPresent,
674 #[error("Excess blob gas is incorrect")]
675 ExcessBlobGasIncorrect,
676 #[error("Parent beacon block root is not present")]
677 ParentBeaconBlockRootNotPresent,
678 #[error("Requests hash is not present")]
679 RequestsHashNotPresent,
680 #[error("Excess blob gas is present")]
682 ExcessBlobGasPresent,
683 #[error("Blob gas used is present")]
684 BlobGasUsedPresent,
685 #[error("Parent beacon block root is present")]
686 ParentBeaconBlockRootPresent,
687 #[error("Requests hash is present")]
688 RequestsHashPresent,
689 #[error("Block access list hash is not present")]
690 BlockAccessListHashNotPresent,
691 #[error("Block access list hash is present")]
692 BlockAccessListHashPresent,
693 #[error("Slot number is not present")]
694 SlotNumberNotPresent,
695 #[error("Slot number is present")]
696 SlotNumberPresent,
697}
698
699#[derive(Debug, thiserror::Error)]
700pub enum InvalidBlockBodyError {
701 #[error("Withdrawals root does not match")]
702 WithdrawalsRootNotMatch,
703 #[error("Transactions root does not match")]
704 TransactionsRootNotMatch,
705 #[error("Ommers is not empty")]
706 OmmersIsNotEmpty,
707}
708
709pub fn validate_block_header(
711 header: &BlockHeader,
712 parent_header: &BlockHeader,
713 elasticity_multiplier: u64,
714) -> Result<(), InvalidBlockHeaderError> {
715 if header.gas_used > header.gas_limit {
716 return Err(InvalidBlockHeaderError::GasUsedGreaterThanGasLimit);
717 }
718
719 let expected_base_fee_per_gas = if let Some(base_fee) = calculate_base_fee_per_gas(
720 header.gas_limit,
721 parent_header.gas_limit,
722 parent_header.gas_used,
723 parent_header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE),
724 elasticity_multiplier,
725 ) {
726 base_fee
727 } else {
728 return Err(InvalidBlockHeaderError::GasLimitTooFarFromParent);
729 };
730
731 if expected_base_fee_per_gas != header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE) {
732 return Err(InvalidBlockHeaderError::BaseFeePerGasIncorrect);
733 }
734
735 if header.timestamp <= parent_header.timestamp {
736 return Err(InvalidBlockHeaderError::TimestampNotGreaterThanParent);
737 }
738
739 if header.number != parent_header.number + 1 {
740 return Err(InvalidBlockHeaderError::BlockNumberNotOneGreater);
741 }
742
743 if header.extra_data.len() > 32 {
744 return Err(InvalidBlockHeaderError::ExtraDataTooLong);
745 }
746
747 if !header.difficulty.is_zero() {
748 return Err(InvalidBlockHeaderError::DifficultyNotZero);
749 }
750
751 if header.nonce != 0 {
752 return Err(InvalidBlockHeaderError::NonceNotZero);
753 }
754
755 if header.ommers_hash != *DEFAULT_OMMERS_HASH {
756 return Err(InvalidBlockHeaderError::OmmersHashNotDefault);
757 }
758
759 if header.parent_hash != parent_header.hash() {
760 return Err(InvalidBlockHeaderError::ParentHashIncorrect);
761 }
762
763 Ok(())
764}
765
766pub fn validate_block_body(
768 block_header: &BlockHeader,
769 block_body: &BlockBody,
770 crypto: &dyn Crypto,
771) -> Result<(), InvalidBlockBodyError> {
772 let computed_tx_root = compute_transactions_root(&block_body.transactions, crypto);
776
777 if block_header.transactions_root != computed_tx_root {
778 return Err(InvalidBlockBodyError::TransactionsRootNotMatch);
779 }
780
781 if !block_body.ommers.is_empty() {
782 return Err(InvalidBlockBodyError::OmmersIsNotEmpty);
783 }
784
785 match (block_header.withdrawals_root, &block_body.withdrawals) {
786 (Some(withdrawals_root), Some(withdrawals)) => {
787 let computed_withdrawals_root = compute_withdrawals_root(withdrawals, crypto);
788 if withdrawals_root != computed_withdrawals_root {
789 return Err(InvalidBlockBodyError::WithdrawalsRootNotMatch);
790 }
791 }
792 (None, None) => {}
797 _ => return Err(InvalidBlockBodyError::WithdrawalsRootNotMatch),
798 }
799
800 Ok(())
801}
802
803pub fn normalize_legacy_withdrawals(header: &BlockHeader, body: &mut BlockBody) {
815 if body.withdrawals.is_none() && header.withdrawals_root == Some(*EMPTY_WITHDRAWALS_HASH) {
816 tracing::debug!(
817 block = header.number,
818 "Normalizing legacy omitted-withdrawals block body from trusted history"
819 );
820 body.withdrawals = Some(Vec::new());
821 }
822}
823
824pub fn validate_prague_header_fields(
827 header: &BlockHeader,
828 parent_header: &BlockHeader,
829 chain_config: &ChainConfig,
830) -> Result<(), InvalidBlockHeaderError> {
831 if header.excess_blob_gas.is_none() {
832 return Err(InvalidBlockHeaderError::ExcessBlobGasNotPresent);
833 }
834 if header.blob_gas_used.is_none() {
835 return Err(InvalidBlockHeaderError::BlobGasUsedNotPresent);
836 }
837 validate_excess_blob_gas(header, parent_header, chain_config)?;
838
839 if header.parent_beacon_block_root.is_none() {
840 return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
841 }
842 if header.requests_hash.is_none() {
843 return Err(InvalidBlockHeaderError::RequestsHashNotPresent);
844 }
845 if chain_config.is_amsterdam_activated(header.timestamp) {
846 if header.block_access_list_hash.is_none() {
850 return Err(InvalidBlockHeaderError::BlockAccessListHashNotPresent);
851 }
852 if header.slot_number.is_none() {
853 return Err(InvalidBlockHeaderError::SlotNumberNotPresent);
854 }
855 } else {
856 if header.block_access_list_hash.is_some() {
857 return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
858 }
859 if header.slot_number.is_some() {
860 return Err(InvalidBlockHeaderError::SlotNumberPresent);
861 }
862 }
863 Ok(())
864}
865
866pub fn validate_cancun_header_fields(
869 header: &BlockHeader,
870 parent_header: &BlockHeader,
871 chain_config: &ChainConfig,
872) -> Result<(), InvalidBlockHeaderError> {
873 if header.excess_blob_gas.is_none() {
874 return Err(InvalidBlockHeaderError::ExcessBlobGasNotPresent);
875 }
876 if header.blob_gas_used.is_none() {
877 return Err(InvalidBlockHeaderError::BlobGasUsedNotPresent);
878 }
879 validate_excess_blob_gas(header, parent_header, chain_config)?;
880 if header.parent_beacon_block_root.is_none() {
881 return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
882 }
883 if header.requests_hash.is_some() {
884 return Err(InvalidBlockHeaderError::RequestsHashPresent);
885 }
886 if header.block_access_list_hash.is_some() {
887 return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
888 }
889 if header.slot_number.is_some() {
890 return Err(InvalidBlockHeaderError::SlotNumberPresent);
891 }
892 Ok(())
893}
894
895pub fn validate_pre_cancun_header_fields(
898 header: &BlockHeader,
899) -> Result<(), InvalidBlockHeaderError> {
900 if header.excess_blob_gas.is_some() {
901 return Err(InvalidBlockHeaderError::ExcessBlobGasPresent);
902 }
903 if header.blob_gas_used.is_some() {
904 return Err(InvalidBlockHeaderError::BlobGasUsedPresent);
905 }
906 if header.parent_beacon_block_root.is_some() {
907 return Err(InvalidBlockHeaderError::ParentBeaconBlockRootPresent);
908 }
909 if header.requests_hash.is_some() {
910 return Err(InvalidBlockHeaderError::RequestsHashPresent);
911 }
912 if header.block_access_list_hash.is_some() {
913 return Err(InvalidBlockHeaderError::BlockAccessListHashPresent);
914 }
915 if header.slot_number.is_some() {
916 return Err(InvalidBlockHeaderError::SlotNumberPresent);
917 }
918 Ok(())
919}
920
921fn validate_excess_blob_gas(
922 header: &BlockHeader,
923 parent_header: &BlockHeader,
924 chain_config: &ChainConfig,
925) -> Result<(), InvalidBlockHeaderError> {
926 let expected_excess_blob_gas = chain_config
927 .get_fork_blob_schedule(header.timestamp)
928 .map(|schedule| {
929 calc_excess_blob_gas(parent_header, schedule, chain_config.fork(header.timestamp))
930 })
931 .unwrap_or_default();
932 if header
933 .excess_blob_gas
934 .is_none_or(|header_excess_blob_gas| header_excess_blob_gas != expected_excess_blob_gas)
935 {
936 return Err(InvalidBlockHeaderError::ExcessBlobGasIncorrect);
937 }
938 Ok(())
939}
940
941pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fork: Fork) -> u64 {
942 let parent_blob_gas_used = parent.blob_gas_used.unwrap_or_default();
943 let parent_base_fee_per_gas = parent.base_fee_per_gas.unwrap_or_default();
944 let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or_default();
945
946 let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
947 let target_blob_gas_per_block = (schedule.target * GAS_PER_BLOB) as u64;
948 if excess_blob_gas < target_blob_gas_per_block {
949 return 0;
950 }
951
952 if fork >= Fork::Osaka
953 && U256::from(BLOB_BASE_COST) * U256::from(parent_base_fee_per_gas)
954 > (U256::from(GAS_PER_BLOB))
955 * calculate_base_fee_per_blob_gas(
956 parent_excess_blob_gas,
957 schedule.base_fee_update_fraction,
958 )
959 {
960 return parent_excess_blob_gas
961 + parent_blob_gas_used * (schedule.max as u64 - schedule.target as u64)
962 / schedule.max as u64;
963 }
964
965 excess_blob_gas - target_blob_gas_per_block
966}
967
968#[cfg(test)]
969mod test {
970 use super::*;
971 use crate::constants::EMPTY_KECCAK_HASH;
972 use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER, INITIAL_BASE_FEE};
973 use ethereum_types::H160;
974 use hex_literal::hex;
975 use std::str::FromStr;
976
977 #[test]
978 fn test_compute_withdrawals_root() {
979 let withdrawals = vec![Withdrawal {
990 index: 0x00,
991 validator_index: 0x00,
992 address: H160::from_slice(&hex!("c94f5374fce5edbc8e2a8697c15331677e6ebf0b")),
993 amount: 0x00_u64,
994 }];
995 let expected_root = H256::from_slice(&hex!(
996 "48a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95"
997 ));
998 let root = compute_withdrawals_root(&withdrawals, ðrex_crypto::NativeCrypto);
999 assert_eq!(root, expected_root);
1000 }
1001
1002 #[test]
1003 fn test_validate_block_header() {
1004 let parent_block = BlockHeader {
1005 parent_hash: H256::from_str(
1006 "0x0000000000000000000000000000000000000000000000000000000000000000",
1007 )
1008 .unwrap(),
1009 ommers_hash: H256::from_str(
1010 "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1011 )
1012 .unwrap(),
1013 coinbase: Address::zero(),
1014 state_root: H256::from_str(
1015 "0x590245a249decc317041b8dc7141cec0559c533efb82221e4e0a30a6456acf8b",
1016 )
1017 .unwrap(),
1018 transactions_root: H256::from_str(
1019 "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1020 )
1021 .unwrap(),
1022 receipts_root: H256::from_str(
1023 "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1024 )
1025 .unwrap(),
1026 logs_bloom: Bloom::from([0; 256]),
1027 difficulty: U256::zero(),
1028 number: 0,
1029 gas_limit: 0x016345785d8a0000,
1030 gas_used: 0,
1031 timestamp: 0,
1032 extra_data: Bytes::new(),
1033 prev_randao: H256::zero(),
1034 nonce: 0x0000000000000000,
1035 base_fee_per_gas: Some(0x07),
1036 withdrawals_root: Some(
1037 H256::from_str(
1038 "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1039 )
1040 .unwrap(),
1041 ),
1042 blob_gas_used: Some(0x00),
1043 excess_blob_gas: Some(0x00),
1044 parent_beacon_block_root: Some(H256::zero()),
1045 requests_hash: Some(*EMPTY_KECCAK_HASH),
1046 ..Default::default()
1047 };
1048 let block = BlockHeader {
1049 parent_hash: H256::from_str(
1050 "0x48e29e7357408113a4166e04e9f1aeff0680daa2b97ba93df6512a73ddf7a154",
1051 )
1052 .unwrap(),
1053 ommers_hash: H256::from_str(
1054 "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1055 )
1056 .unwrap(),
1057 coinbase: Address::from_str("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(),
1058 state_root: H256::from_str(
1059 "0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9",
1060 )
1061 .unwrap(),
1062 transactions_root: H256::from_str(
1063 "0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d",
1064 )
1065 .unwrap(),
1066 receipts_root: H256::from_str(
1067 "0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa",
1068 )
1069 .unwrap(),
1070 logs_bloom: Bloom::from([0; 256]),
1071 difficulty: U256::zero(),
1072 number: 1,
1073 gas_limit: 0x016345785d8a0000,
1074 gas_used: 0xa8de,
1075 timestamp: 0x03e8,
1076 extra_data: Bytes::new(),
1077 prev_randao: H256::zero(),
1078 nonce: 0x0000000000000000,
1079 base_fee_per_gas: Some(0x07),
1080 withdrawals_root: Some(
1081 H256::from_str(
1082 "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1083 )
1084 .unwrap(),
1085 ),
1086 blob_gas_used: Some(0x00),
1087 excess_blob_gas: Some(0x00),
1088 parent_beacon_block_root: Some(H256::zero()),
1089 requests_hash: Some(*EMPTY_KECCAK_HASH),
1090 ..Default::default()
1091 };
1092 assert!(validate_block_header(&block, &parent_block, ELASTICITY_MULTIPLIER).is_ok());
1093 assert_eq!(parent_block.encode_to_vec().len(), parent_block.length());
1094 assert_eq!(block.encode_to_vec().len(), block.length());
1095 }
1096
1097 #[test]
1098 fn test_compute_transactions_root() {
1099 let encoded_transactions = [
1100 "0x01f8d68330182404842daf517a830186a08080b880c1597f3c842558e64df52c3e0f0973067577c030c0c6578dbb2eef63155a21106fd4426057527f296b2ecdfabc81e34ffc82e89dec20f6b7c41fa1969d3c3bc44262c86f08b5b76077527fb7ece918787c50c878052c30a8b1d4abc07331e6d14b8ded52bbc58a6e9992b76097527f0110937c38cc13b914f201fc09dc6f7a80c001a09930cb92b4a27dce971c697a8c47fa34c98d076abc7b36e1239d6abcfc7c8403a041b35118447fe77c38c0b3a92a2dd3ecba4a9e4b35cc6534cd787f56c0cf2e21",
1101 "0xf86e81fa843127403882f61894db8d964741c53e55df9c2d4e9414c6c96482874e870aa87bee538000808360306ca03aa421df67a101c45ff9cb06ce28f518a5d8d8dbb76a79361280071909650a27a05a447ff053c4ae601cfe81859b58d5603f2d0a73481c50f348089032feb0b073",
1102 "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6",
1103 "0x01f8ea8330182402842daf517a830186a094000000000000000000000000000000000000000080b880bdb30d976000604e557145600060a155d67fe7e473caf6e33cba341136268fc1189ba07837ef8a266570289ff53afc43436260c7527f333dfe837f4838f6053e5e46e4151aeec28f356ec39a2db9769f36ec92e3e3f660e7527f0b261608674300d4621eff679096a6ed786591aca69f2b22a3ea6949621daade610107527f3cc080a01f3f906540fb56b0576c51b3ffa86df213fd1f407378c9441cfdd9d5f3c1df3da035691b16c053b68ec74683ae020293cbc6a47ac773dc8defb96cb680c576e5a3",
1104 ];
1105 let transactions: Vec<Transaction> = encoded_transactions
1106 .iter()
1107 .map(|hex| {
1108 Transaction::decode_canonical(&hex::decode(hex.trim_start_matches("0x")).unwrap())
1109 .unwrap()
1110 })
1111 .collect();
1112 let transactions_root =
1113 compute_transactions_root(&transactions, ðrex_crypto::NativeCrypto);
1114 let expected_root = H256::from_slice(
1115 &hex::decode("adf0387d2303fe80aeca23bf6828c979b44d8a8fe4a1ba1d3511bc1567ca80de")
1116 .unwrap(),
1117 );
1118 assert_eq!(transactions_root, expected_root);
1119 }
1120
1121 #[test]
1122 fn test_calculate_base_fee_per_gas_big_numbers() {
1125 let expected_base_fee = Some(1317727380375);
1126 let block_gas_limit = 30000000;
1127 let parent_gas_limit = 30000000;
1128 let parent_gas_used = 1981764;
1129 let parent_base_fee_per_gas = 1478077008012;
1130 let calc_base_fee = calculate_base_fee_per_gas(
1131 block_gas_limit,
1132 parent_gas_limit,
1133 parent_gas_used,
1134 parent_base_fee_per_gas,
1135 ELASTICITY_MULTIPLIER,
1136 );
1137 assert_eq!(calc_base_fee, expected_base_fee)
1138 }
1139
1140 #[test]
1141 fn test_calc_blob_fee_post_osaka_bpo1() {
1142 let parent = BlockHeader {
1143 excess_blob_gas: Some(5149252),
1144 blob_gas_used: Some(1310720),
1145 base_fee_per_gas: Some(30),
1146 ..Default::default()
1147 };
1148 let schedule = ForkBlobSchedule {
1149 target: 9,
1150 max: 14,
1151 base_fee_update_fraction: 8832827,
1152 };
1153 let fork = Fork::Osaka;
1154
1155 let res = calc_excess_blob_gas(&parent, schedule, fork);
1156 assert_eq!(res, 5617366)
1157 }
1158
1159 #[test]
1160 fn test_calc_blob_fee_post_osaka_bpo3() {
1161 let parent = BlockHeader {
1162 excess_blob_gas: Some(19251039),
1163 blob_gas_used: Some(2490368),
1164 base_fee_per_gas: Some(50),
1165 ..Default::default()
1166 };
1167 let schedule = ForkBlobSchedule {
1168 target: 21,
1169 max: 32,
1170 base_fee_update_fraction: 20609697,
1171 };
1172 let fork = Fork::Osaka;
1173 let res = calc_excess_blob_gas(&parent, schedule, fork);
1174 assert_eq!(res, 20107103)
1175 }
1176
1177 #[test]
1178 fn test_calc_blob_fee_post_osaka_bpo1_ef() {
1179 let parent = BlockHeader {
1180 excess_blob_gas: Some(0x360000),
1181 blob_gas_used: Some(0),
1182 base_fee_per_gas: Some(0x11),
1183 ..Default::default()
1184 };
1185 let schedule = ForkBlobSchedule {
1186 target: 9,
1187 max: 14,
1188 base_fee_update_fraction: 0x86c73b,
1189 };
1190 let fork = Fork::Osaka;
1191
1192 let res = calc_excess_blob_gas(&parent, schedule, fork);
1193 assert_eq!(res, 3538944)
1194 }
1195
1196 #[test]
1197 fn test_calc_excess_blob_gas_at_amsterdam_inherits_bpo1_target() {
1198 let parent = BlockHeader {
1205 excess_blob_gas: Some(0),
1206 blob_gas_used: Some(12 * GAS_PER_BLOB as u64),
1207 base_fee_per_gas: Some(INITIAL_BASE_FEE),
1208 ..Default::default()
1209 };
1210 let config = ChainConfig {
1211 osaka_time: Some(0),
1212 bpo1_time: Some(0),
1213 amsterdam_time: Some(100),
1214 ..Default::default()
1215 };
1216
1217 let schedule = config
1218 .get_fork_blob_schedule(100)
1219 .expect("Amsterdam must resolve a blob schedule");
1220 assert_eq!(schedule.target, 10);
1221 assert_eq!(schedule.max, 15);
1222
1223 let res = calc_excess_blob_gas(&parent, schedule, config.fork(100));
1224 assert_eq!(res, 4 * GAS_PER_BLOB as u64);
1225 }
1226
1227 #[test]
1228 fn test_fake_exponential_overflow() {
1229 assert!(fake_exponential(57532635.into(), 3145728.into(), 3338477).is_ok());
1231 }
1232
1233 #[test]
1234 fn test_fake_exponential_bounds_overflow() {
1235 let thing = fake_exponential(
1237 MIN_BASE_FEE_PER_BLOB_GAS.into(),
1238 400_000_000.into(),
1239 BLOB_BASE_FEE_UPDATE_FRACTION,
1240 );
1241 assert!(thing.is_ok());
1243 }
1244
1245 #[test]
1246 fn burned_fees_header_roundtrip_and_hash() {
1247 let h = BlockHeader {
1251 base_fee_per_gas: Some(7),
1252 withdrawals_root: Some(H256::zero()),
1253 blob_gas_used: Some(0),
1254 excess_blob_gas: Some(0),
1255 parent_beacon_block_root: Some(H256::zero()),
1256 requests_hash: Some(H256::zero()),
1257 block_access_list_hash: Some(H256::zero()),
1258 slot_number: Some(1),
1259 burned_fees: Some(12345),
1260 ..Default::default()
1261 };
1262 let encoded = h.encode_to_vec();
1263 let decoded = BlockHeader::decode(&encoded).expect("decode");
1264 assert_eq!(decoded.burned_fees, Some(12345));
1265 let h_none = BlockHeader {
1267 burned_fees: None,
1268 ..Default::default()
1269 };
1270 assert_eq!(
1271 BlockHeader::decode(&h_none.encode_to_vec())
1272 .unwrap()
1273 .burned_fees,
1274 None
1275 );
1276 let h_with = BlockHeader {
1278 base_fee_per_gas: Some(7),
1279 withdrawals_root: Some(H256::zero()),
1280 blob_gas_used: Some(0),
1281 excess_blob_gas: Some(0),
1282 parent_beacon_block_root: Some(H256::zero()),
1283 requests_hash: Some(H256::zero()),
1284 block_access_list_hash: Some(H256::zero()),
1285 slot_number: Some(1),
1286 burned_fees: Some(12345),
1287 ..Default::default()
1288 };
1289 let h_without = BlockHeader {
1290 base_fee_per_gas: Some(7),
1291 withdrawals_root: Some(H256::zero()),
1292 blob_gas_used: Some(0),
1293 excess_blob_gas: Some(0),
1294 parent_beacon_block_root: Some(H256::zero()),
1295 requests_hash: Some(H256::zero()),
1296 block_access_list_hash: Some(H256::zero()),
1297 slot_number: Some(1),
1298 burned_fees: None,
1299 ..Default::default()
1300 };
1301 assert_ne!(h_with.hash(), h_without.hash());
1302 }
1303
1304 #[test]
1311 fn into_with_burned_fees_clears_stale_cache() {
1312 use crate::NativeCrypto;
1313
1314 let header_none = BlockHeader::default();
1316
1317 let hash_stale = header_none.hash();
1319
1320 let header_some_fresh = BlockHeader {
1322 burned_fees: Some(42),
1323 ..BlockHeader::default()
1324 };
1325 let hash_expected = header_some_fresh.compute_block_hash(&NativeCrypto);
1326
1327 assert_ne!(
1329 hash_stale, hash_expected,
1330 "burned_fees=None and burned_fees=Some(42) must produce different block hashes"
1331 );
1332
1333 let fresh_header = header_none.into_with_burned_fees(Some(42));
1335 let hash_fresh = fresh_header.hash();
1336
1337 assert_eq!(
1339 hash_fresh, hash_expected,
1340 "into_with_burned_fees must yield a hash identical to building a fresh header with the same burned_fees"
1341 );
1342 assert_ne!(
1343 hash_fresh, hash_stale,
1344 "OnceCell must have been cleared: fresh hash must differ from the stale cached hash"
1345 );
1346 }
1347
1348 #[test]
1354 fn into_with_burned_fees_none_is_idempotent() {
1355 let header = BlockHeader::default();
1356 let hash_before = header.hash(); let header_after = header.into_with_burned_fees(None);
1360 let hash_after = header_after.hash();
1361
1362 assert_eq!(
1363 hash_before, hash_after,
1364 "injecting None must not change the block hash (Amsterdam no-op)"
1365 );
1366 }
1367
1368 #[test]
1369 fn test_validate_block_body_rejects_missing_withdrawals_field() {
1370 use crate::constants::EMPTY_WITHDRAWALS_HASH;
1371
1372 let crypto = NativeCrypto;
1373 let header = BlockHeader {
1374 transactions_root: compute_transactions_root(&[], &crypto),
1375 withdrawals_root: Some(*EMPTY_WITHDRAWALS_HASH),
1376 ..Default::default()
1377 };
1378
1379 let body_missing = BlockBody {
1384 withdrawals: None,
1385 ..BlockBody::empty()
1386 };
1387 assert!(matches!(
1388 validate_block_body(&header, &body_missing, &crypto),
1389 Err(InvalidBlockBodyError::WithdrawalsRootNotMatch)
1390 ));
1391
1392 assert!(validate_block_body(&header, &BlockBody::empty(), &crypto).is_ok());
1394
1395 let pre_shanghai_header = BlockHeader {
1397 transactions_root: compute_transactions_root(&[], &crypto),
1398 ..Default::default()
1399 };
1400 assert!(matches!(
1401 validate_block_body(&pre_shanghai_header, &BlockBody::empty(), &crypto),
1402 Err(InvalidBlockBodyError::WithdrawalsRootNotMatch)
1403 ));
1404 let body_no_withdrawals = BlockBody {
1406 withdrawals: None,
1407 ..BlockBody::empty()
1408 };
1409 assert!(validate_block_body(&pre_shanghai_header, &body_no_withdrawals, &crypto).is_ok());
1410 }
1411
1412 #[test]
1413 fn test_normalize_legacy_withdrawals() {
1414 use crate::constants::EMPTY_WITHDRAWALS_HASH;
1415
1416 let crypto = NativeCrypto;
1417
1418 let header = BlockHeader {
1422 transactions_root: compute_transactions_root(&[], &crypto),
1423 withdrawals_root: Some(*EMPTY_WITHDRAWALS_HASH),
1424 ..Default::default()
1425 };
1426 let mut body = BlockBody {
1427 withdrawals: None,
1428 ..BlockBody::empty()
1429 };
1430 normalize_legacy_withdrawals(&header, &mut body);
1431 assert_eq!(body.withdrawals, Some(Vec::new()));
1432 assert!(validate_block_body(&header, &body, &crypto).is_ok());
1433
1434 let bad_header = BlockHeader {
1437 transactions_root: compute_transactions_root(&[], &crypto),
1438 withdrawals_root: Some(H256::repeat_byte(0xAB)),
1439 ..Default::default()
1440 };
1441 let mut bad_body = BlockBody {
1442 withdrawals: None,
1443 ..BlockBody::empty()
1444 };
1445 normalize_legacy_withdrawals(&bad_header, &mut bad_body);
1446 assert_eq!(bad_body.withdrawals, None);
1447 assert!(validate_block_body(&bad_header, &bad_body, &crypto).is_err());
1448
1449 let pre_shanghai_header = BlockHeader {
1451 transactions_root: compute_transactions_root(&[], &crypto),
1452 ..Default::default()
1453 };
1454 let mut pre_shanghai_body = BlockBody {
1455 withdrawals: None,
1456 ..BlockBody::empty()
1457 };
1458 normalize_legacy_withdrawals(&pre_shanghai_header, &mut pre_shanghai_body);
1459 assert_eq!(pre_shanghai_body.withdrawals, None);
1460
1461 let mut canonical = BlockBody::empty();
1463 let before = canonical.withdrawals.clone();
1464 normalize_legacy_withdrawals(&header, &mut canonical);
1465 assert_eq!(canonical.withdrawals, before);
1466 }
1467}