Skip to content
Draft
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
29 changes: 8 additions & 21 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -495,31 +495,18 @@ 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));
// A block ships one merged Type-2 proof binding all its attestations; it is
// verified as a whole and never decomposed at import. Standalone per-attestation
// Type-1 proofs are not recoverable here, so block-borne votes carry no
// fork-choice weight until reaggregation (SNARK-splitting the block proof via
// `split_type_2_by_message`) and gossip deliver real Type-1s into the pool.
// Block-imported vote weight is therefore deferred by up to one slot.
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);

Expand Down
36 changes: 35 additions & 1 deletion crates/blockchain/tests/forkchoice_spectests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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`
Expand Down
3 changes: 0 additions & 3 deletions crates/common/types/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading