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

Commit 769a3f9

Browse files
authored
fix: validate Shasta grandparent base fee (#697)
* fix: validate shasta grandparent base fee * chore: bump gaiko submodule
1 parent f9f37ca commit 769a3f9

5 files changed

Lines changed: 132 additions & 26 deletions

File tree

lib/src/utils/shasta.rs

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

160156
protocol_manifest
161157
.blocks

lib/src/utils/shasta_rules.rs

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

provers/sgx/config/sgx-guest.docker.manifest.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ libos.entrypoint = "/sgx-guest"
99
loader.entrypoint = "file:{{ gramine.libos }}"
1010
loader.env.HOME = "/root"
1111
loader.env.LD_LIBRARY_PATH = "/lib:{{ arch_libdir }}"
12+
loader.env.DEV_PACAYA_HEIGHT = "0"
13+
loader.env.DEV_ONTAKE_HEIGHT = "0"
14+
loader.env.DEV_SHASTA_TIMESTAMP = "0"
1215
loader.env.RUST_LOG = "info"
1316
loader.insecure__use_cmdline_argv = true
1417
loader.log_level = "{{ log_level }}"

provers/sgx/config/sgx-guest.local.manifest.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ loader.env.LD_LIBRARY_PATH = "/lib:{{ arch_libdir }}"
1212
loader.env.DEV_PACAYA_HEIGHT = "0"
1313
loader.env.DEV_ONTAKE_HEIGHT = "0"
1414
loader.env.DEV_SHASTA_HEIGHT = "0"
15+
loader.env.DEV_SHASTA_TIMESTAMP = "0"
1516
loader.env.RUST_LOG = "debug"
1617
loader.insecure__use_cmdline_argv = true
1718
loader.log_level = "{{ log_level }}"

0 commit comments

Comments
 (0)