diff --git a/Makefile b/Makefile index 013b157..e0de9e0 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ docker-build: ## 🐳 Build the Docker image -t ghcr.io/lambdaclass/ethlambda:$(DOCKER_TAG) . @echo -# 2026-04-28: bump for leanSpec PR #682 (validate_attestation future-slot bound). -LEAN_SPEC_COMMIT_HASH:=62eff6e7e6041a283877a546a07cb3b83f4f7d5b +# 2026-04-29 +LEAN_SPEC_COMMIT_HASH:=495c29d49f2b12b7cc240c4028e15d4253a7d54e leanSpec: git clone https://github.com/leanEthereum/leanSpec.git --single-branch diff --git a/crates/blockchain/tests/forkchoice_spectests.rs b/crates/blockchain/tests/forkchoice_spectests.rs index 13d8cf7..23f4503 100644 --- a/crates/blockchain/tests/forkchoice_spectests.rs +++ b/crates/blockchain/tests/forkchoice_spectests.rs @@ -21,7 +21,7 @@ const SUPPORTED_FIXTURE_FORMAT: &str = "fork_choice_test"; mod common; mod types; -/// List of skipped tests +/// List of skipped tests. const SKIP_TESTS: &[&str] = &[]; fn run(path: &Path) -> datatest_stable::Result<()> { @@ -221,9 +221,12 @@ fn validate_checks( .as_ref() .and_then(|label| block_registry.get(label).copied()) }); - if checks.safe_target.is_some() { - return Err(format!("Step {}: 'safeTarget' check not supported", step_idx).into()); - } + let resolved_safe_target_root = checks.safe_target.or_else(|| { + checks + .safe_target_root_label + .as_ref() + .and_then(|label| block_registry.get(label).copied()) + }); // Validate attestationTargetSlot if let Some(expected_slot) = checks.attestation_target_slot { let target = store::get_attestation_target(st); @@ -330,6 +333,30 @@ fn validate_checks( } } + // Validate safeTargetSlot + if let Some(expected_slot) = checks.safe_target_slot { + let actual_slot = st.safe_target_slot(); + if actual_slot != expected_slot { + return Err(format!( + "Step {}: safeTargetSlot mismatch: expected {}, got {}", + step_idx, expected_slot, actual_slot + ) + .into()); + } + } + + // Validate safeTarget root (resolved from label if root not provided) + if let Some(ref expected_root) = resolved_safe_target_root { + let actual_root = st.safe_target(); + if actual_root != *expected_root { + return Err(format!( + "Step {}: safeTarget mismatch: expected {:?}, got {:?}", + step_idx, expected_root, actual_root + ) + .into()); + } + } + // Validate attestationChecks if let Some(ref att_checks) = checks.attestation_checks { for att_check in att_checks { diff --git a/crates/blockchain/tests/types.rs b/crates/blockchain/tests/types.rs index a1f0232..97b56dd 100644 --- a/crates/blockchain/tests/types.rs +++ b/crates/blockchain/tests/types.rs @@ -136,38 +136,51 @@ impl BlockStepData { // Check Types // ============================================================================ +/// Store-state expectations for a fork choice test step. +/// +/// All fields are optional; only fields explicitly set by the fixture are validated. +/// Root-typed fields have a `*RootLabel` companion that resolves a block label via the +/// step's block registry, mirroring the leanSpec fixture schema. #[derive(Debug, Clone, Deserialize)] pub struct StoreChecks { - // Validated fields + /// Expected store time in intervals since genesis. + pub time: Option, + #[serde(rename = "headSlot")] pub head_slot: Option, #[serde(rename = "headRoot")] pub head_root: Option, - #[serde(rename = "attestationChecks")] - pub attestation_checks: Option>, - #[serde(rename = "attestationTargetSlot")] - pub attestation_target_slot: Option, - - /// Expected store time in intervals since genesis (validated when present). - pub time: Option, - - // Unsupported fields (will error if present in test fixture) #[serde(rename = "headRootLabel")] pub head_root_label: Option, + #[serde(rename = "latestJustifiedSlot")] pub latest_justified_slot: Option, #[serde(rename = "latestJustifiedRoot")] pub latest_justified_root: Option, #[serde(rename = "latestJustifiedRootLabel")] pub latest_justified_root_label: Option, + #[serde(rename = "latestFinalizedSlot")] pub latest_finalized_slot: Option, #[serde(rename = "latestFinalizedRoot")] pub latest_finalized_root: Option, #[serde(rename = "latestFinalizedRootLabel")] pub latest_finalized_root_label: Option, + + /// Legacy single-field schema; expected safe target block root. #[serde(rename = "safeTarget")] pub safe_target: Option, + /// Expected slot of the safe target block (leanSpec #680 schema). + #[serde(rename = "safeTargetSlot")] + pub safe_target_slot: Option, + /// Expected safe target block root by label reference (leanSpec #680 schema). + #[serde(rename = "safeTargetRootLabel")] + pub safe_target_root_label: Option, + + #[serde(rename = "attestationTargetSlot")] + pub attestation_target_slot: Option, + #[serde(rename = "attestationChecks")] + pub attestation_checks: Option>, #[serde(rename = "lexicographicHeadAmong")] pub lexicographic_head_among: Option>, }