Clean up Electra observed aggregates (#5929)

* Use consistent key in observed_attestations

* Remove unwraps from observed aggregates
This commit is contained in:
Michael Sproul
2024-06-18 00:23:02 +10:00
committed by GitHub
parent c4f2284dbe
commit 3ac3ddb2b7
4 changed files with 61 additions and 36 deletions

View File

@@ -35,8 +35,10 @@
mod batch; mod batch;
use crate::{ use crate::{
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, metrics, beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
observed_aggregates::ObserveOutcome, observed_attesters::Error as ObservedAttestersError, metrics,
observed_aggregates::{ObserveOutcome, ObservedAttestationKey},
observed_attesters::Error as ObservedAttestersError,
BeaconChain, BeaconChainError, BeaconChainTypes, BeaconChain, BeaconChainError, BeaconChainTypes,
}; };
use bls::verify_signature_sets; use bls::verify_signature_sets;
@@ -58,9 +60,8 @@ use state_processing::{
use std::borrow::Cow; use std::borrow::Cow;
use strum::AsRefStr; use strum::AsRefStr;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::{ use types::{
Attestation, AttestationData, AttestationRef, BeaconCommittee, BeaconStateError, Attestation, AttestationRef, BeaconCommittee, BeaconStateError,
BeaconStateError::NoCommitteeFound, ChainSpec, CommitteeIndex, Epoch, EthSpec, ForkName, BeaconStateError::NoCommitteeFound, ChainSpec, CommitteeIndex, Epoch, EthSpec, ForkName,
Hash256, IndexedAttestation, SelectionProof, SignedAggregateAndProof, Slot, SubnetId, Hash256, IndexedAttestation, SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
}; };
@@ -309,12 +310,6 @@ struct IndexedAggregatedAttestation<'a, T: BeaconChainTypes> {
observed_attestation_key_root: Hash256, observed_attestation_key_root: Hash256,
} }
#[derive(TreeHash)]
pub struct ObservedAttestationKey {
pub committee_index: u64,
pub attestation_data: AttestationData,
}
/// Wraps a `Attestation` that has been verified up until the point that an `IndexedAttestation` can /// Wraps a `Attestation` that has been verified up until the point that an `IndexedAttestation` can
/// be derived. /// be derived.
/// ///

View File

@@ -39,7 +39,7 @@ mod light_client_server_cache;
pub mod metrics; pub mod metrics;
pub mod migrate; pub mod migrate;
mod naive_aggregation_pool; mod naive_aggregation_pool;
mod observed_aggregates; pub mod observed_aggregates;
mod observed_attesters; mod observed_attesters;
mod observed_blob_sidecars; mod observed_blob_sidecars;
pub mod observed_block_producers; pub mod observed_block_producers;

View File

@@ -6,11 +6,14 @@ use ssz_types::{BitList, BitVector};
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::consts::altair::{ use types::consts::altair::{
SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE,
}; };
use types::slot_data::SlotData; use types::slot_data::SlotData;
use types::{Attestation, AttestationRef, EthSpec, Hash256, Slot, SyncCommitteeContribution}; use types::{
Attestation, AttestationData, AttestationRef, EthSpec, Hash256, Slot, SyncCommitteeContribution,
};
pub type ObservedSyncContributions<E> = ObservedAggregates< pub type ObservedSyncContributions<E> = ObservedAggregates<
SyncCommitteeContribution<E>, SyncCommitteeContribution<E>,
@@ -20,6 +23,17 @@ pub type ObservedSyncContributions<E> = ObservedAggregates<
pub type ObservedAggregateAttestations<E> = pub type ObservedAggregateAttestations<E> =
ObservedAggregates<Attestation<E>, E, BitList<<E as types::EthSpec>::MaxValidatorsPerSlot>>; ObservedAggregates<Attestation<E>, E, BitList<<E as types::EthSpec>::MaxValidatorsPerSlot>>;
/// Attestation data augmented with committee index
///
/// This is hashed and used to key the map of observed aggregate attestations. This is important
/// post-Electra where the attestation data committee index is 0 and we want to avoid accidentally
/// comparing aggregation bits for *different* committees.
#[derive(TreeHash)]
pub struct ObservedAttestationKey {
pub committee_index: u64,
pub attestation_data: AttestationData,
}
/// A trait use to associate capacity constants with the type being stored in `ObservedAggregates`. /// A trait use to associate capacity constants with the type being stored in `ObservedAggregates`.
pub trait Consts { pub trait Consts {
/// The default capacity of items stored per slot, in a single `SlotHashSet`. /// The default capacity of items stored per slot, in a single `SlotHashSet`.
@@ -92,11 +106,11 @@ pub trait SubsetItem {
/// Returns the item that gets stored in `ObservedAggregates` for later subset /// Returns the item that gets stored in `ObservedAggregates` for later subset
/// comparison with incoming aggregates. /// comparison with incoming aggregates.
fn get_item(&self) -> Self::Item; fn get_item(&self) -> Result<Self::Item, Error>;
/// Returns a unique value that keys the object to the item that is being stored /// Returns a unique value that keys the object to the item that is being stored
/// in `ObservedAggregates`. /// in `ObservedAggregates`.
fn root(&self) -> Hash256; fn root(&self) -> Result<Hash256, Error>;
} }
impl<'a, E: EthSpec> SubsetItem for AttestationRef<'a, E> { impl<'a, E: EthSpec> SubsetItem for AttestationRef<'a, E> {
@@ -126,19 +140,22 @@ impl<'a, E: EthSpec> SubsetItem for AttestationRef<'a, E> {
} }
/// Returns the sync contribution aggregation bits. /// Returns the sync contribution aggregation bits.
fn get_item(&self) -> Self::Item { fn get_item(&self) -> Result<Self::Item, Error> {
match self { match self {
Self::Base(att) => { Self::Base(att) => att
// TODO(electra) fix unwrap .extend_aggregation_bits()
att.extend_aggregation_bits().unwrap() .map_err(|_| Error::GetItemError),
} Self::Electra(att) => Ok(att.aggregation_bits.clone()),
Self::Electra(att) => att.aggregation_bits.clone(),
} }
} }
/// Returns the hash tree root of the attestation data. /// Returns the hash tree root of the attestation data augmented with the committee index.
fn root(&self) -> Hash256 { fn root(&self) -> Result<Hash256, Error> {
self.data().tree_hash_root() Ok(ObservedAttestationKey {
committee_index: self.committee_index().ok_or(Error::RootError)?,
attestation_data: self.data().clone(),
}
.tree_hash_root())
} }
} }
@@ -153,19 +170,19 @@ impl<'a, E: EthSpec> SubsetItem for &'a SyncCommitteeContribution<E> {
} }
/// Returns the sync contribution aggregation bits. /// Returns the sync contribution aggregation bits.
fn get_item(&self) -> Self::Item { fn get_item(&self) -> Result<Self::Item, Error> {
self.aggregation_bits.clone() Ok(self.aggregation_bits.clone())
} }
/// Returns the hash tree root of the root, slot and subcommittee index /// Returns the hash tree root of the root, slot and subcommittee index
/// of the sync contribution. /// of the sync contribution.
fn root(&self) -> Hash256 { fn root(&self) -> Result<Hash256, Error> {
SyncCommitteeData { Ok(SyncCommitteeData {
root: self.beacon_block_root, root: self.beacon_block_root,
slot: self.slot, slot: self.slot,
subcommittee_index: self.subcommittee_index, subcommittee_index: self.subcommittee_index,
} }
.tree_hash_root() .tree_hash_root())
} }
} }
@@ -192,6 +209,8 @@ pub enum Error {
expected: Slot, expected: Slot,
attestation: Slot, attestation: Slot,
}, },
GetItemError,
RootError,
} }
/// A `HashMap` that contains entries related to some `Slot`. /// A `HashMap` that contains entries related to some `Slot`.
@@ -234,7 +253,7 @@ impl<I> SlotHashSet<I> {
// If true, we replace the new item with its existing subset. This allows us // If true, we replace the new item with its existing subset. This allows us
// to hold fewer items in the list. // to hold fewer items in the list.
} else if item.is_superset(existing) { } else if item.is_superset(existing) {
*existing = item.get_item(); *existing = item.get_item()?;
return Ok(ObserveOutcome::New); return Ok(ObserveOutcome::New);
} }
} }
@@ -252,7 +271,7 @@ impl<I> SlotHashSet<I> {
return Err(Error::ReachedMaxObservationsPerSlot(self.max_capacity)); return Err(Error::ReachedMaxObservationsPerSlot(self.max_capacity));
} }
let item = item.get_item(); let item = item.get_item()?;
self.map.entry(root).or_default().push(item); self.map.entry(root).or_default().push(item);
Ok(ObserveOutcome::New) Ok(ObserveOutcome::New)
} }
@@ -345,7 +364,7 @@ where
root_opt: Option<Hash256>, root_opt: Option<Hash256>,
) -> Result<ObserveOutcome, Error> { ) -> Result<ObserveOutcome, Error> {
let index = self.get_set_index(item.get_slot())?; let index = self.get_set_index(item.get_slot())?;
let root = root_opt.unwrap_or_else(|| item.root()); let root = root_opt.map_or_else(|| item.root(), Ok)?;
self.sets self.sets
.get_mut(index) .get_mut(index)
@@ -487,7 +506,10 @@ mod tests {
for a in &items { for a in &items {
assert_eq!( assert_eq!(
store.is_known_subset(a.as_reference(), a.as_reference().root()), store.is_known_subset(
a.as_reference(),
a.as_reference().root().unwrap()
),
Ok(false), Ok(false),
"should indicate an unknown attestation is unknown" "should indicate an unknown attestation is unknown"
); );
@@ -500,12 +522,18 @@ mod tests {
for a in &items { for a in &items {
assert_eq!( assert_eq!(
store.is_known_subset(a.as_reference(), a.as_reference().root()), store.is_known_subset(
a.as_reference(),
a.as_reference().root().unwrap()
),
Ok(true), Ok(true),
"should indicate a known attestation is known" "should indicate a known attestation is known"
); );
assert_eq!( assert_eq!(
store.observe_item(a.as_reference(), Some(a.as_reference().root())), store.observe_item(
a.as_reference(),
Some(a.as_reference().root().unwrap())
),
Ok(ObserveOutcome::Subset), Ok(ObserveOutcome::Subset),
"should acknowledge an existing attestation" "should acknowledge an existing attestation"
); );

View File

@@ -2,8 +2,8 @@
use beacon_chain::attestation_verification::{ use beacon_chain::attestation_verification::{
batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations, Error, batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations, Error,
ObservedAttestationKey,
}; };
use beacon_chain::observed_aggregates::ObservedAttestationKey;
use beacon_chain::test_utils::{MakeAttestationOptions, HARNESS_GENESIS_TIME}; use beacon_chain::test_utils::{MakeAttestationOptions, HARNESS_GENESIS_TIME};
use beacon_chain::{ use beacon_chain::{
attestation_verification::Error as AttnError, attestation_verification::Error as AttnError,
@@ -852,7 +852,9 @@ async fn aggregated_gossip_verification() {
err, err,
AttnError::AttestationSupersetKnown(hash) AttnError::AttestationSupersetKnown(hash)
if hash == ObservedAttestationKey { if hash == ObservedAttestationKey {
committee_index: tester.valid_aggregate.message().aggregate().expect("should get committee index"), committee_index: tester.valid_aggregate.message().aggregate()
.committee_index()
.expect("should get committee index"),
attestation_data: tester.valid_aggregate.message().aggregate().data().clone(), attestation_data: tester.valid_aggregate.message().aggregate().data().clone(),
}.tree_hash_root() }.tree_hash_root()
)) ))