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
6 changes: 4 additions & 2 deletions crates/blockchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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"));
}

Expand Down
57 changes: 42 additions & 15 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We still need to support multiple validators at the P2P layer, but this is a good first step.

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();

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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();

Expand Down Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion crates/blockchain/tests/signature_spectests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Loading