diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index be632076..c3842440 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -37,7 +37,7 @@ fn accept_new_attestations(store: &mut Store, log_tree: bool) { /// /// When `log_tree` is true, also computes block weights and logs an ASCII /// fork choice tree to the terminal. -fn update_head(store: &mut Store, log_tree: bool) { +pub fn update_head(store: &mut Store, log_tree: bool) { let blocks = store.get_live_chain(); let attestations = store.extract_latest_known_attestations(); let old_head = store.head(); @@ -413,8 +413,8 @@ pub fn on_block_without_verification( /// Core block processing logic. /// -/// When `verify` is true, cryptographic signatures are validated and stored -/// for future block building. When false, all signature checks are skipped. +/// When `verify` is true, cryptographic signatures are verified. +/// When false, all signature checks are skipped. fn on_block_core( store: &mut Store, signed_block: SignedBlock, @@ -495,31 +495,12 @@ fn on_block_core( store.insert_signed_block(block_root, signed_block.clone()); store.insert_state(block_root, post_state); - // Process block body attestations and feed them into the payload buffer - // so fork choice's LMD GHOST overlay can see block-only votes. - // - // Per-attestation participant bitfields come straight from - // `block.body.attestations[i].aggregation_bits`. Standalone Type-1 - // proof bytes are not recoverable from a block at import time; - // downstream re-aggregation has to come from the gossip channel or be - // recovered by SNARK-splitting `signed_block.proof` via - // `split_type_2_by_message`. The entries inserted here are info-only, - // used only for fork-choice vote bookkeeping. - let aggregated_attestations = &block.body.attestations; - - let mut known_entries: Vec<(HashedAttestationData, TypeOneMultiSignature)> = - Vec::with_capacity(aggregated_attestations.len()); - for att in aggregated_attestations.iter() { - let hashed = HashedAttestationData::new(att.data.clone()); - let type_one = TypeOneMultiSignature::empty(att.aggregation_bits.clone()); - known_entries.push((hashed, type_one)); + for att in block.body.attestations.iter() { // Count each participating validator as a valid attestation. let count = validator_indices(&att.aggregation_bits).count() as u64; metrics::inc_attestations_valid(count); } - store.insert_known_aggregated_payloads_batch(known_entries); - // Update forkchoice head based on new block and attestations update_head(store, false); diff --git a/crates/blockchain/tests/forkchoice_spectests.rs b/crates/blockchain/tests/forkchoice_spectests.rs index d0bd9f82..500c6bd2 100644 --- a/crates/blockchain/tests/forkchoice_spectests.rs +++ b/crates/blockchain/tests/forkchoice_spectests.rs @@ -7,7 +7,9 @@ use std::{ use ethlambda_blockchain::{MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT, store}; use ethlambda_storage::{Store, backend::InMemoryBackend}; use ethlambda_types::{ - attestation::{AttestationData, SignedAggregatedAttestation, SignedAttestation}, + attestation::{ + AttestationData, HashedAttestationData, SignedAggregatedAttestation, SignedAttestation, + }, block::{Block, TypeOneMultiSignature}, primitives::{ByteList, H256, HashTreeRoot as _}, state::{State, anchor_pair_is_consistent}, @@ -97,7 +99,39 @@ fn run(path: &Path) -> datatest_stable::Result<()> { // NOTE: the has_proposal argument is set to true, following the spec store::on_tick(&mut store, block_time_ms, true); let result = store::on_block_without_verification(&mut store, signed_block); + let import_ok = result.is_ok(); assert_step_outcome(step_idx, step.valid, result)?; + + // Deconstruct the imported block into per-attestation Type-1s, + // mirroring the node's post-import reaggregation. The real node + // SNARK-splits the block's merged Type-2 proof and folds the + // recovered Type-1s into the pool so block-borne votes carry + // fork-choice weight; leanSpec's fork-choice harness gets the + // same effect by simulating the proposer build. Fixture blocks + // are blank (no real proof to split), so reconstruct structurally + // from the body's aggregation_bits — fork choice reads only the + // participant set, not the proof bytes. The recovered entries go + // straight into the known pool to match the proposer-view store + // the fixtures encode. + if import_ok { + let block = block_data.to_block(); + let entries: Vec<(HashedAttestationData, TypeOneMultiSignature)> = block + .body + .attestations + .iter() + .map(|att| { + ( + HashedAttestationData::new(att.data.clone()), + TypeOneMultiSignature::empty(att.aggregation_bits.clone()), + ) + }) + .collect(); + store.insert_known_aggregated_payloads_batch(entries); + // on_block already ran the head update before these votes + // existed; recompute so the head reflects the block's own + // attestations, matching the proposer-view store. + store::update_head(&mut store, false); + } } "tick" => { // Fixtures use either `time` (UNIX seconds) or `interval` diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index 3b5f0d83..f4e15dc5 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -126,9 +126,6 @@ pub type ByteList512KiB = ByteList<524_288>; /// Maximum number of distinct `AttestationData` entries permitted in a single /// block. Canonical home for the cap shared across `ethlambda-blockchain`, /// `ethlambda-test-fixtures`, and the wire types in this crate. -/// -/// See: leanSpec PR #717, which lowered the cap from 16 to 8 alongside the -/// merged block-proof refactor. pub const MAX_ATTESTATIONS_DATA: usize = 8; /// A Type-1 single-message proof aggregating signatures from many validators.