@@ -527,6 +527,42 @@ pub const INITIAL_BASE_FEE: u64 = 1_000_000_000;
527527/// CHANGE(taiko): add ShastaInitialBaseFee for Shasta fork.
528528pub const SHASTA_INITIAL_BASE_FEE : u64 = 25_000_000 ;
529529
530+ fn parent_block_time_from_grandparent (
531+ parent : & Header ,
532+ l2_grandparent_header : Option < & Header > ,
533+ ) -> Option < u64 > {
534+ if parent. number == 0 {
535+ return Some ( BLOCK_TIME_TARGET ) ;
536+ }
537+
538+ let Some ( grandparent) = l2_grandparent_header else {
539+ warn ! (
540+ "missing L2 grandparent header for parent block {}" ,
541+ parent. number
542+ ) ;
543+ return None ;
544+ } ;
545+
546+ if grandparent. number + 1 != parent. number {
547+ warn ! (
548+ "L2 grandparent number mismatch: grandparent {}, parent {}" ,
549+ grandparent. number, parent. number
550+ ) ;
551+ return None ;
552+ }
553+
554+ let grandparent_hash = grandparent. hash_slow ( ) ;
555+ if grandparent_hash != parent. parent_hash {
556+ warn ! (
557+ "L2 grandparent hash mismatch: expected {}, found {}" ,
558+ parent. parent_hash, grandparent_hash
559+ ) ;
560+ return None ;
561+ }
562+
563+ Some ( parent. timestamp . saturating_sub ( grandparent. timestamp ) )
564+ }
565+
530566pub fn validate_shasta_block_base_fee (
531567 block_guest_inputs : & [ GuestInput ] ,
532568 use_init_base_fee : bool ,
@@ -545,14 +581,11 @@ pub fn validate_shasta_block_base_fee(
545581 } else {
546582 // Calculate parent_block_time = parent.timestamp - grandparent.timestamp
547583 // According to EIP-4396, we need the time between parent and grandparent
548- let parent_block_time = if let Some ( grandparent) = l2_grandparent_header {
549- block_guest_inputs[ 0 ]
550- . parent_header
551- . timestamp
552- . saturating_sub ( grandparent. timestamp )
553- } else {
554- // Fallback: if no parent's parent (e.g., first block ever), use default block time target
555- BLOCK_TIME_TARGET
584+ let Some ( parent_block_time) = parent_block_time_from_grandparent (
585+ & block_guest_inputs[ 0 ] . parent_header ,
586+ l2_grandparent_header,
587+ ) else {
588+ return false ;
556589 } ;
557590 let first_block_base_fee = calc_next_shasta_base_fee (
558591 block_guest_inputs[ 0 ] . parent_header . gas_limit ,
@@ -638,6 +671,15 @@ mod tests {
638671
639672 use super :: calc_next_shasta_base_fee;
640673
674+ fn base_fee_guest_input ( parent_header : Header , block_base_fee : u64 ) -> GuestInput {
675+ let mut input = GuestInput {
676+ parent_header,
677+ ..Default :: default ( )
678+ } ;
679+ input. block . header . base_fee_per_gas = Some ( block_base_fee) ;
680+ input
681+ }
682+
641683 #[ test]
642684 fn test_calc_next_shasta_base_fee ( ) {
643685 let parent_gas_limit = 16_000_000 ;
@@ -674,6 +716,70 @@ mod tests {
674716 assert_eq ! ( result, 5_059_102 ) ;
675717 }
676718
719+ #[ test]
720+ fn test_base_fee_rejects_missing_grandparent_for_non_genesis_parent ( ) {
721+ let parent_header = Header {
722+ number : 10 ,
723+ timestamp : 100 ,
724+ gas_limit : 16_000_000 ,
725+ gas_used : 15_000_000 ,
726+ base_fee_per_gas : Some ( 5_000_000 ) ,
727+ ..Default :: default ( )
728+ } ;
729+ let default_time_base_fee = calc_next_shasta_base_fee (
730+ parent_header. gas_limit ,
731+ parent_header. gas_used ,
732+ parent_header. base_fee_per_gas . unwrap ( ) ,
733+ super :: BLOCK_TIME_TARGET ,
734+ super :: DEFAULT_ELASTICITY_MULTIPLIER ,
735+ super :: DEFAULT_BASE_FEE_CHANGE_DENOMINATOR ,
736+ super :: MIN_BASE_FEE_SHASTA ,
737+ ) ;
738+ let input = base_fee_guest_input ( parent_header, default_time_base_fee) ;
739+
740+ assert ! ( !super :: validate_shasta_block_base_fee(
741+ & [ input] ,
742+ false ,
743+ None ,
744+ super :: MIN_BASE_FEE_SHASTA ,
745+ ) ) ;
746+ }
747+
748+ #[ test]
749+ fn test_base_fee_rejects_unlinked_grandparent ( ) {
750+ let grandparent = Header {
751+ number : 9 ,
752+ timestamp : 98 ,
753+ ..Default :: default ( )
754+ } ;
755+ let parent_header = Header {
756+ number : 10 ,
757+ timestamp : 100 ,
758+ gas_limit : 16_000_000 ,
759+ gas_used : 15_000_000 ,
760+ base_fee_per_gas : Some ( 5_000_000 ) ,
761+ parent_hash : B256 :: repeat_byte ( 0x42 ) ,
762+ ..Default :: default ( )
763+ } ;
764+ let grandparent_time_base_fee = calc_next_shasta_base_fee (
765+ parent_header. gas_limit ,
766+ parent_header. gas_used ,
767+ parent_header. base_fee_per_gas . unwrap ( ) ,
768+ parent_header. timestamp - grandparent. timestamp ,
769+ super :: DEFAULT_ELASTICITY_MULTIPLIER ,
770+ super :: DEFAULT_BASE_FEE_CHANGE_DENOMINATOR ,
771+ super :: MIN_BASE_FEE_SHASTA ,
772+ ) ;
773+ let input = base_fee_guest_input ( parent_header, grandparent_time_base_fee) ;
774+
775+ assert ! ( !super :: validate_shasta_block_base_fee(
776+ & [ input] ,
777+ false ,
778+ Some ( & grandparent) ,
779+ super :: MIN_BASE_FEE_SHASTA ,
780+ ) ) ;
781+ }
782+
677783 #[ test]
678784 fn test_anchor_range_includes_max_offset ( ) {
679785 let l1_header_number = 1000u64 ;
0 commit comments