Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 31 additions & 4 deletions crates/blockchain/tests/forkchoice_spectests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
33 changes: 23 additions & 10 deletions crates/blockchain/tests/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>,

#[serde(rename = "headSlot")]
pub head_slot: Option<u64>,
#[serde(rename = "headRoot")]
pub head_root: Option<H256>,
#[serde(rename = "attestationChecks")]
pub attestation_checks: Option<Vec<AttestationCheck>>,
#[serde(rename = "attestationTargetSlot")]
pub attestation_target_slot: Option<u64>,

/// Expected store time in intervals since genesis (validated when present).
pub time: Option<u64>,

// Unsupported fields (will error if present in test fixture)
#[serde(rename = "headRootLabel")]
pub head_root_label: Option<String>,

#[serde(rename = "latestJustifiedSlot")]
pub latest_justified_slot: Option<u64>,
#[serde(rename = "latestJustifiedRoot")]
pub latest_justified_root: Option<H256>,
#[serde(rename = "latestJustifiedRootLabel")]
pub latest_justified_root_label: Option<String>,

#[serde(rename = "latestFinalizedSlot")]
pub latest_finalized_slot: Option<u64>,
#[serde(rename = "latestFinalizedRoot")]
pub latest_finalized_root: Option<H256>,
#[serde(rename = "latestFinalizedRootLabel")]
pub latest_finalized_root_label: Option<String>,

/// Legacy single-field schema; expected safe target block root.
#[serde(rename = "safeTarget")]
pub safe_target: Option<H256>,
/// Expected slot of the safe target block (leanSpec #680 schema).
#[serde(rename = "safeTargetSlot")]
pub safe_target_slot: Option<u64>,
/// Expected safe target block root by label reference (leanSpec #680 schema).
#[serde(rename = "safeTargetRootLabel")]
pub safe_target_root_label: Option<String>,
Comment on lines +170 to +178
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing safeTargetRoot (direct-root) field for new schema

The new leanSpec #680 schema is paired as safeTargetSlot + safeTargetRootLabel. If a future fixture revision also ships a direct-root counterpart named safeTargetRoot (mirroring the latestJustifiedRoot / latestFinalizedRoot pattern rather than reusing the legacy safeTarget field), serde will silently discard it and the validation block in forkchoice_spectests.rs will never fire. Consider adding safeTargetRoot: Option<H256> now so the struct schema is symmetric with latestJustified* / latestFinalized*, even if it stays dormant until fixtures use it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/tests/types.rs
Line: 170-178

Comment:
**Missing `safeTargetRoot` (direct-root) field for new schema**

The new leanSpec #680 schema is paired as `safeTargetSlot` + `safeTargetRootLabel`. If a future fixture revision also ships a direct-root counterpart named `safeTargetRoot` (mirroring the `latestJustifiedRoot` / `latestFinalizedRoot` pattern rather than reusing the legacy `safeTarget` field), serde will silently discard it and the validation block in `forkchoice_spectests.rs` will never fire. Consider adding `safeTargetRoot: Option<H256>` now so the struct schema is symmetric with `latestJustified*` / `latestFinalized*`, even if it stays dormant until fixtures use it.

How can I resolve this? If you propose a fix, please make it concise.


#[serde(rename = "attestationTargetSlot")]
pub attestation_target_slot: Option<u64>,
#[serde(rename = "attestationChecks")]
pub attestation_checks: Option<Vec<AttestationCheck>>,
#[serde(rename = "lexicographicHeadAmong")]
pub lexicographic_head_among: Option<Vec<String>>,
}
Expand Down
Loading