Skip to content

Commit c114952

Browse files
committed
Wire safeTarget store checks in the fork choice test harness so that
the spec-test runner actually validates safe_target behavior end-to-end. Today the harness explicitly rejects any fixture carrying a \'safeTarget\' field with "'\safeTarget\' check not supported", and it has no field at all for the leanSpec #680 schema (safeTargetSlot, safeTargetRootLabel). The result is that the safe_target semantics shipped in PR #316 have no fixture-level coverage: regenerated fixtures are silently parsed without asserting on the field, and any test using the legacy \'safeTarget\' field errors out instead of validating. Changes: - Add safeTargetSlot and safeTargetRootLabel to StoreChecks, mirroring the latestJustified* / latestFinalized* pattern. - Replace the rejection branch with the same label-resolution pattern used for justified/finalized roots, falling back from safeTarget to safeTargetRootLabel via the step\'s block registry. - Add validation blocks for safeTargetSlot (against st.safe_target_slot()) and the resolved root (against st.safe_target()). - Reorganize StoreChecks into logical groups and drop the now-stale "Unsupported fields (will error if present)" comment, which was misleading: only safeTarget actually errored, everything else was validated. This change is forward-compatible: no current fixture in leanSpec/fixtures/consensus/ contains any safeTarget* field, so the new validation paths are dormant until LEAN_SPEC_COMMIT_HASH is bumped to a revision that includes leanSpec #680. Verified with cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings, and the forkchoice_spectests harness (47 passing fixtures unchanged). Pre-existing failures from a stale leanSpec submodule checkout (1 fork-choice fixture, 34 ssz fixtures) reproduce identically on main and are unrelated to this change.
1 parent 507a35c commit c114952

2 files changed

Lines changed: 53 additions & 13 deletions

File tree

crates/blockchain/tests/forkchoice_spectests.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ fn validate_checks(
221221
.as_ref()
222222
.and_then(|label| block_registry.get(label).copied())
223223
});
224-
if checks.safe_target.is_some() {
225-
return Err(format!("Step {}: 'safeTarget' check not supported", step_idx).into());
226-
}
224+
let resolved_safe_target_root = checks.safe_target.or_else(|| {
225+
checks
226+
.safe_target_root_label
227+
.as_ref()
228+
.and_then(|label| block_registry.get(label).copied())
229+
});
227230
// Validate attestationTargetSlot
228231
if let Some(expected_slot) = checks.attestation_target_slot {
229232
let target = store::get_attestation_target(st);
@@ -330,6 +333,30 @@ fn validate_checks(
330333
}
331334
}
332335

336+
// Validate safeTargetSlot
337+
if let Some(expected_slot) = checks.safe_target_slot {
338+
let actual_slot = st.safe_target_slot();
339+
if actual_slot != expected_slot {
340+
return Err(format!(
341+
"Step {}: safeTargetSlot mismatch: expected {}, got {}",
342+
step_idx, expected_slot, actual_slot
343+
)
344+
.into());
345+
}
346+
}
347+
348+
// Validate safeTarget root (resolved from label if root not provided)
349+
if let Some(ref expected_root) = resolved_safe_target_root {
350+
let actual_root = st.safe_target();
351+
if actual_root != *expected_root {
352+
return Err(format!(
353+
"Step {}: safeTarget mismatch: expected {:?}, got {:?}",
354+
step_idx, expected_root, actual_root
355+
)
356+
.into());
357+
}
358+
}
359+
333360
// Validate attestationChecks
334361
if let Some(ref att_checks) = checks.attestation_checks {
335362
for att_check in att_checks {

crates/blockchain/tests/types.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,38 +136,51 @@ impl BlockStepData {
136136
// Check Types
137137
// ============================================================================
138138

139+
/// Store-state expectations for a fork choice test step.
140+
///
141+
/// All fields are optional; only fields explicitly set by the fixture are validated.
142+
/// Root-typed fields have a `*RootLabel` companion that resolves a block label via the
143+
/// step's block registry, mirroring the leanSpec fixture schema.
139144
#[derive(Debug, Clone, Deserialize)]
140145
pub struct StoreChecks {
141-
// Validated fields
146+
/// Expected store time in intervals since genesis.
147+
pub time: Option<u64>,
148+
142149
#[serde(rename = "headSlot")]
143150
pub head_slot: Option<u64>,
144151
#[serde(rename = "headRoot")]
145152
pub head_root: Option<H256>,
146-
#[serde(rename = "attestationChecks")]
147-
pub attestation_checks: Option<Vec<AttestationCheck>>,
148-
#[serde(rename = "attestationTargetSlot")]
149-
pub attestation_target_slot: Option<u64>,
150-
151-
/// Expected store time in intervals since genesis (validated when present).
152-
pub time: Option<u64>,
153-
154-
// Unsupported fields (will error if present in test fixture)
155153
#[serde(rename = "headRootLabel")]
156154
pub head_root_label: Option<String>,
155+
157156
#[serde(rename = "latestJustifiedSlot")]
158157
pub latest_justified_slot: Option<u64>,
159158
#[serde(rename = "latestJustifiedRoot")]
160159
pub latest_justified_root: Option<H256>,
161160
#[serde(rename = "latestJustifiedRootLabel")]
162161
pub latest_justified_root_label: Option<String>,
162+
163163
#[serde(rename = "latestFinalizedSlot")]
164164
pub latest_finalized_slot: Option<u64>,
165165
#[serde(rename = "latestFinalizedRoot")]
166166
pub latest_finalized_root: Option<H256>,
167167
#[serde(rename = "latestFinalizedRootLabel")]
168168
pub latest_finalized_root_label: Option<String>,
169+
170+
/// Legacy single-field schema; expected safe target block root.
169171
#[serde(rename = "safeTarget")]
170172
pub safe_target: Option<H256>,
173+
/// Expected slot of the safe target block (leanSpec #680 schema).
174+
#[serde(rename = "safeTargetSlot")]
175+
pub safe_target_slot: Option<u64>,
176+
/// Expected safe target block root by label reference (leanSpec #680 schema).
177+
#[serde(rename = "safeTargetRootLabel")]
178+
pub safe_target_root_label: Option<String>,
179+
180+
#[serde(rename = "attestationTargetSlot")]
181+
pub attestation_target_slot: Option<u64>,
182+
#[serde(rename = "attestationChecks")]
183+
pub attestation_checks: Option<Vec<AttestationCheck>>,
171184
#[serde(rename = "lexicographicHeadAmong")]
172185
pub lexicographic_head_among: Option<Vec<String>>,
173186
}

0 commit comments

Comments
 (0)