Electra: Remaining Consensus Data Structures (#5712)

* Attestation superstruct changes for EIP 7549 (#5644)

* update

* experiment

* superstruct changes

* revert

* superstruct changes

* fix tests

* indexed attestation

* indexed attestation superstruct

* updated TODOs

* `superstruct` the `AttesterSlashing` (#5636)

* `superstruct` Attester Fork Variants

* Push a little further

* Deal with Encode / Decode of AttesterSlashing

* not so sure about this..

* Stop Encode/Decode Bounds from Propagating Out

* Tons of Changes..

* More Conversions to AttestationRef

* Add AsReference trait (#15)

* Add AsReference trait

* Fix some snafus

* Got it Compiling! :D

* Got Tests Building

* Get beacon chain tests compiling

---------

Co-authored-by: Michael Sproul <micsproul@gmail.com>

* Merge remote-tracking branch 'upstream/unstable' into electra_attestation_changes

* Make EF Tests Fork-Agnostic (#5713)

* Finish EF Test Fork Agnostic (#5714)

* Superstruct `AggregateAndProof` (#5715)

* Upgrade `superstruct` to `0.8.0`

* superstruct `AggregateAndProof`

* Merge remote-tracking branch 'sigp/unstable' into electra_attestation_changes

* cargo fmt

* Merge pull request #5726 from realbigsean/electra_attestation_changes

Merge unstable into Electra attestation changes

* EIP7549 `get_attestation_indices` (#5657)

* get attesting indices electra impl

* fmt

* get tests to pass

* fmt

* fix some beacon chain tests

* fmt

* fix slasher test

* fmt got me again

* fix more tests

* fix tests

* Some small changes (#5739)

* cargo fmt (#5740)

* Sketch op pool changes

* fix get attesting indices (#5742)

* fix get attesting indices

* better errors

* fix compile

* only get committee index once

* Ef test fixes (#5753)

* attestation related ef test fixes

* delete commented out stuff

* Fix Aggregation Pool for Electra (#5754)

* Fix Aggregation Pool for Electra

* Remove Outdated Interface

* fix ssz (#5755)

* Get `electra_op_pool` up to date (#5756)

* fix get attesting indices (#5742)

* fix get attesting indices

* better errors

* fix compile

* only get committee index once

* Ef test fixes (#5753)

* attestation related ef test fixes

* delete commented out stuff

* Fix Aggregation Pool for Electra (#5754)

* Fix Aggregation Pool for Electra

* Remove Outdated Interface

* fix ssz (#5755)

---------

Co-authored-by: realbigsean <sean@sigmaprime.io>

* Revert "Get `electra_op_pool` up to date (#5756)" (#5757)

This reverts commit ab9e58aa3d.

* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into electra_op_pool

* Compute on chain aggregate impl (#5752)

* add compute_on_chain_agg impl to op pool changes

* fmt

* get op pool tests to pass

* update the naive agg pool interface (#5760)

* Fix bugs in cross-committee aggregation

* Add comment to max cover optimisation

* Fix assert

* Merge pull request #5749 from sigp/electra_op_pool

Optimise Electra op pool aggregation

* update committee offset

* Fix Electra Fork Choice Tests (#5764)

* Subscribe to the correct subnets for electra attestations (#5782)

* subscribe to the correct att subnets for electra

* subscribe to the correct att subnets for electra

* cargo fmt

* fix slashing handling

* Merge remote-tracking branch 'upstream/unstable'

* Send unagg attestation based on fork

* Publish all aggregates

* just one more check bro plz..

* Merge pull request #5832 from ethDreamer/electra_attestation_changes_merge_unstable

Merge `unstable` into `electra_attestation_changes`

* Merge pull request #5835 from realbigsean/fix-validator-logic

Fix validator logic

* Merge pull request #5816 from realbigsean/electra-attestation-slashing-handling

Electra slashing handling

* Electra attestation changes rm decode impl (#5856)

* Remove Crappy Decode impl for Attestation

* Remove Inefficient Attestation Decode impl

* Implement Schema Upgrade / Downgrade

* Update beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs

Co-authored-by: Michael Sproul <micsproul@gmail.com>

---------

Co-authored-by: Michael Sproul <micsproul@gmail.com>

* Fix failing attestation tests and misc electra attestation cleanup (#5810)

* - get attestation related beacon chain tests to pass
- observed attestations are now keyed off of data + committee index
- rename op pool attestationref to compactattestationref
- remove unwraps in agg pool and use options instead
- cherry pick some changes from ef-tests-electra

* cargo fmt

* fix failing test

* Revert dockerfile changes

* make committee_index return option

* function args shouldnt be a ref to attestation ref

* fmt

* fix dup imports

---------

Co-authored-by: realbigsean <seananderson33@GMAIL.com>

* fix some todos (#5817)

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes

* add consolidations to merkle calc for inclusion proof

* Remove Duplicate KZG Commitment Merkle Proof Code (#5874)

* Remove Duplicate KZG Commitment Merkle Proof Code

* s/tree_lists/fields/

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes

* fix compile

* Fix slasher tests (#5906)

* Fix electra tests

* Add electra attestations to double vote tests

* Update superstruct to 0.8

* Merge remote-tracking branch 'origin/unstable' into electra_attestation_changes

* Small cleanup in slasher tests

* Clean up Electra observed aggregates (#5929)

* Use consistent key in observed_attestations

* Remove unwraps from observed aggregates

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes

* De-dup attestation constructor logic

* Remove unwraps in Attestation construction

* Dedup match_attestation_data

* Remove outdated TODO

* Use ForkName Ord in fork-choice tests

* Use ForkName Ord in BeaconBlockBody

* Make to_electra not fallible

* Remove TestRandom impl for IndexedAttestation

* Remove IndexedAttestation faulty Decode impl

* Drop TestRandom impl

* Add PendingAttestationInElectra

* Indexed att on disk (#35)

* indexed att on disk

* fix lints

* Update slasher/src/migrate.rs

Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>

---------

Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com>
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>

* add electra fork enabled fn to ForkName impl (#36)

* add electra fork enabled fn to ForkName impl

* remove inadvertent file

* Update common/eth2/src/types.rs

Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>

* Dedup attestation constructor logic in attester cache

* Use if let Ok for committee_bits

* Dedup Attestation constructor code

* Diff reduction in tests

* Fix beacon_chain tests

* Diff reduction

* Use Ord for ForkName in pubsub

* Resolve into_attestation_and_indices todo

* Remove stale TODO

* Fix beacon_chain tests

* Test spec invariant

* Use electra_enabled in pubsub

* Remove get_indexed_attestation_from_signed_aggregate

* Use ok_or instead of if let else

* committees are sorted

* remove dup method `get_indexed_attestation_from_committees`

* Merge pull request #5940 from dapplion/electra_attestation_changes_lionreview

Electra attestations #5712 review

* update default persisted op pool deserialization

* ensure aggregate and proof uses serde untagged on ref

* Fork aware ssz static attestation tests

* Electra attestation changes from Lions review (#5971)

* dedup/cleanup and remove unneeded hashset use

* remove irrelevant TODOs

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes

* Electra attestation changes sean review (#5972)

* instantiate empty bitlist in unreachable code

* clean up error conversion

* fork enabled bool cleanup

* remove a couple todos

* return bools instead of options in `aggregate` and use the result

* delete commented out code

* use map macros in simple transformations

* remove signers_disjoint_from

* get ef tests compiling

* get ef tests compiling

* update intentionally excluded files

* Avoid changing slasher schema for Electra

* Delete slasher schema v4

* Fix clippy

* Fix compilation of beacon_chain tests

* Update database.rs

* Add electra lightclient types

* Update slasher/src/database.rs

* fix imports

* Merge pull request #5980 from dapplion/electra-lightclient

Add electra lightclient types

* Merge pull request #5975 from michaelsproul/electra-slasher-no-migration

Avoid changing slasher schema for Electra

* Update beacon_node/beacon_chain/src/attestation_verification.rs

* Update beacon_node/beacon_chain/src/attestation_verification.rs
This commit is contained in:
ethDreamer
2024-06-24 16:08:07 -05:00
committed by GitHub
parent 758b58c9e9
commit c52c598f69
118 changed files with 5076 additions and 1741 deletions

View File

@@ -35,17 +35,23 @@
mod batch;
use crate::{
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, metrics,
observed_aggregates::ObserveOutcome, observed_attesters::Error as ObservedAttestersError,
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
metrics,
observed_aggregates::{ObserveOutcome, ObservedAttestationKey},
observed_attesters::Error as ObservedAttestersError,
BeaconChain, BeaconChainError, BeaconChainTypes,
};
use bls::verify_signature_sets;
use itertools::Itertools;
use proto_array::Block as ProtoBlock;
use slog::debug;
use slot_clock::SlotClock;
use state_processing::{
common::get_indexed_attestation,
per_block_processing::errors::AttestationValidationError,
common::{
attesting_indices_base,
attesting_indices_electra::{self, get_committee_indices},
},
per_block_processing::errors::{AttestationValidationError, BlockOperationError},
signature_sets::{
indexed_attestation_signature_set_from_pubkeys,
signed_aggregate_selection_proof_signature_set, signed_aggregate_signature_set,
@@ -55,8 +61,9 @@ use std::borrow::Cow;
use strum::AsRefStr;
use tree_hash::TreeHash;
use types::{
Attestation, BeaconCommittee, ChainSpec, CommitteeIndex, Epoch, EthSpec, ForkName, Hash256,
IndexedAttestation, SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
Attestation, AttestationRef, BeaconCommittee, BeaconStateError::NoCommitteeFound, ChainSpec,
CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation, SelectionProof,
SignedAggregateAndProof, Slot, SubnetId,
};
pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations};
@@ -137,6 +144,12 @@ pub enum Error {
///
/// The peer has sent an invalid message.
ValidatorIndexTooHigh(usize),
/// The validator index is not set to zero after Electra.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
CommitteeIndexNonZero(usize),
/// The `attestation.data.beacon_block_root` block is unknown.
///
/// ## Peer scoring
@@ -185,6 +198,12 @@ pub enum Error {
///
/// The peer has sent an invalid message.
NotExactlyOneAggregationBitSet(usize),
/// The attestation doesn't have only one aggregation bit set.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
NotExactlyOneCommitteeBitSet(usize),
/// We have already observed an attestation for the `validator_index` and refuse to process
/// another.
///
@@ -248,7 +267,7 @@ pub enum Error {
impl From<BeaconChainError> for Error {
fn from(e: BeaconChainError) -> Self {
Error::BeaconChainError(e)
Self::BeaconChainError(e)
}
}
@@ -263,10 +282,11 @@ enum CheckAttestationSignature {
/// `IndexedAttestation` can be derived.
///
/// These attestations have *not* undergone signature verification.
/// The `observed_attestation_key_root` is the hashed value of an `ObservedAttestationKey`.
struct IndexedAggregatedAttestation<'a, T: BeaconChainTypes> {
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
indexed_attestation: IndexedAttestation<T::EthSpec>,
attestation_data_root: Hash256,
observed_attestation_key_root: Hash256,
}
/// Wraps a `Attestation` that has been verified up until the point that an `IndexedAttestation` can
@@ -274,7 +294,7 @@ struct IndexedAggregatedAttestation<'a, T: BeaconChainTypes> {
///
/// These attestations have *not* undergone signature verification.
struct IndexedUnaggregatedAttestation<'a, T: BeaconChainTypes> {
attestation: &'a Attestation<T::EthSpec>,
attestation: AttestationRef<'a, T::EthSpec>,
indexed_attestation: IndexedAttestation<T::EthSpec>,
subnet_id: SubnetId,
validator_index: u64,
@@ -295,7 +315,7 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
/// Wraps an `Attestation` that has been fully verified for propagation on the gossip network.
pub struct VerifiedUnaggregatedAttestation<'a, T: BeaconChainTypes> {
attestation: &'a Attestation<T::EthSpec>,
attestation: AttestationRef<'a, T::EthSpec>,
indexed_attestation: IndexedAttestation<T::EthSpec>,
subnet_id: SubnetId,
}
@@ -322,20 +342,20 @@ impl<'a, T: BeaconChainTypes> Clone for IndexedUnaggregatedAttestation<'a, T> {
/// A helper trait implemented on wrapper types that can be progressed to a state where they can be
/// verified for application to fork choice.
pub trait VerifiedAttestation<T: BeaconChainTypes>: Sized {
fn attestation(&self) -> &Attestation<T::EthSpec>;
fn attestation(&self) -> AttestationRef<T::EthSpec>;
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec>;
// Inefficient default implementation. This is overridden for gossip verified attestations.
fn into_attestation_and_indices(self) -> (Attestation<T::EthSpec>, Vec<u64>) {
let attestation = self.attestation().clone();
let attesting_indices = self.indexed_attestation().attesting_indices.clone().into();
let attestation = self.attestation().clone_as_attestation();
let attesting_indices = self.indexed_attestation().attesting_indices_to_vec();
(attestation, attesting_indices)
}
}
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedAggregatedAttestation<'a, T> {
fn attestation(&self) -> &Attestation<T::EthSpec> {
fn attestation(&self) -> AttestationRef<T::EthSpec> {
self.attestation()
}
@@ -345,7 +365,7 @@ impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedAggregatedAttes
}
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedUnaggregatedAttestation<'a, T> {
fn attestation(&self) -> &Attestation<T::EthSpec> {
fn attestation(&self) -> AttestationRef<T::EthSpec> {
self.attestation
}
@@ -357,7 +377,7 @@ impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedUnaggregatedAtt
/// Information about invalid attestations which might still be slashable despite being invalid.
pub enum AttestationSlashInfo<'a, T: BeaconChainTypes, TErr> {
/// The attestation is invalid, but its signature wasn't checked.
SignatureNotChecked(&'a Attestation<T::EthSpec>, TErr),
SignatureNotChecked(AttestationRef<'a, T::EthSpec>, TErr),
/// As for `SignatureNotChecked`, but we know the `IndexedAttestation`.
SignatureNotCheckedIndexed(IndexedAttestation<T::EthSpec>, TErr),
/// The attestation's signature is invalid, so it will never be slashable.
@@ -382,7 +402,7 @@ fn process_slash_info<T: BeaconChainTypes>(
let (indexed_attestation, check_signature, err) = match slash_info {
SignatureNotChecked(attestation, err) => {
if let Error::UnknownHeadBlock { .. } = err {
if attestation.data.beacon_block_root == attestation.data.target.root {
if attestation.data().beacon_block_root == attestation.data().target.root {
return err;
}
}
@@ -451,7 +471,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
signed_aggregate: &SignedAggregateAndProof<T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<Hash256, Error> {
let attestation = &signed_aggregate.message.aggregate;
let attestation = signed_aggregate.message().aggregate();
// Ensure attestation is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
@@ -460,30 +480,39 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
verify_propagation_slot_range(&chain.slot_clock, attestation, &chain.spec)?;
// Check the attestation's epoch matches its target.
if attestation.data.slot.epoch(T::EthSpec::slots_per_epoch())
!= attestation.data.target.epoch
if attestation.data().slot.epoch(T::EthSpec::slots_per_epoch())
!= attestation.data().target.epoch
{
return Err(Error::InvalidTargetEpoch {
slot: attestation.data.slot,
epoch: attestation.data.target.epoch,
slot: attestation.data().slot,
epoch: attestation.data().target.epoch,
});
}
// Ensure the valid aggregated attestation has not already been seen locally.
let attestation_data = &attestation.data;
let attestation_data_root = attestation_data.tree_hash_root();
let observed_attestation_key_root = ObservedAttestationKey {
committee_index: attestation
.committee_index()
.ok_or(Error::NotExactlyOneCommitteeBitSet(0))?,
attestation_data: attestation.data().clone(),
}
.tree_hash_root();
// [New in Electra:EIP7549]
verify_committee_index(attestation)?;
if chain
.observed_attestations
.write()
.is_known_subset(attestation, attestation_data_root)
.is_known_subset(attestation, observed_attestation_key_root)
.map_err(|e| Error::BeaconChainError(e.into()))?
{
metrics::inc_counter(&metrics::AGGREGATED_ATTESTATION_SUBSETS);
return Err(Error::AttestationSupersetKnown(attestation_data_root));
return Err(Error::AttestationSupersetKnown(
observed_attestation_key_root,
));
}
let aggregator_index = signed_aggregate.message.aggregator_index;
let aggregator_index = signed_aggregate.message().aggregator_index();
// Ensure there has been no other observed aggregate for the given `aggregator_index`.
//
@@ -491,7 +520,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
match chain
.observed_aggregators
.read()
.validator_has_been_observed(attestation.data.target.epoch, aggregator_index as usize)
.validator_has_been_observed(attestation.data().target.epoch, aggregator_index as usize)
{
Ok(true) => Err(Error::AggregatorAlreadyKnown(aggregator_index)),
Ok(false) => Ok(()),
@@ -523,10 +552,10 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
verify_attestation_target_root::<T::EthSpec>(&head_block, attestation)?;
// Ensure that the attestation has participants.
if attestation.aggregation_bits.is_zero() {
if attestation.is_aggregation_bits_zero() {
Err(Error::EmptyAggregationBitfield)
} else {
Ok(attestation_data_root)
Ok(observed_attestation_key_root)
}
}
@@ -536,23 +565,47 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
chain: &BeaconChain<T>,
) -> Result<Self, AttestationSlashInfo<'a, T, Error>> {
use AttestationSlashInfo::*;
let attestation = &signed_aggregate.message.aggregate;
let aggregator_index = signed_aggregate.message.aggregator_index;
let attestation_data_root = match Self::verify_early_checks(signed_aggregate, chain) {
let observed_attestation_key_root = match Self::verify_early_checks(signed_aggregate, chain)
{
Ok(root) => root,
Err(e) => return Err(SignatureNotChecked(&signed_aggregate.message.aggregate, e)),
Err(e) => {
return Err(SignatureNotChecked(
signed_aggregate.message().aggregate(),
e,
))
}
};
// Committees must be sorted by ascending index order 0..committees_per_slot
let get_indexed_attestation_with_committee =
|(committee, _): (BeaconCommittee, CommitteesPerSlot)| {
// Note: this clones the signature which is known to be a relatively slow operation.
//
// Future optimizations should remove this clone.
let selection_proof =
SelectionProof::from(signed_aggregate.message.selection_proof.clone());
|(committees, _): (Vec<BeaconCommittee>, CommitteesPerSlot)| {
let (index, aggregator_index, selection_proof, data) = match signed_aggregate {
SignedAggregateAndProof::Base(signed_aggregate) => (
signed_aggregate.message.aggregate.data.index,
signed_aggregate.message.aggregator_index,
// Note: this clones the signature which is known to be a relatively slow operation.
// Future optimizations should remove this clone.
signed_aggregate.message.selection_proof.clone(),
signed_aggregate.message.aggregate.data.clone(),
),
SignedAggregateAndProof::Electra(signed_aggregate) => (
signed_aggregate
.message
.aggregate
.committee_index()
.ok_or(Error::NotExactlyOneCommitteeBitSet(0))?,
signed_aggregate.message.aggregator_index,
signed_aggregate.message.selection_proof.clone(),
signed_aggregate.message.aggregate.data.clone(),
),
};
let slot = data.slot;
if !selection_proof
let committee = committees
.get(index as usize)
.ok_or(Error::NoCommitteeForSlotAndIndex { slot, index })?;
if !SelectionProof::from(selection_proof)
.is_aggregator(committee.committee.len(), &chain.spec)
.map_err(|e| Error::BeaconChainError(e.into()))?
{
@@ -564,23 +617,44 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
return Err(Error::AggregatorNotInCommittee { aggregator_index });
}
get_indexed_attestation(committee.committee, attestation)
.map_err(|e| BeaconChainError::from(e).into())
// p2p aggregates have a single committee, we can assert that aggregation_bits is always
// less then MaxValidatorsPerCommittee
match signed_aggregate {
SignedAggregateAndProof::Base(signed_aggregate) => {
attesting_indices_base::get_indexed_attestation(
committee.committee,
&signed_aggregate.message.aggregate,
)
.map_err(|e| BeaconChainError::from(e).into())
}
SignedAggregateAndProof::Electra(signed_aggregate) => {
attesting_indices_electra::get_indexed_attestation(
&committees,
&signed_aggregate.message.aggregate,
)
.map_err(|e| BeaconChainError::from(e).into())
}
}
};
let indexed_attestation = match map_attestation_committee(
let attestation = signed_aggregate.message().aggregate();
let indexed_attestation = match map_attestation_committees(
chain,
attestation,
get_indexed_attestation_with_committee,
) {
Ok(indexed_attestation) => indexed_attestation,
Err(e) => return Err(SignatureNotChecked(&signed_aggregate.message.aggregate, e)),
Err(e) => {
return Err(SignatureNotChecked(
signed_aggregate.message().aggregate(),
e,
))
}
};
Ok(IndexedAggregatedAttestation {
signed_aggregate,
indexed_attestation,
attestation_data_root,
observed_attestation_key_root,
})
}
}
@@ -589,11 +663,11 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
/// Run the checks that happen after the indexed attestation and signature have been checked.
fn verify_late_checks(
signed_aggregate: &SignedAggregateAndProof<T::EthSpec>,
attestation_data_root: Hash256,
observed_attestation_key_root: Hash256,
chain: &BeaconChain<T>,
) -> Result<(), Error> {
let attestation = &signed_aggregate.message.aggregate;
let aggregator_index = signed_aggregate.message.aggregator_index;
let attestation = signed_aggregate.message().aggregate();
let aggregator_index = signed_aggregate.message().aggregator_index();
// Observe the valid attestation so we do not re-process it.
//
@@ -602,11 +676,13 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
if let ObserveOutcome::Subset = chain
.observed_attestations
.write()
.observe_item(attestation, Some(attestation_data_root))
.observe_item(attestation, Some(observed_attestation_key_root))
.map_err(|e| Error::BeaconChainError(e.into()))?
{
metrics::inc_counter(&metrics::AGGREGATED_ATTESTATION_SUBSETS);
return Err(Error::AttestationSupersetKnown(attestation_data_root));
return Err(Error::AttestationSupersetKnown(
observed_attestation_key_root,
));
}
// Observe the aggregator so we don't process another aggregate from them.
@@ -616,12 +692,12 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
if chain
.observed_aggregators
.write()
.observe_validator(attestation.data.target.epoch, aggregator_index as usize)
.observe_validator(attestation.data().target.epoch, aggregator_index as usize)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorAttestationKnown {
validator_index: aggregator_index,
epoch: attestation.data.target.epoch,
epoch: attestation.data().target.epoch,
});
}
@@ -666,7 +742,7 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
let IndexedAggregatedAttestation {
signed_aggregate,
indexed_attestation,
attestation_data_root,
observed_attestation_key_root,
} = signed_aggregate;
match check_signature {
@@ -690,7 +766,9 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
CheckAttestationSignature::No => (),
};
if let Err(e) = Self::verify_late_checks(signed_aggregate, attestation_data_root, chain) {
if let Err(e) =
Self::verify_late_checks(signed_aggregate, observed_attestation_key_root, chain)
{
return Err(SignatureValid(indexed_attestation, e));
}
@@ -701,8 +779,8 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
}
/// Returns the underlying `attestation` for the `signed_aggregate`.
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
&self.signed_aggregate.message.aggregate
pub fn attestation(&self) -> AttestationRef<'a, T::EthSpec> {
self.signed_aggregate.message().aggregate()
}
/// Returns the underlying `signed_aggregate`.
@@ -714,16 +792,16 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
/// Run the checks that happen before an indexed attestation is constructed.
pub fn verify_early_checks(
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<(), Error> {
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
let attestation_epoch = attestation.data().slot.epoch(T::EthSpec::slots_per_epoch());
// Check the attestation's epoch matches its target.
if attestation_epoch != attestation.data.target.epoch {
if attestation_epoch != attestation.data().target.epoch {
return Err(Error::InvalidTargetEpoch {
slot: attestation.data.slot,
epoch: attestation.data.target.epoch,
slot: attestation.data().slot,
epoch: attestation.data().target.epoch,
});
}
@@ -735,11 +813,14 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
// Check to ensure that the attestation is "unaggregated". I.e., it has exactly one
// aggregation bit set.
let num_aggregation_bits = attestation.aggregation_bits.num_set_bits();
let num_aggregation_bits = attestation.num_set_aggregation_bits();
if num_aggregation_bits != 1 {
return Err(Error::NotExactlyOneAggregationBitSet(num_aggregation_bits));
}
// [New in Electra:EIP7549]
verify_committee_index(attestation)?;
// Attestations must be for a known block. If the block is unknown, we simply drop the
// attestation and do not delay consideration for later.
//
@@ -755,14 +836,14 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
/// Run the checks that apply to the indexed attestation before the signature is checked.
pub fn verify_middle_checks(
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
indexed_attestation: &IndexedAttestation<T::EthSpec>,
committees_per_slot: u64,
subnet_id: Option<SubnetId>,
chain: &BeaconChain<T>,
) -> Result<(u64, SubnetId), Error> {
let expected_subnet_id = SubnetId::compute_subnet_for_attestation_data::<T::EthSpec>(
&indexed_attestation.data,
let expected_subnet_id = SubnetId::compute_subnet_for_attestation::<T::EthSpec>(
attestation,
committees_per_slot,
&chain.spec,
)
@@ -779,8 +860,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
};
let validator_index = *indexed_attestation
.attesting_indices
.first()
.attesting_indices_first()
.ok_or(Error::NotExactlyOneAggregationBitSet(0))?;
/*
@@ -790,12 +870,12 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
if chain
.observed_gossip_attesters
.read()
.validator_has_been_observed(attestation.data.target.epoch, validator_index as usize)
.validator_has_been_observed(attestation.data().target.epoch, validator_index as usize)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorAttestationKnown {
validator_index,
epoch: attestation.data.target.epoch,
epoch: attestation.data().target.epoch,
});
}
@@ -812,7 +892,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
subnet_id: Option<SubnetId>,
chain: &BeaconChain<T>,
) -> Result<Self, Error> {
Self::verify_slashable(attestation, subnet_id, chain)
Self::verify_slashable(attestation.to_ref(), subnet_id, chain)
.map(|verified_unaggregated| {
if let Some(slasher) = chain.slasher.as_ref() {
slasher.accept_attestation(verified_unaggregated.indexed_attestation.clone());
@@ -824,7 +904,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
/// Verify the attestation, producing extra information about whether it might be slashable.
pub fn verify_slashable(
attestation: &'a Attestation<T::EthSpec>,
attestation: AttestationRef<'a, T::EthSpec>,
subnet_id: Option<SubnetId>,
chain: &BeaconChain<T>,
) -> Result<Self, AttestationSlashInfo<'a, T, Error>> {
@@ -873,7 +953,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
impl<'a, T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'a, T> {
/// Run the checks that apply after the signature has been checked.
fn verify_late_checks(
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
validator_index: u64,
chain: &BeaconChain<T>,
) -> Result<(), Error> {
@@ -886,12 +966,12 @@ impl<'a, T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'a, T> {
if chain
.observed_gossip_attesters
.write()
.observe_validator(attestation.data.target.epoch, validator_index as usize)
.observe_validator(attestation.data().target.epoch, validator_index as usize)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorAttestationKnown {
validator_index,
epoch: attestation.data.target.epoch,
epoch: attestation.data().target.epoch,
});
}
Ok(())
@@ -967,7 +1047,7 @@ impl<'a, T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'a, T> {
}
/// Returns the wrapped `attestation`.
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
pub fn attestation(&self) -> AttestationRef<T::EthSpec> {
self.attestation
}
@@ -997,34 +1077,34 @@ impl<'a, T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'a, T> {
/// already finalized.
fn verify_head_block_is_known<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
max_skip_slots: Option<u64>,
) -> Result<ProtoBlock, Error> {
let block_opt = chain
.canonical_head
.fork_choice_read_lock()
.get_block(&attestation.data.beacon_block_root)
.get_block(&attestation.data().beacon_block_root)
.or_else(|| {
chain
.early_attester_cache
.get_proto_block(attestation.data.beacon_block_root)
.get_proto_block(attestation.data().beacon_block_root)
});
if let Some(block) = block_opt {
// Reject any block that exceeds our limit on skipped slots.
if let Some(max_skip_slots) = max_skip_slots {
if attestation.data.slot > block.slot + max_skip_slots {
if attestation.data().slot > block.slot + max_skip_slots {
return Err(Error::TooManySkippedSlots {
head_block_slot: block.slot,
attestation_slot: attestation.data.slot,
attestation_slot: attestation.data().slot,
});
}
}
Ok(block)
} else if chain.is_pre_finalization_block(attestation.data.beacon_block_root)? {
} else if chain.is_pre_finalization_block(attestation.data().beacon_block_root)? {
Err(Error::HeadBlockFinalized {
beacon_block_root: attestation.data.beacon_block_root,
beacon_block_root: attestation.data().beacon_block_root,
})
} else {
// The block is either:
@@ -1034,7 +1114,7 @@ fn verify_head_block_is_known<T: BeaconChainTypes>(
// 2) A post-finalization block that we don't know about yet. We'll queue
// the attestation until the block becomes available (or we time out).
Err(Error::UnknownHeadBlock {
beacon_block_root: attestation.data.beacon_block_root,
beacon_block_root: attestation.data().beacon_block_root,
})
}
}
@@ -1045,10 +1125,10 @@ fn verify_head_block_is_known<T: BeaconChainTypes>(
/// Accounts for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
slot_clock: &S,
attestation: &Attestation<E>,
attestation: AttestationRef<E>,
spec: &ChainSpec,
) -> Result<(), Error> {
let attestation_slot = attestation.data.slot;
let attestation_slot = attestation.data().slot;
let latest_permissible_slot = slot_clock
.now_with_future_tolerance(spec.maximum_gossip_clock_disparity())
.ok_or(BeaconChainError::UnableToReadSlot)?;
@@ -1067,14 +1147,13 @@ pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
let current_fork =
spec.fork_name_at_slot::<E>(slot_clock.now().ok_or(BeaconChainError::UnableToReadSlot)?);
let earliest_permissible_slot = match current_fork {
ForkName::Base | ForkName::Altair | ForkName::Bellatrix | ForkName::Capella => {
one_epoch_prior
}
// EIP-7045
ForkName::Deneb | ForkName::Electra => one_epoch_prior
let earliest_permissible_slot = if !current_fork.deneb_enabled() {
one_epoch_prior
// EIP-7045
} else {
one_epoch_prior
.epoch(E::slots_per_epoch())
.start_slot(E::slots_per_epoch()),
.start_slot(E::slots_per_epoch())
};
if attestation_slot < earliest_permissible_slot {
@@ -1102,18 +1181,17 @@ pub fn verify_attestation_signature<T: BeaconChainTypes>(
let fork = chain
.spec
.fork_at_epoch(indexed_attestation.data.target.epoch);
.fork_at_epoch(indexed_attestation.data().target.epoch);
let signature_set = indexed_attestation_signature_set_from_pubkeys(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
&indexed_attestation.signature,
indexed_attestation.signature(),
indexed_attestation,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?;
metrics::stop_timer(signature_setup_timer);
let _signature_verification_timer =
@@ -1130,11 +1208,11 @@ pub fn verify_attestation_signature<T: BeaconChainTypes>(
/// `attestation.data.beacon_block_root`.
pub fn verify_attestation_target_root<E: EthSpec>(
head_block: &ProtoBlock,
attestation: &Attestation<E>,
attestation: AttestationRef<E>,
) -> Result<(), Error> {
// Check the attestation target root.
let head_block_epoch = head_block.slot.epoch(E::slots_per_epoch());
let attestation_epoch = attestation.data.slot.epoch(E::slots_per_epoch());
let attestation_epoch = attestation.data().slot.epoch(E::slots_per_epoch());
if head_block_epoch > attestation_epoch {
// The epoch references an invalid head block from a future epoch.
//
@@ -1147,7 +1225,7 @@ pub fn verify_attestation_target_root<E: EthSpec>(
// Reference:
// https://github.com/ethereum/eth2.0-specs/pull/2001#issuecomment-699246659
return Err(Error::InvalidTargetRoot {
attestation: attestation.data.target.root,
attestation: attestation.data().target.root,
// It is not clear what root we should expect in this case, since the attestation is
// fundamentally invalid.
expected: None,
@@ -1166,9 +1244,9 @@ pub fn verify_attestation_target_root<E: EthSpec>(
};
// Reject any attestation with an invalid target root.
if target_root != attestation.data.target.root {
if target_root != attestation.data().target.root {
return Err(Error::InvalidTargetRoot {
attestation: attestation.data.target.root,
attestation: attestation.data().target.root,
expected: Some(target_root),
});
}
@@ -1199,14 +1277,14 @@ pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
let aggregator_index = signed_aggregate.message.aggregator_index;
let aggregator_index = signed_aggregate.message().aggregator_index();
if aggregator_index >= pubkey_cache.len() as u64 {
return Err(Error::AggregatorPubkeyUnknown(aggregator_index));
}
let fork = chain
.spec
.fork_at_epoch(indexed_attestation.data.target.epoch);
.fork_at_epoch(indexed_attestation.data().target.epoch);
let signature_sets = vec![
signed_aggregate_selection_proof_signature_set(
@@ -1227,7 +1305,7 @@ pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
.map_err(BeaconChainError::SignatureSetError)?,
indexed_attestation_signature_set_from_pubkeys(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
&indexed_attestation.signature,
indexed_attestation.signature(),
indexed_attestation,
&fork,
chain.genesis_validators_root,
@@ -1239,6 +1317,28 @@ pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
Ok(verify_signature_sets(signature_sets.iter()))
}
/// Verify that the `attestation` committee index is properly set for the attestation's fork.
/// This function will only apply verification post-Electra.
pub fn verify_committee_index<E: EthSpec>(attestation: AttestationRef<E>) -> Result<(), Error> {
if let Ok(committee_bits) = attestation.committee_bits() {
// Check to ensure that the attestation is for a single committee.
let num_committee_bits = get_committee_indices::<E>(committee_bits);
if num_committee_bits.len() != 1 {
return Err(Error::NotExactlyOneCommitteeBitSet(
num_committee_bits.len(),
));
}
// Ensure the attestation index is set to zero post Electra.
if attestation.data().index != 0 {
return Err(Error::CommitteeIndexNonZero(
attestation.data().index as usize,
));
}
}
Ok(())
}
/// Assists in readability.
type CommitteesPerSlot = u64;
@@ -1246,35 +1346,71 @@ type CommitteesPerSlot = u64;
/// public keys cached in the `chain`.
pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
) -> Result<(IndexedAttestation<T::EthSpec>, CommitteesPerSlot), Error> {
map_attestation_committee(chain, attestation, |(committee, committees_per_slot)| {
get_indexed_attestation(committee.committee, attestation)
.map(|attestation| (attestation, committees_per_slot))
.map_err(Error::Invalid)
map_attestation_committees(chain, attestation, |(committees, committees_per_slot)| {
match attestation {
AttestationRef::Base(att) => {
let committee = committees
.iter()
.filter(|&committee| committee.index == att.data.index)
.at_most_one()
.map_err(|_| Error::NoCommitteeForSlotAndIndex {
slot: att.data.slot,
index: att.data.index,
})?;
if let Some(committee) = committee {
attesting_indices_base::get_indexed_attestation(committee.committee, att)
.map(|attestation| (attestation, committees_per_slot))
.map_err(Error::Invalid)
} else {
Err(Error::NoCommitteeForSlotAndIndex {
slot: att.data.slot,
index: att.data.index,
})
}
}
AttestationRef::Electra(att) => {
attesting_indices_electra::get_indexed_attestation(&committees, att)
.map(|attestation| (attestation, committees_per_slot))
.map_err(|e| {
if let BlockOperationError::BeaconStateError(NoCommitteeFound(index)) = e {
Error::NoCommitteeForSlotAndIndex {
slot: att.data.slot,
index,
}
} else {
Error::Invalid(e)
}
})
}
}
})
}
/// Runs the `map_fn` with the committee and committee count per slot for the given `attestation`.
///
/// This function exists in this odd "map" pattern because efficiently obtaining the committee for
/// an attestation can be complex. It might involve reading straight from the
/// This function exists in this odd "map" pattern because efficiently obtaining the committees for
/// an attestation's slot can be complex. It might involve reading straight from the
/// `beacon_chain.shuffling_cache` or it might involve reading it from a state from the DB. Due to
/// the complexities of `RwLock`s on the shuffling cache, a simple `Cow` isn't suitable here.
///
/// If the committee for `attestation` isn't found in the `shuffling_cache`, we will read a state
/// If the committees for an `attestation`'s slot aren't found in the `shuffling_cache`, we will read a state
/// from disk and then update the `shuffling_cache`.
fn map_attestation_committee<T, F, R>(
///
/// Committees are sorted by ascending index order 0..committees_per_slot
fn map_attestation_committees<T, F, R>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
attestation: AttestationRef<T::EthSpec>,
map_fn: F,
) -> Result<R, Error>
where
T: BeaconChainTypes,
F: Fn((BeaconCommittee, CommitteesPerSlot)) -> Result<R, Error>,
F: Fn((Vec<BeaconCommittee>, CommitteesPerSlot)) -> Result<R, Error>,
{
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
let target = &attestation.data.target;
let attestation_epoch = attestation.data().slot.epoch(T::EthSpec::slots_per_epoch());
let target = &attestation.data().target;
// Attestation target must be for a known block.
//
@@ -1297,12 +1433,12 @@ where
let committees_per_slot = committee_cache.committees_per_slot();
Ok(committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
.get_beacon_committees_at_slot(attestation.data().slot)
.map(|committees| map_fn((committees, committees_per_slot)))
.unwrap_or_else(|_| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,
index: attestation.data.index,
slot: attestation.data().slot,
index: attestation.committee_index().unwrap_or(0),
})
}))
})