1use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader, EMPTY_OMMER_ROOT_HASH};
4use alloy_eips::{
5 calc_next_block_base_fee,
6 eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK},
7};
8use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
9use reth_consensus::ConsensusError;
10use reth_primitives::SealedBlock;
11use reth_primitives_traits::{BlockBody, GotExpected, SealedHeader};
12use revm_primitives::calc_excess_blob_gas;
13
14#[inline]
16pub fn validate_header_gas<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
17 if header.gas_used() > header.gas_limit() {
18 return Err(ConsensusError::HeaderGasUsedExceedsGasLimit {
19 gas_used: header.gas_used(),
20 gas_limit: header.gas_limit(),
21 })
22 }
23 Ok(())
24}
25
26#[inline]
28pub fn validate_header_base_fee<H: BlockHeader, ChainSpec: EthereumHardforks>(
29 header: &H,
30 chain_spec: &ChainSpec,
31) -> Result<(), ConsensusError> {
32 if chain_spec.is_fork_active_at_block(EthereumHardfork::London, header.number()) &&
33 header.base_fee_per_gas().is_none()
34 {
35 return Err(ConsensusError::BaseFeeMissing)
36 }
37 Ok(())
38}
39
40#[inline]
46pub fn validate_shanghai_withdrawals<H: BlockHeader, B: BlockBody>(
47 block: &SealedBlock<H, B>,
48) -> Result<(), ConsensusError> {
49 let withdrawals = block.body.withdrawals().ok_or(ConsensusError::BodyWithdrawalsMissing)?;
50 let withdrawals_root = alloy_consensus::proofs::calculate_withdrawals_root(withdrawals);
51 let header_withdrawals_root =
52 block.withdrawals_root().ok_or(ConsensusError::WithdrawalsRootMissing)?;
53 if withdrawals_root != *header_withdrawals_root {
54 return Err(ConsensusError::BodyWithdrawalsRootDiff(
55 GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(),
56 ));
57 }
58 Ok(())
59}
60
61#[inline]
67pub fn validate_cancun_gas<H: BlockHeader, B: BlockBody>(
68 block: &SealedBlock<H, B>,
69) -> Result<(), ConsensusError> {
70 let header_blob_gas_used =
73 block.header().blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
74 let total_blob_gas = block.body.blob_gas_used();
75 if total_blob_gas != header_blob_gas_used {
76 return Err(ConsensusError::BlobGasUsedDiff(GotExpected {
77 got: header_blob_gas_used,
78 expected: total_blob_gas,
79 }));
80 }
81 Ok(())
82}
83
84pub fn validate_body_against_header<B, H>(body: &B, header: &H) -> Result<(), ConsensusError>
91where
92 B: BlockBody,
93 H: BlockHeader,
94{
95 let ommers_hash = body.calculate_ommers_root();
96 if Some(header.ommers_hash()) != ommers_hash {
97 return Err(ConsensusError::BodyOmmersHashDiff(
98 GotExpected {
99 got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
100 expected: header.ommers_hash(),
101 }
102 .into(),
103 ))
104 }
105
106 let tx_root = body.calculate_tx_root();
107 if header.transactions_root() != tx_root {
108 return Err(ConsensusError::BodyTransactionRootDiff(
109 GotExpected { got: tx_root, expected: header.transactions_root() }.into(),
110 ))
111 }
112
113 match (header.withdrawals_root(), body.calculate_withdrawals_root()) {
114 (Some(header_withdrawals_root), Some(withdrawals_root)) => {
115 if withdrawals_root != header_withdrawals_root {
116 return Err(ConsensusError::BodyWithdrawalsRootDiff(
117 GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(),
118 ))
119 }
120 }
121 (None, None) => {
122 }
124 _ => return Err(ConsensusError::WithdrawalsRootUnexpected),
125 }
126
127 Ok(())
128}
129
130pub fn validate_block_pre_execution<H, B, ChainSpec>(
137 block: &SealedBlock<H, B>,
138 chain_spec: &ChainSpec,
139) -> Result<(), ConsensusError>
140where
141 H: BlockHeader,
142 B: BlockBody,
143 ChainSpec: EthereumHardforks,
144{
145 let ommers_hash = block.body.calculate_ommers_root();
147 if Some(block.header.ommers_hash()) != ommers_hash {
148 return Err(ConsensusError::BodyOmmersHashDiff(
149 GotExpected {
150 got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
151 expected: block.header.ommers_hash(),
152 }
153 .into(),
154 ))
155 }
156
157 if let Err(error) = block.ensure_transaction_root_valid() {
159 return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
160 }
161
162 if chain_spec.is_shanghai_active_at_timestamp(block.timestamp()) {
164 validate_shanghai_withdrawals(block)?;
165 }
166
167 if chain_spec.is_cancun_active_at_timestamp(block.timestamp()) {
168 validate_cancun_gas(block)?;
169 }
170
171 Ok(())
172}
173
174pub fn validate_4844_header_standalone<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
183 let blob_gas_used = header.blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
184 let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?;
185
186 if header.parent_beacon_block_root().is_none() {
187 return Err(ConsensusError::ParentBeaconBlockRootMissing)
188 }
189
190 if blob_gas_used > MAX_DATA_GAS_PER_BLOCK {
191 return Err(ConsensusError::BlobGasUsedExceedsMaxBlobGasPerBlock {
192 blob_gas_used,
193 max_blob_gas_per_block: MAX_DATA_GAS_PER_BLOCK,
194 })
195 }
196
197 if blob_gas_used % DATA_GAS_PER_BLOB != 0 {
198 return Err(ConsensusError::BlobGasUsedNotMultipleOfBlobGasPerBlob {
199 blob_gas_used,
200 blob_gas_per_blob: DATA_GAS_PER_BLOB,
201 })
202 }
203
204 if excess_blob_gas % DATA_GAS_PER_BLOB != 0 {
207 return Err(ConsensusError::ExcessBlobGasNotMultipleOfBlobGasPerBlob {
208 excess_blob_gas,
209 blob_gas_per_blob: DATA_GAS_PER_BLOB,
210 })
211 }
212
213 Ok(())
214}
215
216#[inline]
221pub fn validate_header_extradata<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
222 let extradata_len = header.extra_data().len();
223 if extradata_len > MAXIMUM_EXTRA_DATA_SIZE {
224 Err(ConsensusError::ExtraDataExceedsMax { len: extradata_len })
225 } else {
226 Ok(())
227 }
228}
229
230#[inline]
235pub fn validate_against_parent_hash_number<H: BlockHeader>(
236 header: &H,
237 parent: &SealedHeader<H>,
238) -> Result<(), ConsensusError> {
239 if parent.number() + 1 != header.number() {
241 return Err(ConsensusError::ParentBlockNumberMismatch {
242 parent_block_number: parent.number(),
243 block_number: header.number(),
244 })
245 }
246
247 if parent.hash() != header.parent_hash() {
248 return Err(ConsensusError::ParentHashMismatch(
249 GotExpected { got: header.parent_hash(), expected: parent.hash() }.into(),
250 ))
251 }
252
253 Ok(())
254}
255
256#[inline]
258pub fn validate_against_parent_eip1559_base_fee<
259 H: BlockHeader,
260 ChainSpec: EthChainSpec + EthereumHardforks,
261>(
262 header: &H,
263 parent: &H,
264 chain_spec: &ChainSpec,
265) -> Result<(), ConsensusError> {
266 if chain_spec.fork(EthereumHardfork::London).active_at_block(header.number()) {
267 let base_fee = header.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?;
268
269 let expected_base_fee =
270 if chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number()) {
271 alloy_eips::eip1559::INITIAL_BASE_FEE
272 } else {
273 let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?;
276 calc_next_block_base_fee(
277 parent.gas_used(),
278 parent.gas_limit(),
279 base_fee,
280 chain_spec.base_fee_params_at_timestamp(header.timestamp()),
281 )
282 };
283 if expected_base_fee != base_fee {
284 return Err(ConsensusError::BaseFeeDiff(GotExpected {
285 expected: expected_base_fee,
286 got: base_fee,
287 }))
288 }
289 }
290
291 Ok(())
292}
293
294#[inline]
296pub fn validate_against_parent_timestamp<H: BlockHeader>(
297 header: &H,
298 parent: &H,
299) -> Result<(), ConsensusError> {
300 if header.timestamp() <= parent.timestamp() {
301 return Err(ConsensusError::TimestampIsInPast {
302 parent_timestamp: parent.timestamp(),
303 timestamp: header.timestamp(),
304 })
305 }
306 Ok(())
307}
308
309pub fn validate_against_parent_4844<H: BlockHeader>(
314 header: &H,
315 parent: &H,
316) -> Result<(), ConsensusError> {
317 let parent_blob_gas_used = parent.blob_gas_used().unwrap_or(0);
324 let parent_excess_blob_gas = parent.excess_blob_gas().unwrap_or(0);
325
326 if header.blob_gas_used().is_none() {
327 return Err(ConsensusError::BlobGasUsedMissing)
328 }
329 let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?;
330
331 let expected_excess_blob_gas =
332 calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used);
333 if expected_excess_blob_gas != excess_blob_gas {
334 return Err(ConsensusError::ExcessBlobGasDiff {
335 diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas },
336 parent_excess_blob_gas,
337 parent_blob_gas_used,
338 })
339 }
340
341 Ok(())
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use alloy_consensus::{Header, TxEip4844, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH};
348 use alloy_eips::{
349 eip4895::{Withdrawal, Withdrawals},
350 BlockHashOrNumber,
351 };
352 use alloy_primitives::{
353 hex_literal::hex, Address, BlockHash, BlockNumber, Bytes, PrimitiveSignature as Signature,
354 U256,
355 };
356 use mockall::mock;
357 use rand::Rng;
358 use reth_chainspec::ChainSpecBuilder;
359 use reth_primitives::{proofs, Account, BlockBody, Transaction, TransactionSigned};
360 use reth_storage_api::{
361 errors::provider::ProviderResult, AccountReader, HeaderProvider, WithdrawalsProvider,
362 };
363 use std::ops::RangeBounds;
364
365 mock! {
366 WithdrawalsProvider {}
367
368 impl WithdrawalsProvider for WithdrawalsProvider {
369 fn latest_withdrawal(&self) -> ProviderResult<Option<Withdrawal>> ;
370
371 fn withdrawals_by_block(
372 &self,
373 _id: BlockHashOrNumber,
374 _timestamp: u64,
375 ) -> ProviderResult<Option<Withdrawals>> ;
376 }
377 }
378
379 struct Provider {
380 is_known: bool,
381 parent: Option<Header>,
382 account: Option<Account>,
383 withdrawals_provider: MockWithdrawalsProvider,
384 }
385
386 impl Provider {
387 fn new(parent: Option<Header>) -> Self {
389 Self {
390 is_known: false,
391 parent,
392 account: None,
393 withdrawals_provider: MockWithdrawalsProvider::new(),
394 }
395 }
396 }
397
398 impl AccountReader for Provider {
399 fn basic_account(&self, _address: Address) -> ProviderResult<Option<Account>> {
400 Ok(self.account)
401 }
402 }
403
404 impl HeaderProvider for Provider {
405 type Header = Header;
406
407 fn is_known(&self, _block_hash: &BlockHash) -> ProviderResult<bool> {
408 Ok(self.is_known)
409 }
410
411 fn header(&self, _block_number: &BlockHash) -> ProviderResult<Option<Header>> {
412 Ok(self.parent.clone())
413 }
414
415 fn header_by_number(&self, _num: u64) -> ProviderResult<Option<Header>> {
416 Ok(self.parent.clone())
417 }
418
419 fn header_td(&self, _hash: &BlockHash) -> ProviderResult<Option<U256>> {
420 Ok(None)
421 }
422
423 fn header_td_by_number(&self, _number: BlockNumber) -> ProviderResult<Option<U256>> {
424 Ok(None)
425 }
426
427 fn headers_range(
428 &self,
429 _range: impl RangeBounds<BlockNumber>,
430 ) -> ProviderResult<Vec<Header>> {
431 Ok(vec![])
432 }
433
434 fn sealed_header(
435 &self,
436 _block_number: BlockNumber,
437 ) -> ProviderResult<Option<SealedHeader>> {
438 Ok(None)
439 }
440
441 fn sealed_headers_while(
442 &self,
443 _range: impl RangeBounds<BlockNumber>,
444 _predicate: impl FnMut(&SealedHeader) -> bool,
445 ) -> ProviderResult<Vec<SealedHeader>> {
446 Ok(vec![])
447 }
448 }
449
450 impl WithdrawalsProvider for Provider {
451 fn withdrawals_by_block(
452 &self,
453 _id: BlockHashOrNumber,
454 _timestamp: u64,
455 ) -> ProviderResult<Option<Withdrawals>> {
456 self.withdrawals_provider.withdrawals_by_block(_id, _timestamp)
457 }
458
459 fn latest_withdrawal(&self) -> ProviderResult<Option<Withdrawal>> {
460 self.withdrawals_provider.latest_withdrawal()
461 }
462 }
463
464 fn mock_blob_tx(nonce: u64, num_blobs: usize) -> TransactionSigned {
465 let mut rng = rand::thread_rng();
466 let request = Transaction::Eip4844(TxEip4844 {
467 chain_id: 1u64,
468 nonce,
469 max_fee_per_gas: 0x28f000fff,
470 max_priority_fee_per_gas: 0x28f000fff,
471 max_fee_per_blob_gas: 0x7,
472 gas_limit: 10,
473 to: Address::default(),
474 value: U256::from(3_u64),
475 input: Bytes::from(vec![1, 2]),
476 access_list: Default::default(),
477 blob_versioned_hashes: std::iter::repeat_with(|| rng.gen()).take(num_blobs).collect(),
478 });
479
480 let signature = Signature::new(U256::default(), U256::default(), true);
481
482 TransactionSigned::new_unhashed(request, signature)
483 }
484
485 fn mock_block() -> (SealedBlock, Header) {
487 let header = Header {
491 parent_hash: hex!("859fad46e75d9be177c2584843501f2270c7e5231711e90848290d12d7c6dcdd").into(),
492 ommers_hash: EMPTY_OMMER_ROOT_HASH,
493 beneficiary: hex!("4675c7e5baafbffbca748158becba61ef3b0a263").into(),
494 state_root: hex!("8337403406e368b3e40411138f4868f79f6d835825d55fd0c2f6e17b1a3948e9").into(),
495 transactions_root: EMPTY_ROOT_HASH,
496 receipts_root: EMPTY_ROOT_HASH,
497 logs_bloom: hex!("002400000000004000220000800002000000000000000000000000000000100000000000000000100000000000000021020000000800000006000000002100040000000c0004000000000008000008200000000000000000000000008000000001040000020000020000002000000800000002000020000000022010000000000000010002001000000000020200000000000001000200880000004000000900020000000000020000000040000000000000000000000000000080000000000001000002000000000000012000200020000000000000001000000000000020000010321400000000100000000000000000000000000000400000000000000000").into(),
498 difficulty: U256::ZERO, number: 0xf21d20,
500 gas_limit: 0x1c9c380,
501 gas_used: 0x6e813,
502 timestamp: 0x635f9657,
503 extra_data: hex!("")[..].into(),
504 mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
505 nonce: 0x0000000000000000u64.into(),
506 base_fee_per_gas: 0x28f0001df.into(),
507 withdrawals_root: None,
508 blob_gas_used: None,
509 excess_blob_gas: None,
510 parent_beacon_block_root: None,
511 requests_hash: None,
512 target_blobs_per_block: None,
513 };
514 let mut parent = header.clone();
517 parent.gas_used = 17763076;
518 parent.gas_limit = 30000000;
519 parent.base_fee_per_gas = Some(0x28041f7f5);
520 parent.number -= 1;
521 parent.timestamp -= 1;
522
523 let ommers = Vec::new();
524 let transactions = Vec::new();
525
526 (
527 SealedBlock {
528 header: SealedHeader::seal(header),
529 body: BlockBody { transactions, ommers, withdrawals: None },
530 },
531 parent,
532 )
533 }
534
535 #[test]
536 fn valid_withdrawal_index() {
537 let chain_spec = ChainSpecBuilder::mainnet().shanghai_activated().build();
538
539 let create_block_with_withdrawals = |indexes: &[u64]| {
540 let withdrawals = Withdrawals::new(
541 indexes
542 .iter()
543 .map(|idx| Withdrawal { index: *idx, ..Default::default() })
544 .collect(),
545 );
546
547 let header = Header {
548 withdrawals_root: Some(proofs::calculate_withdrawals_root(&withdrawals)),
549 ..Default::default()
550 };
551
552 SealedBlock {
553 header: SealedHeader::seal(header),
554 body: BlockBody { withdrawals: Some(withdrawals), ..Default::default() },
555 }
556 };
557
558 let block = create_block_with_withdrawals(&[1]);
560 assert_eq!(validate_block_pre_execution(&block, &chain_spec), Ok(()));
561
562 let block = create_block_with_withdrawals(&[1, 2, 3]);
564 assert_eq!(validate_block_pre_execution(&block, &chain_spec), Ok(()));
565 let block = create_block_with_withdrawals(&[5, 6, 7, 8, 9]);
566 assert_eq!(validate_block_pre_execution(&block, &chain_spec), Ok(()));
567 let (_, parent) = mock_block();
568
569 let mut provider = Provider::new(Some(parent));
571 provider
572 .withdrawals_provider
573 .expect_latest_withdrawal()
574 .return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() })));
575 }
576
577 #[test]
578 fn cancun_block_incorrect_blob_gas_used() {
579 let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build();
580
581 let transaction = mock_blob_tx(1, 10);
583
584 let header = Header {
585 base_fee_per_gas: Some(1337),
586 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
587 blob_gas_used: Some(1),
588 transactions_root: proofs::calculate_transaction_root(&[transaction.clone()]),
589 ..Default::default()
590 };
591 let header = SealedHeader::seal(header);
592
593 let body = BlockBody {
594 transactions: vec![transaction],
595 ommers: vec![],
596 withdrawals: Some(Withdrawals::default()),
597 };
598
599 let block = SealedBlock::new(header, body);
600
601 let expected_blob_gas_used = 10 * DATA_GAS_PER_BLOB;
603
604 assert_eq!(
606 validate_block_pre_execution(&block, &chain_spec),
607 Err(ConsensusError::BlobGasUsedDiff(GotExpected {
608 got: 1,
609 expected: expected_blob_gas_used
610 }))
611 );
612 }
613}