diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index ee6ccfdb..872e6aa6 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -279,7 +279,8 @@ impl BlockChainServer { &mut self, signed_block: SignedBlockWithAttestation, ) -> Result<(), StoreError> { - store::on_block(&mut self.store, signed_block)?; + let validator_ids = self.key_manager.validator_ids(); + store::on_block(&mut self.store, signed_block, &validator_ids)?; metrics::update_head_slot(self.store.head_slot()); metrics::update_latest_justified_slot(self.store.latest_justified().slot); metrics::update_latest_finalized_slot(self.store.latest_finalized().slot); @@ -445,7 +446,8 @@ impl BlockChainServer { warn!("Received unaggregated attestation but node is not an aggregator"); return; } - let _ = store::on_gossip_attestation(&mut self.store, attestation) + let validator_ids = self.key_manager.validator_ids(); + let _ = store::on_gossip_attestation(&mut self.store, attestation, &validator_ids) .inspect_err(|err| warn!(%err, "Failed to process gossiped attestation")); } diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 5a57c3df..03bd4f08 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -26,6 +26,16 @@ use crate::{INTERVALS_PER_SLOT, MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT const JUSTIFICATION_LOOKBACK_SLOTS: u64 = 3; +/// Number of attestation committees per slot. +/// With ATTESTATION_COMMITTEE_COUNT = 1, all validators are in subnet 0. +const ATTESTATION_COMMITTEE_COUNT: u64 = 1; + +/// Compute the attestation subnet ID for a validator. +#[allow(clippy::modulo_one)] +fn compute_subnet_id(validator_id: u64) -> u64 { + validator_id % ATTESTATION_COMMITTEE_COUNT +} + /// Accept new aggregated payloads, promoting them to known for fork choice. fn accept_new_attestations(store: &mut Store, log_tree: bool) { store.promote_new_aggregated_payloads(); @@ -357,6 +367,7 @@ pub fn on_tick( pub fn on_gossip_attestation( store: &mut Store, signed_attestation: SignedAttestation, + local_validator_ids: &[u64], ) -> Result<(), StoreError> { let validator_id = signed_attestation.validator_id; let attestation = Attestation { @@ -396,9 +407,16 @@ pub fn on_gossip_attestation( // Store attestation data by root (content-addressed, idempotent) store.insert_attestation_data_by_root(data_root, attestation.data.clone()); - // Store gossip signature for later aggregation at interval 2. - store.insert_gossip_signature(data_root, attestation.data.slot, validator_id, signature); - metrics::update_gossip_signatures(store.gossip_signatures_count()); + // Store gossip signature for later aggregation at interval 2, + // only if the attester is in the same subnet as one of our validators. + let attester_subnet = compute_subnet_id(validator_id); + let in_our_subnet = local_validator_ids + .iter() + .any(|&vid| compute_subnet_id(vid) == attester_subnet); + if in_our_subnet { + store.insert_gossip_signature(data_root, attestation.data.slot, validator_id, signature); + metrics::update_gossip_signatures(store.gossip_signatures_count()); + } metrics::inc_attestations_valid(); @@ -506,8 +524,9 @@ pub fn on_gossip_aggregated_attestation( pub fn on_block( store: &mut Store, signed_block: SignedBlockWithAttestation, + local_validator_ids: &[u64], ) -> Result<(), StoreError> { - on_block_core(store, signed_block, true) + on_block_core(store, signed_block, true, local_validator_ids) } /// Process a new block without signature verification. @@ -518,7 +537,7 @@ pub fn on_block_without_verification( store: &mut Store, signed_block: SignedBlockWithAttestation, ) -> Result<(), StoreError> { - on_block_core(store, signed_block, false) + on_block_core(store, signed_block, false, &[]) } /// Core block processing logic. @@ -529,6 +548,7 @@ fn on_block_core( store: &mut Store, signed_block: SignedBlockWithAttestation, verify: bool, + local_validator_ids: &[u64], ) -> Result<(), StoreError> { let _timing = metrics::time_fork_choice_block_processing(); @@ -633,16 +653,23 @@ fn on_block_core( }; store.insert_new_aggregated_payload((proposer_vid, proposer_data_root), payload); } else { - // Store the proposer's signature for potential future block building - let proposer_sig = - ValidatorSignature::from_bytes(&signed_block.signature.proposer_signature) - .map_err(|_| StoreError::SignatureDecodingFailed)?; - store.insert_gossip_signature( - proposer_data_root, - proposer_attestation.data.slot, - proposer_vid, - proposer_sig, - ); + // Store the proposer's signature for potential future block building, + // only if the proposer is in the same subnet as one of our validators. + let proposer_subnet = compute_subnet_id(proposer_vid); + let in_our_subnet = local_validator_ids + .iter() + .any(|&vid| compute_subnet_id(vid) == proposer_subnet); + if in_our_subnet { + let proposer_sig = + ValidatorSignature::from_bytes(&signed_block.signature.proposer_signature) + .map_err(|_| StoreError::SignatureDecodingFailed)?; + store.insert_gossip_signature( + proposer_data_root, + proposer_attestation.data.slot, + proposer_vid, + proposer_sig, + ); + } } info!(%slot, %block_root, %state_root, "Processed new block"); diff --git a/crates/blockchain/tests/signature_spectests.rs b/crates/blockchain/tests/signature_spectests.rs index 5d617e33..c35c9ebe 100644 --- a/crates/blockchain/tests/signature_spectests.rs +++ b/crates/blockchain/tests/signature_spectests.rs @@ -55,7 +55,7 @@ fn run(path: &Path) -> datatest_stable::Result<()> { store::on_tick(&mut st, block_time_ms, true, false); // Process the block (this includes signature verification) - let result = store::on_block(&mut st, signed_block); + let result = store::on_block(&mut st, signed_block, &[]); // Step 3: Check that it succeeded or failed as expected match (result.is_ok(), test.expect_exception.as_ref()) {