Skip to content

Commit 84f4709

Browse files
feat(consensus): require the exact 7-byte Shasta extraData layout (#221)
The drivers only ever produce the 7-byte [pctg | proposalId(6)] layout, the live chains carry it, and the proposalId consumers already need the 7-byte tail. Header validation previously enforced only the generic 32-byte cap, so any 0-32-byte extraData would import; pin the rule to the exact layout so a misbehaving block producer fails at import. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 6c4d199 commit 84f4709

3 files changed

Lines changed: 72 additions & 2 deletions

File tree

crates/consensus/src/validation/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::eip4396::{
2222
calculate_next_block_eip4396_base_fee,
2323
};
2424
use alethia_reth_chainspec::{TAIKO_MAINNET, hardfork::TaikoHardforks, spec::TaikoChainSpec};
25-
use alethia_reth_primitives::transaction::is_allowed_tx_type;
25+
use alethia_reth_primitives::{SHASTA_EXTRA_DATA_LEN, transaction::is_allowed_tx_type};
2626

2727
/// Anchor transaction selectors, gas rules, and validation functions.
2828
mod anchor;
@@ -152,6 +152,20 @@ where
152152
}
153153

154154
validate_header_extra_data(header, MAXIMUM_EXTRA_DATA_SIZE)?;
155+
156+
// Shasta extraData must be the 7-byte [pctg | proposalId(6)] layout — the only shape the
157+
// drivers produce and the live chains carry. Reject anything else at import so a
158+
// misbehaving block producer fails loudly here instead of minting headers whose embedded
159+
// proposalId consumers cannot decode.
160+
if self.chain_spec.is_shasta_active(header.timestamp()) &&
161+
header.extra_data().len() != SHASTA_EXTRA_DATA_LEN
162+
{
163+
return Err(ConsensusError::Other(format!(
164+
"invalid Shasta extra-data length: have {}, want {SHASTA_EXTRA_DATA_LEN}",
165+
header.extra_data().len()
166+
)));
167+
}
168+
155169
validate_header_gas(header)?;
156170
validate_header_base_fee(header, &self.chain_spec)
157171
}

crates/consensus/src/validation/tests.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ fn unzen_header_allows_nonzero_difficulty() {
174174
difficulty: U256::from(7_u64),
175175
base_fee_per_gas: Some(1),
176176
gas_limit: 30_000_000,
177+
extra_data: shasta_extra_data(),
177178
..Default::default()
178179
};
179180

@@ -182,6 +183,56 @@ fn unzen_header_allows_nonzero_difficulty() {
182183
.expect("Unzen headers should allow nonzero difficulty");
183184
}
184185

186+
#[test]
187+
fn shasta_header_requires_exact_extra_data_len() {
188+
let consensus = test_consensus(devnet_chain_spec());
189+
let base = Header {
190+
timestamp: 1,
191+
base_fee_per_gas: Some(1),
192+
gas_limit: 30_000_000,
193+
..Default::default()
194+
};
195+
196+
consensus
197+
.validate_header(&SealedHeader::new_unhashed(Header {
198+
extra_data: shasta_extra_data(),
199+
..base.clone()
200+
}))
201+
.expect("the 7-byte extraData layout should validate");
202+
203+
for (name, extra) in [
204+
("empty", Bytes::new()),
205+
("2 bytes", Bytes::from_static(&[75, 0])),
206+
("3 bytes", Bytes::from_static(&[75, 0, 1])),
207+
("12 bytes", Bytes::from_static(&[0; 12])),
208+
] {
209+
let err = consensus
210+
.validate_header(&SealedHeader::new_unhashed(Header {
211+
extra_data: extra,
212+
..base.clone()
213+
}))
214+
.expect_err(name);
215+
assert!(matches!(err, ConsensusError::Other(_)), "{name}: unexpected error {err:?}");
216+
}
217+
}
218+
219+
#[test]
220+
fn pre_shasta_header_has_no_extra_data_len_rule() {
221+
let mut chain_spec = devnet_chain_spec();
222+
chain_spec.inner.hardforks.insert(TaikoHardfork::Shasta, ForkCondition::Never);
223+
let consensus = test_consensus(chain_spec);
224+
let header = Header {
225+
timestamp: 1,
226+
base_fee_per_gas: Some(1),
227+
gas_limit: 30_000_000,
228+
..Default::default()
229+
};
230+
231+
consensus
232+
.validate_header(&SealedHeader::new_unhashed(header))
233+
.expect("pre-Shasta headers have no extraData length rule");
234+
}
235+
185236
#[test]
186237
fn unzen_post_execution_rejects_body_past_truncation_point() {
187238
let consensus = test_consensus(unzen_chain_spec());
@@ -273,6 +324,10 @@ fn test_consensus(chain_spec: TaikoChainSpec) -> TaikoBeaconConsensus {
273324
TaikoBeaconConsensus::new(Arc::new(chain_spec), Arc::new(NullBlockReader))
274325
}
275326

327+
fn shasta_extra_data() -> Bytes {
328+
Bytes::from_static(&[75, 0, 0, 0, 0, 0x4c, 0x81])
329+
}
330+
276331
fn devnet_chain_spec() -> TaikoChainSpec {
277332
(*TAIKO_DEVNET).as_ref().clone()
278333
}

crates/primitives/src/extra_data.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Helpers for decoding Taiko-specific block `extraData` fields.
22
3-
/// Minimum number of bytes required for Shasta extra data.
3+
/// Exact length of the Shasta extra data layout: `[basefeeSharingPctg | proposalId(6)]`.
4+
/// Header validation rejects Shasta headers whose extra data has any other length.
45
pub const SHASTA_EXTRA_DATA_LEN: usize = 7;
56

67
/// Returns the base fee sharing percentage encoded in Shasta extra data.

0 commit comments

Comments
 (0)