Update VC and BN APIs for naive aggregation (#950)

* Refactor `Attestation` production

* Add constant

* Start refactor for aggregation

* Return early when no attesting validators

* Refactor into individual functions

* Tidy, add comments

* Add first draft of NaiveAggregationPool

* Further progress on naive aggregation pool

* Fix compile errors in VC

* Change locking logic for naive pool

* Introduce AttesationType

* Add pruning, comments

* Add MAX_ATTESTATIONS_PER_SLOT restriction

* Add pruning based on slot

* Update BN for new aggregation fns

* Fix test compile errors

* Fix failing rest_api test

* Move SignedAggregateAndProof into own file

* Update docs, fix warning

* Tidy some formatting in validator API

* Remove T::default_spec from signing

* Fix failing rest test

* Tidy

* Add test, fix bug

* Improve naive pool tests

* Add max attestations test

* Revert changes to the op_pool

* Refactor timer
This commit is contained in:
Paul Hauner
2020-03-25 21:14:05 +11:00
committed by GitHub
parent 58111cddb2
commit fbcf0f8e2e
30 changed files with 1407 additions and 752 deletions

View File

@@ -8,6 +8,7 @@ use crate::events::{EventHandler, EventKind};
use crate::fork_choice::{Error as ForkChoiceError, ForkChoice};
use crate::head_tracker::HeadTracker;
use crate::metrics;
use crate::naive_aggregation_pool::{Error as NaiveAggregationError, NaiveAggregationPool};
use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::shuffling_cache::ShufflingCache;
use crate::snapshot_cache::SnapshotCache;
@@ -62,6 +63,23 @@ pub const OP_POOL_DB_KEY: [u8; 32] = [0; 32];
pub const ETH1_CACHE_DB_KEY: [u8; 32] = [0; 32];
pub const FORK_CHOICE_DB_KEY: [u8; 32] = [0; 32];
#[derive(Debug, PartialEq)]
pub enum AttestationType {
/// An attestation with a single-signature that has been published in accordance with the naive
/// aggregation strategy.
///
/// These attestations may have come from a `committee_index{subnet_id}_beacon_attestation`
/// gossip subnet or they have have come directly from a validator attached to our API.
///
/// If `should_store == true`, the attestation will be added to the `NaiveAggregationPool`.
Unaggregated { should_store: bool },
/// An attestation with one more more signatures that has passed through the aggregation phase
/// of the naive aggregation scheme.
///
/// These attestations must have come from the `beacon_aggregate_and_proof` gossip subnet.
Aggregated,
}
#[derive(Debug, PartialEq)]
pub enum AttestationProcessingOutcome {
Processed,
@@ -142,6 +160,12 @@ pub struct BeaconChain<T: BeaconChainTypes> {
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
/// inclusion in a block.
pub op_pool: OperationPool<T::EthSpec>,
/// A pool of attestations dedicated to the "naive aggregation strategy" defined in the eth2
/// specs.
///
/// This pool accepts `Attestation` objects that only have one aggregation bit set and provides
/// a method to get an aggregated `Attestation` for some `AttestationData`.
pub naive_aggregation_pool: NaiveAggregationPool<T::EthSpec>,
/// Provides information from the Ethereum 1 (PoW) chain.
pub eth1_chain: Option<Eth1Chain<T::Eth1Chain, T::EthSpec, T::Store>>,
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
@@ -676,27 +700,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
}
/// Produce an aggregate attestation that has been collected for this slot and committee.
// TODO: Check and optimize
pub fn return_aggregate_attestation(
/// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`.
///
/// The attestation will be obtained from `self.naive_aggregation_pool`.
pub fn get_aggregated_attestation(
&self,
slot: Slot,
index: CommitteeIndex,
) -> Result<Attestation<T::EthSpec>, Error> {
let epoch = |slot: Slot| slot.epoch(T::EthSpec::slots_per_epoch());
let head_state = &self.head()?.beacon_state;
let state = if epoch(slot) == epoch(head_state.slot) {
self.head()?.beacon_state
} else {
// The block proposer shuffling is not affected by the state roots, so we don't need to
// calculate them.
self.state_at_slot(slot, StateSkipConfig::WithoutStateRoots)?
};
self.op_pool
.get_raw_aggregated_attestations(&slot, &index, &state, &self.spec)
.map_err(Error::from)
data: &AttestationData,
) -> Result<Option<Attestation<T::EthSpec>>, Error> {
self.naive_aggregation_pool.get(data).map_err(Into::into)
}
/// Produce a raw unsigned `Attestation` that is valid for the given `slot` and `index`.
@@ -824,12 +835,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn process_attestation(
&self,
attestation: Attestation<T::EthSpec>,
store_raw: Option<bool>,
attestation_type: AttestationType,
) -> Result<AttestationProcessingOutcome, Error> {
metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_REQUESTS);
let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_TIMES);
let outcome = self.process_attestation_internal(attestation.clone(), store_raw);
let outcome = self.process_attestation_internal(attestation.clone(), attestation_type);
match &outcome {
Ok(outcome) => match outcome {
@@ -883,7 +894,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn process_attestation_internal(
&self,
attestation: Attestation<T::EthSpec>,
store_raw: Option<bool>,
attestation_type: AttestationType,
) -> Result<AttestationProcessingOutcome, Error> {
let initial_validation_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_INITIAL_VALIDATION_TIMES);
@@ -1120,20 +1131,61 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// subnet without a validator responsible for aggregating it, we don't store it in the
// op pool.
if self.eth1_chain.is_some() {
if let Some(is_raw) = store_raw {
if is_raw {
// This is a raw un-aggregated attestation received from a subnet with a
// connected validator required to aggregate and publish these attestations
self.op_pool
.insert_raw_attestation(attestation, &fork, &self.spec)?;
} else {
// This an aggregate attestation received from the aggregate attestation
// channel
self.op_pool.insert_aggregate_attestation(
attestation,
&fork,
&self.spec,
)?;
match attestation_type {
AttestationType::Unaggregated { should_store } if should_store => {
match self.naive_aggregation_pool.insert(&attestation) {
Ok(outcome) => trace!(
self.log,
"Stored unaggregated attestation";
"outcome" => format!("{:?}", outcome),
"index" => attestation.data.index,
"slot" => attestation.data.slot.as_u64(),
),
Err(NaiveAggregationError::SlotTooLow {
slot,
lowest_permissible_slot,
}) => {
trace!(
self.log,
"Refused to store unaggregated attestation";
"lowest_permissible_slot" => lowest_permissible_slot.as_u64(),
"slot" => slot.as_u64(),
);
}
Err(e) => error!(
self.log,
"Failed to store unaggregated attestation";
"error" => format!("{:?}", e),
"index" => attestation.data.index,
"slot" => attestation.data.slot.as_u64(),
),
}
}
AttestationType::Unaggregated { .. } => trace!(
self.log,
"Did not store unaggregated attestation";
"index" => attestation.data.index,
"slot" => attestation.data.slot.as_u64(),
),
AttestationType::Aggregated => {
let index = attestation.data.index;
let slot = attestation.data.slot;
match self
.op_pool
.insert_attestation(attestation, &fork, &self.spec)
{
Ok(_) => {}
Err(e) => {
error!(
self.log,
"Failed to add attestation to op pool";
"error" => format!("{:?}", e),
"index" => index,
"slot" => slot.as_u64(),
);
}
}
}
}
}
@@ -1181,7 +1233,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
if let Ok(Some(pubkey)) =
self.validator_pubkey(aggregate_and_proof.aggregator_index as usize)
{
if !signed_aggregate_and_proof.is_valid(&pubkey, &state.fork) {
if !signed_aggregate_and_proof.is_valid(&pubkey, &state.fork, &self.spec) {
Err(AttestationDropReason::AggregatorSignatureInvalid)
} else {
Ok(())
@@ -1915,18 +1967,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn per_slot_task(&self) {
trace!(self.log, "Running beacon chain per slot tasks");
if let Some(slot) = self.slot_clock.now() {
self.op_pool.prune_committee_attestations(&slot)
}
}
/// Called by the timer on every epoch.
///
/// Performs epoch-based pruning.
pub fn per_epoch_task(&self) {
trace!(self.log, "Running beacon chain per epoch tasks");
if let Some(slot) = self.slot_clock.now() {
let current_epoch = slot.epoch(T::EthSpec::slots_per_epoch());
self.op_pool.prune_attestations(&current_epoch);
self.naive_aggregation_pool.prune(slot);
}
}