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
25 changes: 10 additions & 15 deletions lean_client/containers/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,24 +428,19 @@ impl State {
.start_timer()
});

// Each unique AttestationData must appear at most once per block.
// Mirrors leanSpec spec.py:1247-1252. Our own builder collapses
// duplicates upstream via `aggregate_by_data`, so this guards
// externally-built blocks.
ensure!(
!AggregatedAttestation::has_duplicate_data(attestations),
"Block contains duplicate AttestationData entries; \
each AttestationData must appear at most once",
);

// Cap distinct AttestationData entries per block. Mirrors leanSpec
// spec.py:1253-1256. With the duplicate check above, len ==
// distinct-count, so this is equivalent to the spec's
// `len(att_data_set) <= MAX_ATTESTATIONS_DATA`.
// state_transition.py:205-216 — only distinct data is bounded; split
// aggregates sharing one data count once. Wire-level duplicate
// rejection lives at the on_block-equivalent in main.rs.
let distinct_data_count = attestations
.into_iter()
.map(|att| att.data.hash_tree_root())
.collect::<HashSet<H256>>()
.len();
ensure!(
(attestations.len_u64() as usize) <= MAX_ATTESTATIONS_DATA,
distinct_data_count <= MAX_ATTESTATIONS_DATA,
"Block contains {} distinct AttestationData entries; maximum is {}",
attestations.len_u64(),
distinct_data_count,
MAX_ATTESTATIONS_DATA,
);

Expand Down
14 changes: 13 additions & 1 deletion lean_client/fork_choice/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,23 @@ impl Store {

let target_checkpoint = self.get_attestation_target();

let head_state = self
.states
.get(&self.head)
.ok_or(anyhow!("head state is not known"))?;
let mut source = head_state.latest_justified.clone();
if source.root == H256::zero() {
source = Checkpoint {
root: self.head,
slot: source.slot,
};
}

Ok(AttestationData {
slot,
head: head_checkpoint,
target: target_checkpoint,
source: self.latest_justified.clone(),
source,
})
}

Expand Down
19 changes: 16 additions & 3 deletions lean_client/fork_choice/tests/unit_tests/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,19 +598,32 @@ fn test_validator_operations_invalid_parameters() {
}

#[test]
fn test_produce_attestation_data_uses_store_justified() {
fn test_produce_attestation_data_uses_head_state_justified() {
let mut store = create_test_store();

store.latest_justified = Checkpoint {
let head_state_justified = Checkpoint {
root: H256::from_slice(&[0xaa; 32]),
slot: Slot(3),
};
let store_only_justified = Checkpoint {
root: H256::from_slice(&[0xff; 32]),
slot: Slot(5),
};

let mut new_head_state = store.states[&store.head].as_ref().clone();
new_head_state.latest_justified = head_state_justified.clone();
store
.states
.insert(store.head, std::sync::Arc::new(new_head_state));

store.latest_justified = store_only_justified.clone();

let attestation_data = store
.produce_attestation_data(Slot(1))
.expect("produce_attestation_data failed");

assert_eq!(attestation_data.source, store.latest_justified);
assert_eq!(attestation_data.source, head_state_justified);
assert_ne!(attestation_data.source, store_only_justified);
}

fn produce_and_apply(
Expand Down
2 changes: 1 addition & 1 deletion lean_client/networking/src/network/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ where
.send(ChainMessage::ProcessBlock {
signed_block,
is_trusted: false,
should_gossip: true,
should_gossip: false,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

shouldn't we re-gossip blocks, received via P2P?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't need to because gossipsub's mesh already forwards inbound blocks to our other peers, so calling publish() on them just hits Duplicate, which is why we set should_gossip false on every other peer-received path RPC by-root/by-range, gossip attestation, gossip aggregation and only flip it true for our own-produced blocks and attestations.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ok, sounds good

cached_post_state: None,
})
.await
Expand Down
25 changes: 23 additions & 2 deletions lean_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
use anyhow::{Context as _, Result};
use clap::Parser;
use containers::{
Block, BlockBody, BlockHeader, Checkpoint, Config, MultiMessageAggregate, SignedBlock, Slot,
State, Status, Validator,
AggregatedAttestation, Block, BlockBody, BlockHeader, Checkpoint, Config,
MultiMessageAggregate, SignedBlock, Slot, State, Status, Validator,
};
use dedicated_executor::DedicatedExecutor;
use ethereum_types::H256;
Expand Down Expand Up @@ -1317,6 +1317,16 @@ async fn main() -> Result<()> {
continue;
}

if AggregatedAttestation::has_duplicate_data(
&signed_block.block.body.attestations,
) {
warn!(
block_root = %format!("0x{:x}", block_root),
"Rejecting verified block: duplicate AttestationData entries"
);
continue;
}

// Skip if parent state was pruned by finalization while verify ran.
// `apply_verified_block` retains finalized + buffer states only; if
// finalization advanced past this block's parent during the verify
Expand Down Expand Up @@ -1550,6 +1560,17 @@ async fn main() -> Result<()> {
continue;
}

if AggregatedAttestation::has_duplicate_data(
&signed_block.block.body.attestations,
) {
warn!(
slot = block_slot.0,
block_root = %format!("0x{:x}", block_root),
"Rejecting block: duplicate AttestationData entries"
);
continue;
}

info!(
slot = block_slot.0,
block_root = %format!("0x{:x}", block_root),
Expand Down
Loading