@@ -519,6 +519,42 @@ pub const INITIAL_BASE_FEE: u64 = 1_000_000_000;
519519/// CHANGE(taiko): add ShastaInitialBaseFee for Shasta fork.
520520pub const SHASTA_INITIAL_BASE_FEE : u64 = 25_000_000 ;
521521
522+ fn parent_block_time_from_grandparent (
523+ parent : & Header ,
524+ l2_grandparent_header : Option < & Header > ,
525+ ) -> Option < u64 > {
526+ if parent. number == 0 {
527+ return Some ( BLOCK_TIME_TARGET ) ;
528+ }
529+
530+ let Some ( grandparent) = l2_grandparent_header else {
531+ warn ! (
532+ "missing L2 grandparent header for parent block {}" ,
533+ parent. number
534+ ) ;
535+ return None ;
536+ } ;
537+
538+ if grandparent. number + 1 != parent. number {
539+ warn ! (
540+ "L2 grandparent number mismatch: grandparent {}, parent {}" ,
541+ grandparent. number, parent. number
542+ ) ;
543+ return None ;
544+ }
545+
546+ let grandparent_hash = grandparent. hash_slow ( ) ;
547+ if grandparent_hash != parent. parent_hash {
548+ warn ! (
549+ "L2 grandparent hash mismatch: expected {}, found {}" ,
550+ parent. parent_hash, grandparent_hash
551+ ) ;
552+ return None ;
553+ }
554+
555+ Some ( parent. timestamp . saturating_sub ( grandparent. timestamp ) )
556+ }
557+
522558pub fn validate_shasta_block_base_fee (
523559 block_guest_inputs : & [ GuestInput ] ,
524560 use_init_base_fee : bool ,
@@ -537,14 +573,11 @@ pub fn validate_shasta_block_base_fee(
537573 } else {
538574 // Calculate parent_block_time = parent.timestamp - grandparent.timestamp
539575 // According to EIP-4396, we need the time between parent and grandparent
540- let parent_block_time = if let Some ( grandparent) = l2_grandparent_header {
541- block_guest_inputs[ 0 ]
542- . parent_header
543- . timestamp
544- . saturating_sub ( grandparent. timestamp )
545- } else {
546- // Fallback: if no parent's parent (e.g., first block ever), use default block time target
547- BLOCK_TIME_TARGET
576+ let Some ( parent_block_time) = parent_block_time_from_grandparent (
577+ & block_guest_inputs[ 0 ] . parent_header ,
578+ l2_grandparent_header,
579+ ) else {
580+ return false ;
548581 } ;
549582 let first_block_base_fee = calc_next_shasta_base_fee (
550583 block_guest_inputs[ 0 ] . parent_header . gas_limit ,
@@ -630,6 +663,15 @@ mod tests {
630663
631664 use super :: calc_next_shasta_base_fee;
632665
666+ fn base_fee_guest_input ( parent_header : Header , block_base_fee : u64 ) -> GuestInput {
667+ let mut input = GuestInput {
668+ parent_header,
669+ ..Default :: default ( )
670+ } ;
671+ input. block . header . base_fee_per_gas = Some ( block_base_fee) ;
672+ input
673+ }
674+
633675 #[ test]
634676 fn test_calc_next_shasta_base_fee ( ) {
635677 let parent_gas_limit = 16_000_000 ;
@@ -666,6 +708,70 @@ mod tests {
666708 assert_eq ! ( result, 5_059_102 ) ;
667709 }
668710
711+ #[ test]
712+ fn test_base_fee_rejects_missing_grandparent_for_non_genesis_parent ( ) {
713+ let parent_header = Header {
714+ number : 10 ,
715+ timestamp : 100 ,
716+ gas_limit : 16_000_000 ,
717+ gas_used : 15_000_000 ,
718+ base_fee_per_gas : Some ( 5_000_000 ) ,
719+ ..Default :: default ( )
720+ } ;
721+ let default_time_base_fee = calc_next_shasta_base_fee (
722+ parent_header. gas_limit ,
723+ parent_header. gas_used ,
724+ parent_header. base_fee_per_gas . unwrap ( ) ,
725+ super :: BLOCK_TIME_TARGET ,
726+ super :: DEFAULT_ELASTICITY_MULTIPLIER ,
727+ super :: DEFAULT_BASE_FEE_CHANGE_DENOMINATOR ,
728+ super :: MIN_BASE_FEE_SHASTA ,
729+ ) ;
730+ let input = base_fee_guest_input ( parent_header, default_time_base_fee) ;
731+
732+ assert ! ( !super :: validate_shasta_block_base_fee(
733+ & [ input] ,
734+ false ,
735+ None ,
736+ super :: MIN_BASE_FEE_SHASTA ,
737+ ) ) ;
738+ }
739+
740+ #[ test]
741+ fn test_base_fee_rejects_unlinked_grandparent ( ) {
742+ let grandparent = Header {
743+ number : 9 ,
744+ timestamp : 98 ,
745+ ..Default :: default ( )
746+ } ;
747+ let parent_header = Header {
748+ number : 10 ,
749+ timestamp : 100 ,
750+ gas_limit : 16_000_000 ,
751+ gas_used : 15_000_000 ,
752+ base_fee_per_gas : Some ( 5_000_000 ) ,
753+ parent_hash : B256 :: repeat_byte ( 0x42 ) ,
754+ ..Default :: default ( )
755+ } ;
756+ let grandparent_time_base_fee = calc_next_shasta_base_fee (
757+ parent_header. gas_limit ,
758+ parent_header. gas_used ,
759+ parent_header. base_fee_per_gas . unwrap ( ) ,
760+ parent_header. timestamp - grandparent. timestamp ,
761+ super :: DEFAULT_ELASTICITY_MULTIPLIER ,
762+ super :: DEFAULT_BASE_FEE_CHANGE_DENOMINATOR ,
763+ super :: MIN_BASE_FEE_SHASTA ,
764+ ) ;
765+ let input = base_fee_guest_input ( parent_header, grandparent_time_base_fee) ;
766+
767+ assert ! ( !super :: validate_shasta_block_base_fee(
768+ & [ input] ,
769+ false ,
770+ Some ( & grandparent) ,
771+ super :: MIN_BASE_FEE_SHASTA ,
772+ ) ) ;
773+ }
774+
669775 #[ test]
670776 fn test_anchor_range_includes_max_offset ( ) {
671777 let l1_header_number = 1000u64 ;
0 commit comments