Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.

Commit 673e170

Browse files
committed
fix: validate shasta grandparent base fee
1 parent e499ba1 commit 673e170

2 files changed

Lines changed: 127 additions & 25 deletions

File tree

lib/src/utils/shasta.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,6 @@ pub fn generate_transactions_for_shasta_blocks(
122122
last_anchor_block_number,
123123
) =>
124124
{
125-
// parent is pacaya means this is the first shasta block
126-
let use_init_base_fee =
127-
guest_batch_input.inputs[0].parent_header.number == 0;
128-
129-
let min_base_fee = min_base_fee_for_shasta_chain(
130-
guest_batch_input.taiko.chain_spec.chain_id(),
131-
);
132-
//TODO: move to validate_normal_proposal_manifest
133-
if !validate_shasta_block_base_fee(
134-
&guest_batch_input.inputs,
135-
use_init_base_fee,
136-
guest_batch_input.taiko.l2_grandparent_header.as_ref(),
137-
min_base_fee,
138-
) {
139-
warn!("shasta block base fee is invalid, need double check");
140-
assert!(false, "shasta block base fee is invalid");
141-
}
142125
manifest
143126
}
144127
_ => {
@@ -155,6 +138,19 @@ pub fn generate_transactions_for_shasta_blocks(
155138
manifest
156139
}
157140
};
141+
// parent is pacaya means this is the first shasta block
142+
let use_init_base_fee = guest_batch_input.inputs[0].parent_header.number == 0;
143+
let min_base_fee =
144+
min_base_fee_for_shasta_chain(guest_batch_input.taiko.chain_spec.chain_id());
145+
if !validate_shasta_block_base_fee(
146+
&guest_batch_input.inputs,
147+
use_init_base_fee,
148+
guest_batch_input.taiko.l2_grandparent_header.as_ref(),
149+
min_base_fee,
150+
) {
151+
warn!("shasta block base fee is invalid, need double check");
152+
assert!(false, "shasta block base fee is invalid");
153+
}
158154

159155
protocol_manifest
160156
.blocks

lib/src/utils/shasta_rules.rs

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,42 @@ pub const INITIAL_BASE_FEE: u64 = 1_000_000_000;
527527
/// CHANGE(taiko): add ShastaInitialBaseFee for Shasta fork.
528528
pub 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+
530566
pub 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

Comments
 (0)