1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![cfg_attr(not(feature = "std"), no_std)]
10#![cfg_attr(not(test), warn(unused_crate_dependencies))]
11
12extern crate alloc;
13
14use alloc::{format, sync::Arc};
15use alloy_consensus::{BlockHeader as _, EMPTY_OMMER_ROOT_HASH};
16use alloy_primitives::B64;
17use core::fmt::Debug;
18use reth_chainspec::EthChainSpec;
19use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator};
20use reth_consensus_common::validation::{
21 validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number,
22 validate_against_parent_timestamp, validate_cancun_gas, validate_header_base_fee,
23 validate_header_extra_data, validate_header_gas,
24};
25use reth_execution_types::BlockExecutionResult;
26use reth_optimism_forks::OpHardforks;
27use reth_optimism_primitives::DepositReceipt;
28use reth_primitives_traits::{
29 Block, BlockBody, BlockHeader, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock,
30 SealedHeader,
31};
32
33mod proof;
34pub use proof::calculate_receipt_root_no_memo_optimism;
35
36pub mod validation;
37pub use validation::{canyon, isthmus, validate_block_post_execution};
38
39pub mod error;
40pub use error::OpConsensusError;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct OpBeaconConsensus<ChainSpec> {
47 chain_spec: Arc<ChainSpec>,
49}
50
51impl<ChainSpec> OpBeaconConsensus<ChainSpec> {
52 pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
54 Self { chain_spec }
55 }
56}
57
58impl<N, ChainSpec> FullConsensus<N> for OpBeaconConsensus<ChainSpec>
59where
60 N: NodePrimitives<Receipt: DepositReceipt>,
61 ChainSpec: EthChainSpec<Header = N::BlockHeader> + OpHardforks + Debug + Send + Sync,
62{
63 fn validate_block_post_execution(
64 &self,
65 block: &RecoveredBlock<N::Block>,
66 result: &BlockExecutionResult<N::Receipt>,
67 ) -> Result<(), ConsensusError> {
68 validate_block_post_execution(block.header(), &self.chain_spec, result)
69 }
70}
71
72impl<B, ChainSpec> Consensus<B> for OpBeaconConsensus<ChainSpec>
73where
74 B: Block,
75 ChainSpec: EthChainSpec<Header = B::Header> + OpHardforks + Debug + Send + Sync,
76{
77 type Error = ConsensusError;
78
79 fn validate_body_against_header(
80 &self,
81 body: &B::Body,
82 header: &SealedHeader<B::Header>,
83 ) -> Result<(), ConsensusError> {
84 validation::validate_body_against_header_op(&self.chain_spec, body, header.header())
85 }
86
87 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError> {
88 let ommers_hash = block.body().calculate_ommers_root();
90 if Some(block.ommers_hash()) != ommers_hash {
91 return Err(ConsensusError::BodyOmmersHashDiff(
92 GotExpected {
93 got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
94 expected: block.ommers_hash(),
95 }
96 .into(),
97 ))
98 }
99
100 if let Err(error) = block.ensure_transaction_root_valid() {
102 return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
103 }
104
105 if self.chain_spec.is_canyon_active_at_timestamp(block.timestamp()) {
107 canyon::ensure_empty_shanghai_withdrawals(block.body()).map_err(|err| {
108 ConsensusError::Other(format!("failed to verify block {}: {err}", block.number()))
109 })?
110 } else {
111 return Ok(())
112 }
113
114 if self.chain_spec.is_jovian_active_at_timestamp(block.timestamp()) {
119 block.blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
120 } else if self.chain_spec.is_ecotone_active_at_timestamp(block.timestamp()) {
121 validate_cancun_gas(block)?;
122 }
123
124 if self.chain_spec.is_isthmus_active_at_timestamp(block.timestamp()) {
126 isthmus::ensure_withdrawals_storage_root_is_some(block.header()).map_err(|err| {
128 ConsensusError::Other(format!("failed to verify block {}: {err}", block.number()))
129 })?
130 } else {
131 canyon::ensure_empty_withdrawals_root(block.header())?
133 }
134
135 Ok(())
136 }
137}
138
139impl<H, ChainSpec> HeaderValidator<H> for OpBeaconConsensus<ChainSpec>
140where
141 H: BlockHeader,
142 ChainSpec: EthChainSpec<Header = H> + OpHardforks + Debug + Send + Sync,
143{
144 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> {
145 let header = header.header();
146 debug_assert!(
148 self.chain_spec.is_bedrock_active_at_block(header.number()),
149 "manually import OVM blocks"
150 );
151
152 if header.nonce() != Some(B64::ZERO) {
153 return Err(ConsensusError::TheMergeNonceIsNotZero)
154 }
155
156 if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH {
157 return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty)
158 }
159
160 validate_header_extra_data(header)?;
170 validate_header_gas(header)?;
171 validate_header_base_fee(header, &self.chain_spec)
172 }
173
174 fn validate_header_against_parent(
175 &self,
176 header: &SealedHeader<H>,
177 parent: &SealedHeader<H>,
178 ) -> Result<(), ConsensusError> {
179 validate_against_parent_hash_number(header.header(), parent)?;
180
181 if self.chain_spec.is_bedrock_active_at_block(header.number()) {
182 validate_against_parent_timestamp(header.header(), parent.header())?;
183 }
184
185 validate_against_parent_eip1559_base_fee(
186 header.header(),
187 parent.header(),
188 &self.chain_spec,
189 )?;
190
191 if self.chain_spec.is_ecotone_active_at_timestamp(header.timestamp()) {
196 let blob_gas_used = header.blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
197
198 if !self.chain_spec.is_jovian_active_at_timestamp(header.timestamp()) &&
200 blob_gas_used != 0
201 {
202 return Err(ConsensusError::BlobGasUsedDiff(GotExpected {
203 got: blob_gas_used,
204 expected: 0,
205 }));
206 }
207
208 let excess_blob_gas =
209 header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?;
210 if excess_blob_gas != 0 {
211 return Err(ConsensusError::ExcessBlobGasDiff {
212 diff: GotExpected { got: excess_blob_gas, expected: 0 },
213 parent_excess_blob_gas: parent.excess_blob_gas().unwrap_or(0),
214 parent_blob_gas_used: parent.blob_gas_used().unwrap_or(0),
215 })
216 }
217 }
218
219 Ok(())
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use std::sync::Arc;
226
227 use alloy_consensus::{BlockBody, Eip658Value, Header, Receipt, TxEip7702, TxReceipt};
228 use alloy_eips::{eip4895::Withdrawals, eip7685::Requests};
229 use alloy_primitives::{Address, Bytes, Signature, U256};
230 use op_alloy_consensus::{
231 encode_holocene_extra_data, encode_jovian_extra_data, OpTypedTransaction,
232 };
233 use reth_chainspec::BaseFeeParams;
234 use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator};
235 use reth_optimism_chainspec::{OpChainSpec, OpChainSpecBuilder, OP_MAINNET};
236 use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned};
237 use reth_primitives_traits::{proofs, GotExpected, RecoveredBlock, SealedBlock, SealedHeader};
238 use reth_provider::BlockExecutionResult;
239
240 use crate::OpBeaconConsensus;
241
242 fn mock_tx(nonce: u64) -> OpTransactionSigned {
243 let tx = TxEip7702 {
244 chain_id: 1u64,
245 nonce,
246 max_fee_per_gas: 0x28f000fff,
247 max_priority_fee_per_gas: 0x28f000fff,
248 gas_limit: 10,
249 to: Address::default(),
250 value: U256::from(3_u64),
251 input: Bytes::from(vec![1, 2]),
252 access_list: Default::default(),
253 authorization_list: Default::default(),
254 };
255
256 let signature = Signature::new(U256::default(), U256::default(), true);
257
258 OpTransactionSigned::new_unhashed(OpTypedTransaction::Eip7702(tx), signature)
259 }
260
261 #[test]
262 fn test_block_blob_gas_used_validation_isthmus() {
263 let chain_spec = OpChainSpecBuilder::default()
264 .isthmus_activated()
265 .genesis(OP_MAINNET.genesis.clone())
266 .chain(OP_MAINNET.chain)
267 .build();
268
269 let transaction = mock_tx(0);
271
272 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
273
274 let header = Header {
275 base_fee_per_gas: Some(1337),
276 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
277 blob_gas_used: Some(0),
278 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
279 &transaction,
280 )),
281 timestamp: u64::MAX,
282 ..Default::default()
283 };
284 let body = BlockBody {
285 transactions: vec![transaction],
286 ommers: vec![],
287 withdrawals: Some(Withdrawals::default()),
288 };
289
290 let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });
291
292 let pre_execution = beacon_consensus.validate_block_pre_execution(&block);
294
295 assert!(pre_execution.is_ok());
296 }
297
298 #[test]
299 fn test_block_blob_gas_used_validation_failure_isthmus() {
300 let chain_spec = OpChainSpecBuilder::default()
301 .isthmus_activated()
302 .genesis(OP_MAINNET.genesis.clone())
303 .chain(OP_MAINNET.chain)
304 .build();
305
306 let transaction = mock_tx(0);
308
309 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
310
311 let header = Header {
312 base_fee_per_gas: Some(1337),
313 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
314 blob_gas_used: Some(10),
315 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
316 &transaction,
317 )),
318 timestamp: u64::MAX,
319 ..Default::default()
320 };
321 let body = BlockBody {
322 transactions: vec![transaction],
323 ommers: vec![],
324 withdrawals: Some(Withdrawals::default()),
325 };
326
327 let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });
328
329 let pre_execution = beacon_consensus.validate_block_pre_execution(&block);
331
332 assert!(pre_execution.is_err());
333 assert_eq!(
334 pre_execution.unwrap_err(),
335 ConsensusError::BlobGasUsedDiff(GotExpected { got: 10, expected: 0 })
336 );
337 }
338
339 #[test]
340 fn test_block_blob_gas_used_validation_jovian() {
341 const BLOB_GAS_USED: u64 = 1000;
342 const GAS_USED: u64 = 10;
343
344 let chain_spec = OpChainSpecBuilder::default()
345 .jovian_activated()
346 .genesis(OP_MAINNET.genesis.clone())
347 .chain(OP_MAINNET.chain)
348 .build();
349
350 let transaction = mock_tx(0);
352
353 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
354
355 let receipt = OpReceipt::Eip7702(Receipt {
356 status: Eip658Value::success(),
357 cumulative_gas_used: GAS_USED,
358 logs: vec![],
359 });
360
361 let header = Header {
362 base_fee_per_gas: Some(1337),
363 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
364 blob_gas_used: Some(BLOB_GAS_USED),
365 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
366 &transaction,
367 )),
368 timestamp: u64::MAX,
369 gas_used: GAS_USED,
370 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(
371 &receipt.with_bloom_ref(),
372 )),
373 logs_bloom: receipt.bloom(),
374 ..Default::default()
375 };
376 let body = BlockBody {
377 transactions: vec![transaction],
378 ommers: vec![],
379 withdrawals: Some(Withdrawals::default()),
380 };
381
382 let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });
383
384 let result = BlockExecutionResult::<OpReceipt> {
385 blob_gas_used: BLOB_GAS_USED,
386 receipts: vec![receipt],
387 requests: Requests::default(),
388 gas_used: GAS_USED,
389 };
390
391 let pre_execution = beacon_consensus.validate_block_pre_execution(&block);
393
394 assert!(pre_execution.is_ok());
395
396 let block = RecoveredBlock::new_sealed(block, vec![Address::default()]);
397
398 let post_execution = <OpBeaconConsensus<OpChainSpec> as FullConsensus<OpPrimitives>>::validate_block_post_execution(
399 &beacon_consensus,
400 &block,
401 &result
402 );
403
404 assert!(post_execution.is_ok());
406 }
407
408 #[test]
409 fn test_block_blob_gas_used_validation_failure_jovian() {
410 const BLOB_GAS_USED: u64 = 1000;
411 const GAS_USED: u64 = 10;
412
413 let chain_spec = OpChainSpecBuilder::default()
414 .jovian_activated()
415 .genesis(OP_MAINNET.genesis.clone())
416 .chain(OP_MAINNET.chain)
417 .build();
418
419 let transaction = mock_tx(0);
421
422 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
423
424 let receipt = OpReceipt::Eip7702(Receipt {
425 status: Eip658Value::success(),
426 cumulative_gas_used: GAS_USED,
427 logs: vec![],
428 });
429
430 let header = Header {
431 base_fee_per_gas: Some(1337),
432 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
433 blob_gas_used: Some(BLOB_GAS_USED),
434 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
435 &transaction,
436 )),
437 gas_used: GAS_USED,
438 timestamp: u64::MAX,
439 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
440 logs_bloom: receipt.bloom(),
441 ..Default::default()
442 };
443 let body = BlockBody {
444 transactions: vec![transaction],
445 ommers: vec![],
446 withdrawals: Some(Withdrawals::default()),
447 };
448
449 let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });
450
451 let result = BlockExecutionResult::<OpReceipt> {
452 blob_gas_used: BLOB_GAS_USED + 1,
453 receipts: vec![receipt],
454 requests: Requests::default(),
455 gas_used: GAS_USED,
456 };
457
458 let pre_execution = beacon_consensus.validate_block_pre_execution(&block);
460
461 assert!(pre_execution.is_ok());
462
463 let block = RecoveredBlock::new_sealed(block, vec![Address::default()]);
464
465 let post_execution = <OpBeaconConsensus<OpChainSpec> as FullConsensus<OpPrimitives>>::validate_block_post_execution(
466 &beacon_consensus,
467 &block,
468 &result
469 );
470
471 assert!(post_execution.is_err());
473 assert_eq!(
474 post_execution.unwrap_err(),
475 ConsensusError::BlobGasUsedDiff(GotExpected {
476 got: BLOB_GAS_USED + 1,
477 expected: BLOB_GAS_USED,
478 })
479 );
480 }
481
482 #[test]
483 fn test_header_min_base_fee_validation() {
484 const MIN_BASE_FEE: u64 = 1000;
485
486 let chain_spec = OpChainSpecBuilder::default()
487 .jovian_activated()
488 .genesis(OP_MAINNET.genesis.clone())
489 .chain(OP_MAINNET.chain)
490 .build();
491
492 let transaction = mock_tx(0);
494
495 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
496
497 let receipt = OpReceipt::Eip7702(Receipt {
498 status: Eip658Value::success(),
499 cumulative_gas_used: 0,
500 logs: vec![],
501 });
502
503 let parent = Header {
504 number: 0,
505 base_fee_per_gas: Some(MIN_BASE_FEE / 10),
506 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
507 blob_gas_used: Some(0),
508 excess_blob_gas: Some(0),
509 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
510 &transaction,
511 )),
512 gas_used: 0,
513 timestamp: u64::MAX - 1,
514 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
515 logs_bloom: receipt.bloom(),
516 extra_data: encode_jovian_extra_data(
517 Default::default(),
518 BaseFeeParams::optimism(),
519 MIN_BASE_FEE,
520 )
521 .unwrap(),
522 ..Default::default()
523 };
524 let parent = SealedHeader::seal_slow(parent);
525
526 let header = Header {
527 number: 1,
528 base_fee_per_gas: Some(MIN_BASE_FEE),
529 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
530 blob_gas_used: Some(0),
531 excess_blob_gas: Some(0),
532 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
533 &transaction,
534 )),
535 gas_used: 0,
536 timestamp: u64::MAX,
537 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
538 logs_bloom: receipt.bloom(),
539 parent_hash: parent.hash(),
540 ..Default::default()
541 };
542 let header = SealedHeader::seal_slow(header);
543
544 let result = beacon_consensus.validate_header_against_parent(&header, &parent);
545
546 assert!(result.is_ok());
547 }
548
549 #[test]
550 fn test_header_min_base_fee_validation_failure() {
551 const MIN_BASE_FEE: u64 = 1000;
552
553 let chain_spec = OpChainSpecBuilder::default()
554 .jovian_activated()
555 .genesis(OP_MAINNET.genesis.clone())
556 .chain(OP_MAINNET.chain)
557 .build();
558
559 let transaction = mock_tx(0);
561
562 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
563
564 let receipt = OpReceipt::Eip7702(Receipt {
565 status: Eip658Value::success(),
566 cumulative_gas_used: 0,
567 logs: vec![],
568 });
569
570 let parent = Header {
571 number: 0,
572 base_fee_per_gas: Some(MIN_BASE_FEE / 10),
573 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
574 blob_gas_used: Some(0),
575 excess_blob_gas: Some(0),
576 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
577 &transaction,
578 )),
579 gas_used: 0,
580 timestamp: u64::MAX - 1,
581 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
582 logs_bloom: receipt.bloom(),
583 extra_data: encode_jovian_extra_data(
584 Default::default(),
585 BaseFeeParams::optimism(),
586 MIN_BASE_FEE,
587 )
588 .unwrap(),
589 ..Default::default()
590 };
591 let parent = SealedHeader::seal_slow(parent);
592
593 let header = Header {
594 number: 1,
595 base_fee_per_gas: Some(MIN_BASE_FEE - 1),
596 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
597 blob_gas_used: Some(0),
598 excess_blob_gas: Some(0),
599 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
600 &transaction,
601 )),
602 gas_used: 0,
603 timestamp: u64::MAX,
604 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
605 logs_bloom: receipt.bloom(),
606 parent_hash: parent.hash(),
607 ..Default::default()
608 };
609 let header = SealedHeader::seal_slow(header);
610
611 let result = beacon_consensus.validate_header_against_parent(&header, &parent);
612
613 assert!(result.is_err());
614 assert_eq!(
615 result.unwrap_err(),
616 ConsensusError::BaseFeeDiff(GotExpected {
617 got: MIN_BASE_FEE - 1,
618 expected: MIN_BASE_FEE,
619 })
620 );
621 }
622
623 #[test]
624 fn test_header_da_footprint_validation() {
625 const MIN_BASE_FEE: u64 = 100_000;
626 const DA_FOOTPRINT: u64 = GAS_LIMIT - 1;
627 const GAS_LIMIT: u64 = 100_000_000;
628
629 let chain_spec = OpChainSpecBuilder::default()
630 .jovian_activated()
631 .genesis(OP_MAINNET.genesis.clone())
632 .chain(OP_MAINNET.chain)
633 .build();
634
635 let transaction = mock_tx(0);
637
638 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
639
640 let receipt = OpReceipt::Eip7702(Receipt {
641 status: Eip658Value::success(),
642 cumulative_gas_used: 0,
643 logs: vec![],
644 });
645
646 let parent = Header {
647 number: 0,
648 base_fee_per_gas: Some(MIN_BASE_FEE),
649 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
650 blob_gas_used: Some(DA_FOOTPRINT),
651 excess_blob_gas: Some(0),
652 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
653 &transaction,
654 )),
655 gas_used: 0,
656 timestamp: u64::MAX - 1,
657 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
658 logs_bloom: receipt.bloom(),
659 extra_data: encode_jovian_extra_data(
660 Default::default(),
661 BaseFeeParams::optimism(),
662 MIN_BASE_FEE,
663 )
664 .unwrap(),
665 gas_limit: GAS_LIMIT,
666 ..Default::default()
667 };
668 let parent = SealedHeader::seal_slow(parent);
669
670 let header = Header {
671 number: 1,
672 base_fee_per_gas: Some(MIN_BASE_FEE + MIN_BASE_FEE / 10),
673 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
674 blob_gas_used: Some(DA_FOOTPRINT),
675 excess_blob_gas: Some(0),
676 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
677 &transaction,
678 )),
679 gas_used: 0,
680 timestamp: u64::MAX,
681 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
682 logs_bloom: receipt.bloom(),
683 parent_hash: parent.hash(),
684 ..Default::default()
685 };
686 let header = SealedHeader::seal_slow(header);
687
688 let result = beacon_consensus.validate_header_against_parent(&header, &parent);
689
690 assert!(result.is_ok());
691 }
692
693 #[test]
694 fn test_header_isthmus_validation() {
695 const MIN_BASE_FEE: u64 = 100_000;
696 const DA_FOOTPRINT: u64 = GAS_LIMIT - 1;
697 const GAS_LIMIT: u64 = 100_000_000;
698
699 let chain_spec = OpChainSpecBuilder::default()
700 .isthmus_activated()
701 .genesis(OP_MAINNET.genesis.clone())
702 .chain(OP_MAINNET.chain)
703 .build();
704
705 let transaction = mock_tx(0);
707
708 let beacon_consensus = OpBeaconConsensus::new(Arc::new(chain_spec));
709
710 let receipt = OpReceipt::Eip7702(Receipt {
711 status: Eip658Value::success(),
712 cumulative_gas_used: 0,
713 logs: vec![],
714 });
715
716 let parent = Header {
717 number: 0,
718 base_fee_per_gas: Some(MIN_BASE_FEE),
719 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
720 blob_gas_used: Some(DA_FOOTPRINT),
721 excess_blob_gas: Some(0),
722 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
723 &transaction,
724 )),
725 gas_used: 0,
726 timestamp: u64::MAX - 1,
727 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
728 logs_bloom: receipt.bloom(),
729 extra_data: encode_holocene_extra_data(Default::default(), BaseFeeParams::optimism())
730 .unwrap(),
731 gas_limit: GAS_LIMIT,
732 ..Default::default()
733 };
734 let parent = SealedHeader::seal_slow(parent);
735
736 let header = Header {
737 number: 1,
738 base_fee_per_gas: Some(MIN_BASE_FEE - 2 * MIN_BASE_FEE / 100),
739 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
740 blob_gas_used: Some(DA_FOOTPRINT),
741 excess_blob_gas: Some(0),
742 transactions_root: proofs::calculate_transaction_root(std::slice::from_ref(
743 &transaction,
744 )),
745 gas_used: 0,
746 timestamp: u64::MAX,
747 receipts_root: proofs::calculate_receipt_root(std::slice::from_ref(&receipt)),
748 logs_bloom: receipt.bloom(),
749 parent_hash: parent.hash(),
750 ..Default::default()
751 };
752 let header = SealedHeader::seal_slow(header);
753
754 let result = beacon_consensus.validate_header_against_parent(&header, &parent);
755
756 assert!(result.is_err());
757 assert_eq!(
758 result.unwrap_err(),
759 ConsensusError::BlobGasUsedDiff(GotExpected { got: DA_FOOTPRINT, expected: 0 })
760 );
761 }
762}