2020-05-06 11:42:56 +00:00
|
|
|
//! Provides verification for the following attestations:
|
|
|
|
//!
|
|
|
|
//! - "Unaggregated" `Attestation` received from either gossip or the HTTP API.
|
|
|
|
//! - "Aggregated" `SignedAggregateAndProof` received from gossip or the HTTP API.
|
|
|
|
//!
|
|
|
|
//! For clarity, we define:
|
|
|
|
//!
|
|
|
|
//! - Unaggregated: an `Attestation` object that has exactly one aggregation bit set.
|
|
|
|
//! - Aggregated: a `SignedAggregateAndProof` which has zero or more signatures.
|
|
|
|
//! - Note: "zero or more" may soon change to "one or more".
|
|
|
|
//!
|
|
|
|
//! Similar to the `crate::block_verification` module, we try to avoid doing duplicate verification
|
|
|
|
//! work as an attestation passes through different stages of verification. We represent these
|
|
|
|
//! different stages of verification with wrapper types. These wrapper-types flow in a particular
|
|
|
|
//! pattern:
|
|
|
|
//!
|
|
|
|
//! ```ignore
|
|
|
|
//! types::Attestation types::SignedAggregateAndProof
|
|
|
|
//! | |
|
|
|
|
//! ▼ ▼
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
//! IndexedUnaggregatedAttestation IndexedAggregatedAttestation
|
|
|
|
//! | |
|
|
|
|
//! VerifiedUnaggregatedAttestation VerifiedAggregatedAttestation
|
2020-05-06 11:42:56 +00:00
|
|
|
//! | |
|
|
|
|
//! -------------------------------------
|
|
|
|
//! |
|
|
|
|
//! ▼
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
//! impl VerifiedAttestation
|
2020-05-06 11:42:56 +00:00
|
|
|
//! ```
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
mod batch;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
use crate::{
|
2021-07-15 00:52:02 +00:00
|
|
|
beacon_chain::{MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT},
|
2020-05-06 11:42:56 +00:00
|
|
|
metrics,
|
2021-07-15 00:52:02 +00:00
|
|
|
observed_aggregates::ObserveOutcome,
|
2020-05-06 11:42:56 +00:00
|
|
|
observed_attesters::Error as ObservedAttestersError,
|
|
|
|
BeaconChain, BeaconChainError, BeaconChainTypes,
|
|
|
|
};
|
|
|
|
use bls::verify_signature_sets;
|
2020-09-27 20:59:40 +00:00
|
|
|
use proto_array::Block as ProtoBlock;
|
2020-11-23 03:43:22 +00:00
|
|
|
use slog::debug;
|
2020-05-06 11:42:56 +00:00
|
|
|
use slot_clock::SlotClock;
|
|
|
|
use state_processing::{
|
|
|
|
common::get_indexed_attestation,
|
|
|
|
per_block_processing::errors::AttestationValidationError,
|
|
|
|
signature_sets::{
|
|
|
|
indexed_attestation_signature_set_from_pubkeys,
|
|
|
|
signed_aggregate_selection_proof_signature_set, signed_aggregate_signature_set,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
use std::borrow::Cow;
|
2021-01-19 06:33:58 +00:00
|
|
|
use strum::AsRefStr;
|
2020-05-06 11:42:56 +00:00
|
|
|
use tree_hash::TreeHash;
|
|
|
|
use types::{
|
|
|
|
Attestation, BeaconCommittee, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation,
|
2020-09-29 03:46:54 +00:00
|
|
|
SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
|
2020-05-06 11:42:56 +00:00
|
|
|
};
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations};
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Returned when an attestation was not successfully verified. It might not have been verified for
|
|
|
|
/// two reasons:
|
|
|
|
///
|
|
|
|
/// - The attestation is malformed or inappropriate for the context (indicated by all variants
|
|
|
|
/// other than `BeaconChainError`).
|
|
|
|
/// - The application encountered an internal error whilst attempting to determine validity
|
|
|
|
/// (the `BeaconChainError` variant)
|
2021-01-19 06:33:58 +00:00
|
|
|
#[derive(Debug, AsRefStr)]
|
2020-05-06 11:42:56 +00:00
|
|
|
pub enum Error {
|
|
|
|
/// The attestation is from a slot that is later than the current slot (with respect to the
|
|
|
|
/// gossip clock disparity).
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// Assuming the local clock is correct, the peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
FutureSlot {
|
|
|
|
attestation_slot: Slot,
|
|
|
|
latest_permissible_slot: Slot,
|
|
|
|
},
|
|
|
|
/// The attestation is from a slot that is prior to the earliest permissible slot (with
|
|
|
|
/// respect to the gossip clock disparity).
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// Assuming the local clock is correct, the peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
PastSlot {
|
|
|
|
attestation_slot: Slot,
|
|
|
|
earliest_permissible_slot: Slot,
|
|
|
|
},
|
|
|
|
/// The attestations aggregation bits were empty when they shouldn't be.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
EmptyAggregationBitfield,
|
|
|
|
/// The `selection_proof` on the aggregate attestation does not elect it as an aggregator.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
InvalidSelectionProof { aggregator_index: u64 },
|
|
|
|
/// The `selection_proof` on the aggregate attestation selects it as a validator, however the
|
|
|
|
/// aggregator index is not in the committee for that attestation.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
AggregatorNotInCommittee { aggregator_index: u64 },
|
|
|
|
/// The aggregator index refers to a validator index that we have not seen.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
AggregatorPubkeyUnknown(u64),
|
|
|
|
/// The attestation has been seen before; either in a block, on the gossip network or from a
|
|
|
|
/// local validator.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// It's unclear if this attestation is valid, however we have already observed it and do not
|
|
|
|
/// need to observe it again.
|
2020-05-06 11:42:56 +00:00
|
|
|
AttestationAlreadyKnown(Hash256),
|
|
|
|
/// There has already been an aggregation observed for this validator, we refuse to process a
|
|
|
|
/// second.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// It's unclear if this attestation is valid, however we have already observed an aggregate
|
|
|
|
/// attestation from this validator for this epoch and should not observe another.
|
2020-05-06 11:42:56 +00:00
|
|
|
AggregatorAlreadyKnown(u64),
|
|
|
|
/// The aggregator index is higher than the maximum possible validator count.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
ValidatorIndexTooHigh(usize),
|
|
|
|
/// The `attestation.data.beacon_block_root` block is unknown.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The attestation points to a block we have not yet imported. It's unclear if the attestation
|
|
|
|
/// is valid or not.
|
2020-05-06 11:42:56 +00:00
|
|
|
UnknownHeadBlock { beacon_block_root: Hash256 },
|
2022-01-27 22:58:32 +00:00
|
|
|
/// The `attestation.data.beacon_block_root` block is from before the finalized checkpoint.
|
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The attestation is not descended from the finalized checkpoint, which is a REJECT according
|
|
|
|
/// to the spec. We downscore lightly because this could also happen if we are processing
|
|
|
|
/// attestations extremely slowly.
|
|
|
|
HeadBlockFinalized { beacon_block_root: Hash256 },
|
2020-07-26 03:16:49 +00:00
|
|
|
/// The `attestation.data.slot` is not from the same epoch as `data.target.epoch`.
|
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
BadTargetEpoch,
|
|
|
|
/// The target root of the attestation points to a block that we have not verified.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// This is invalid behaviour whilst we first check for `UnknownHeadBlock`.
|
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
UnknownTargetRoot(Hash256),
|
|
|
|
/// A signature on the attestation is invalid.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
InvalidSignature,
|
|
|
|
/// There is no committee for the slot and committee index of this attestation and the
|
|
|
|
/// attestation should not have been produced.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
NoCommitteeForSlotAndIndex { slot: Slot, index: CommitteeIndex },
|
|
|
|
/// The unaggregated attestation doesn't have only one aggregation bit set.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
NotExactlyOneAggregationBitSet(usize),
|
|
|
|
/// We have already observed an attestation for the `validator_index` and refuse to process
|
|
|
|
/// another.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// It's unclear if this attestation is valid, however we have already observed a
|
|
|
|
/// single-participant attestation from this validator for this epoch and should not observe
|
|
|
|
/// another.
|
2020-05-06 11:42:56 +00:00
|
|
|
PriorAttestationKnown { validator_index: u64, epoch: Epoch },
|
|
|
|
/// The attestation is attesting to a state that is later than itself. (Viz., attesting to the
|
|
|
|
/// future).
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
AttestsToFutureBlock { block: Slot, attestation: Slot },
|
2020-06-18 09:11:03 +00:00
|
|
|
/// The attestation was received on an invalid attestation subnet.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-06-18 09:11:03 +00:00
|
|
|
InvalidSubnetId {
|
|
|
|
received: SubnetId,
|
|
|
|
expected: SubnetId,
|
|
|
|
},
|
2020-05-06 11:42:56 +00:00
|
|
|
/// The attestation failed the `state_processing` verification stage.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
2020-05-06 11:42:56 +00:00
|
|
|
Invalid(AttestationValidationError),
|
2020-08-17 10:54:58 +00:00
|
|
|
/// The attestation head block is too far behind the attestation slot, causing many skip slots.
|
|
|
|
/// This is deemed a DoS risk.
|
|
|
|
TooManySkippedSlots {
|
|
|
|
head_block_slot: Slot,
|
|
|
|
attestation_slot: Slot,
|
|
|
|
},
|
2020-09-27 20:59:40 +00:00
|
|
|
/// The attestation has an invalid target epoch.
|
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
|
|
|
InvalidTargetEpoch { slot: Slot, epoch: Epoch },
|
|
|
|
/// The attestation references an invalid target block.
|
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// The peer has sent an invalid message.
|
|
|
|
InvalidTargetRoot {
|
|
|
|
attestation: Hash256,
|
2020-10-02 10:46:37 +00:00
|
|
|
expected: Option<Hash256>,
|
2020-09-27 20:59:40 +00:00
|
|
|
},
|
2020-05-06 11:42:56 +00:00
|
|
|
/// There was an error whilst processing the attestation. It is not known if it is valid or invalid.
|
2020-07-26 03:16:49 +00:00
|
|
|
///
|
|
|
|
/// ## Peer scoring
|
|
|
|
///
|
|
|
|
/// We were unable to process this attestation due to an internal error. It's unclear if the
|
|
|
|
/// attestation is valid.
|
2020-05-06 11:42:56 +00:00
|
|
|
BeaconChainError(BeaconChainError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<BeaconChainError> for Error {
|
|
|
|
fn from(e: BeaconChainError) -> Self {
|
|
|
|
Error::BeaconChainError(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Used to avoid double-checking signatures.
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
enum CheckAttestationSignature {
|
|
|
|
Yes,
|
|
|
|
No,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wraps a `SignedAggregateAndProof` that has been verified up until the point that an
|
|
|
|
/// `IndexedAttestation` can be derived.
|
|
|
|
///
|
|
|
|
/// These attestations have *not* undergone signature verification.
|
|
|
|
struct IndexedAggregatedAttestation<'a, T: BeaconChainTypes> {
|
|
|
|
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
|
|
|
attestation_root: Hash256,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wraps a `Attestation` that has been verified up until the point that an `IndexedAttestation` can
|
|
|
|
/// be derived.
|
|
|
|
///
|
|
|
|
/// These attestations have *not* undergone signature verification.
|
|
|
|
struct IndexedUnaggregatedAttestation<'a, T: BeaconChainTypes> {
|
|
|
|
attestation: &'a Attestation<T::EthSpec>,
|
2020-05-06 11:42:56 +00:00
|
|
|
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
subnet_id: SubnetId,
|
|
|
|
validator_index: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wraps a `SignedAggregateAndProof` that has been fully verified for propagation on the gossip
|
|
|
|
/// network.
|
|
|
|
pub struct VerifiedAggregatedAttestation<'a, T: BeaconChainTypes> {
|
|
|
|
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
|
|
|
|
pub fn into_indexed_attestation(self) -> IndexedAttestation<T::EthSpec> {
|
|
|
|
self.indexed_attestation
|
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// 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>,
|
2020-05-06 11:42:56 +00:00
|
|
|
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
2020-09-29 03:46:54 +00:00
|
|
|
subnet_id: SubnetId,
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'a, T> {
|
|
|
|
pub fn into_indexed_attestation(self) -> IndexedAttestation<T::EthSpec> {
|
|
|
|
self.indexed_attestation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Custom `Clone` implementation is to avoid the restrictive trait bounds applied by the usual derive
|
|
|
|
/// macro.
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> Clone for IndexedUnaggregatedAttestation<'a, T> {
|
2020-05-06 11:42:56 +00:00
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
attestation: self.attestation,
|
2020-05-06 11:42:56 +00:00
|
|
|
indexed_attestation: self.indexed_attestation.clone(),
|
2020-09-29 03:46:54 +00:00
|
|
|
subnet_id: self.subnet_id,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
validator_index: self.validator_index,
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A helper trait implemented on wrapper types that can be progressed to a state where they can be
|
|
|
|
/// verified for application to fork choice.
|
Refactor op pool for speed and correctness (#3312)
## Proposed Changes
This PR has two aims: to speed up attestation packing in the op pool, and to fix bugs in the verification of attester slashings, proposer slashings and voluntary exits. The changes are bundled into a single database schema upgrade (v12).
Attestation packing is sped up by removing several inefficiencies:
- No more recalculation of `attesting_indices` during packing.
- No (unnecessary) examination of the `ParticipationFlags`: a bitfield suffices. See `RewardCache`.
- No re-checking of attestation validity during packing: the `AttestationMap` provides attestations which are "correct by construction" (I have checked this using Hydra).
- No SSZ re-serialization for the clunky `AttestationId` type (it can be removed in a future release).
So far the speed-up seems to be roughly 2-10x, from 500ms down to 50-100ms.
Verification of attester slashings, proposer slashings and voluntary exits is fixed by:
- Tracking the `ForkVersion`s that were used to verify each message inside the `SigVerifiedOp`. This allows us to quickly re-verify that they match the head state's opinion of what the `ForkVersion` should be at the epoch(s) relevant to the message.
- Storing the `SigVerifiedOp` on disk rather than the raw operation. This allows us to continue track the fork versions after a reboot.
This is mostly contained in this commit 52bb1840ae5c4356a8fc3a51e5df23ed65ed2c7f.
## Additional Info
The schema upgrade uses the justified state to re-verify attestations and compute `attesting_indices` for them. It will drop any attestations that fail to verify, by the logic that attestations are most valuable in the few slots after they're observed, and are probably stale and useless by the time a node restarts. Exits and proposer slashings and similarly re-verified to obtain `SigVerifiedOp`s.
This PR contains a runtime killswitch `--paranoid-block-proposal` which opts out of all the optimisations in favour of closely verifying every included message. Although I'm quite sure that the optimisations are correct this flag could be useful in the event of an unforeseen emergency.
Finally, you might notice that the `RewardCache` appears quite useless in its current form because it is only updated on the hot-path immediately before proposal. My hope is that in future we can shift calls to `RewardCache::update` into the background, e.g. while performing the state advance. It is also forward-looking to `tree-states` compatibility, where iterating and indexing `state.{previous,current}_epoch_participation` is expensive and needs to be minimised.
2022-08-29 09:10:26 +00:00
|
|
|
pub trait VerifiedAttestation<T: BeaconChainTypes>: Sized {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
fn attestation(&self) -> &Attestation<T::EthSpec>;
|
|
|
|
|
v0.12 fork choice update (#1229)
* Incomplete scraps
* Add progress on new fork choice impl
* Further progress
* First complete compiling version
* Remove chain reference
* Add new lmd_ghost crate
* Start integrating into beacon chain
* Update `milagro_bls` to new release (#1183)
* Update milagro_bls to new release
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Tidy up fake cryptos
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* move SecretHash to bls and put plaintext back
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Rough beacon chain impl working
* Remove fork_choice_2
* Remove checkpoint manager
* Half finished ssz impl
* Add missed file
* Add persistence
* Tidy, fix some compile errors
* Remove RwLock from ProtoArrayForkChoice
* Fix store-based compile errors
* Add comments, tidy
* Move function out of ForkChoice struct
* Start testing
* More testing
* Fix compile error
* Tidy beacon_chain::fork_choice
* Queue attestations from the current slot
* Allow fork choice to handle prior-to-genesis start
* Improve error granularity
* Test attestation dequeuing
* Process attestations during block
* Store target root in fork choice
* Move fork choice verification into new crate
* Update tests
* Consensus updates for v0.12 (#1228)
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Fix typo found in review
* Add `Block` struct to ProtoArray
* Start fixing get_ancestor
* Add rough progress on testing
* Get fork choice tests working
* Progress with testing
* Fix partialeq impl
* Move slot clock from fc_store
* Improve testing
* Add testing for best justified
* Add clone back to SystemTimeSlotClock
* Add balances test
* Start adding balances cache again
* Wire-in balances cache
* Improve tests
* Remove commented-out tests
* Remove beacon_chain::ForkChoice
* Rename crates
* Update wider codebase to new fork_choice layout
* Move advance_slot in test harness
* Tidy ForkChoice::update_time
* Fix verification tests
* Fix compile error with iter::once
* Fix fork choice tests
* Ensure block attestations are processed
* Fix failing beacon_chain tests
* Add first invalid block check
* Add finalized block check
* Progress with testing, new store builder
* Add fixes to get_ancestor
* Fix old genesis justification test
* Fix remaining fork choice tests
* Change root iteration method
* Move on_verified_block
* Remove unused method
* Start adding attestation verification tests
* Add invalid ffg target test
* Add target epoch test
* Add queued attestation test
* Remove old fork choice verification tests
* Tidy, add test
* Move fork choice lock drop
* Rename BeaconForkChoiceStore
* Add comments, tidy BeaconForkChoiceStore
* Update metrics, rename fork_choice_store.rs
* Remove genesis_block_root from ForkChoice
* Tidy
* Update fork_choice comments
* Tidy, add comments
* Tidy, simplify ForkChoice, fix compile issue
* Tidy, removed dead file
* Increase http request timeout
* Fix failing rest_api test
* Set HTTP timeout back to 5s
* Apply fix to get_ancestor
* Address Michael's comments
* Fix typo
* Revert "Fix broken attestation verification test"
This reverts commit 722cdc903b12611de27916a57eeecfa3224f2279.
Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-06-17 01:10:22 +00:00
|
|
|
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec>;
|
Refactor op pool for speed and correctness (#3312)
## Proposed Changes
This PR has two aims: to speed up attestation packing in the op pool, and to fix bugs in the verification of attester slashings, proposer slashings and voluntary exits. The changes are bundled into a single database schema upgrade (v12).
Attestation packing is sped up by removing several inefficiencies:
- No more recalculation of `attesting_indices` during packing.
- No (unnecessary) examination of the `ParticipationFlags`: a bitfield suffices. See `RewardCache`.
- No re-checking of attestation validity during packing: the `AttestationMap` provides attestations which are "correct by construction" (I have checked this using Hydra).
- No SSZ re-serialization for the clunky `AttestationId` type (it can be removed in a future release).
So far the speed-up seems to be roughly 2-10x, from 500ms down to 50-100ms.
Verification of attester slashings, proposer slashings and voluntary exits is fixed by:
- Tracking the `ForkVersion`s that were used to verify each message inside the `SigVerifiedOp`. This allows us to quickly re-verify that they match the head state's opinion of what the `ForkVersion` should be at the epoch(s) relevant to the message.
- Storing the `SigVerifiedOp` on disk rather than the raw operation. This allows us to continue track the fork versions after a reboot.
This is mostly contained in this commit 52bb1840ae5c4356a8fc3a51e5df23ed65ed2c7f.
## Additional Info
The schema upgrade uses the justified state to re-verify attestations and compute `attesting_indices` for them. It will drop any attestations that fail to verify, by the logic that attestations are most valuable in the few slots after they're observed, and are probably stale and useless by the time a node restarts. Exits and proposer slashings and similarly re-verified to obtain `SigVerifiedOp`s.
This PR contains a runtime killswitch `--paranoid-block-proposal` which opts out of all the optimisations in favour of closely verifying every included message. Although I'm quite sure that the optimisations are correct this flag could be useful in the event of an unforeseen emergency.
Finally, you might notice that the `RewardCache` appears quite useless in its current form because it is only updated on the hot-path immediately before proposal. My hope is that in future we can shift calls to `RewardCache::update` into the background, e.g. while performing the state advance. It is also forward-looking to `tree-states` compatibility, where iterating and indexing `state.{previous,current}_epoch_participation` is expensive and needs to be minimised.
2022-08-29 09:10:26 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
(attestation, attesting_indices)
|
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedAggregatedAttestation<'a, T> {
|
|
|
|
fn attestation(&self) -> &Attestation<T::EthSpec> {
|
|
|
|
self.attestation()
|
|
|
|
}
|
|
|
|
|
v0.12 fork choice update (#1229)
* Incomplete scraps
* Add progress on new fork choice impl
* Further progress
* First complete compiling version
* Remove chain reference
* Add new lmd_ghost crate
* Start integrating into beacon chain
* Update `milagro_bls` to new release (#1183)
* Update milagro_bls to new release
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Tidy up fake cryptos
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* move SecretHash to bls and put plaintext back
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Rough beacon chain impl working
* Remove fork_choice_2
* Remove checkpoint manager
* Half finished ssz impl
* Add missed file
* Add persistence
* Tidy, fix some compile errors
* Remove RwLock from ProtoArrayForkChoice
* Fix store-based compile errors
* Add comments, tidy
* Move function out of ForkChoice struct
* Start testing
* More testing
* Fix compile error
* Tidy beacon_chain::fork_choice
* Queue attestations from the current slot
* Allow fork choice to handle prior-to-genesis start
* Improve error granularity
* Test attestation dequeuing
* Process attestations during block
* Store target root in fork choice
* Move fork choice verification into new crate
* Update tests
* Consensus updates for v0.12 (#1228)
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Fix typo found in review
* Add `Block` struct to ProtoArray
* Start fixing get_ancestor
* Add rough progress on testing
* Get fork choice tests working
* Progress with testing
* Fix partialeq impl
* Move slot clock from fc_store
* Improve testing
* Add testing for best justified
* Add clone back to SystemTimeSlotClock
* Add balances test
* Start adding balances cache again
* Wire-in balances cache
* Improve tests
* Remove commented-out tests
* Remove beacon_chain::ForkChoice
* Rename crates
* Update wider codebase to new fork_choice layout
* Move advance_slot in test harness
* Tidy ForkChoice::update_time
* Fix verification tests
* Fix compile error with iter::once
* Fix fork choice tests
* Ensure block attestations are processed
* Fix failing beacon_chain tests
* Add first invalid block check
* Add finalized block check
* Progress with testing, new store builder
* Add fixes to get_ancestor
* Fix old genesis justification test
* Fix remaining fork choice tests
* Change root iteration method
* Move on_verified_block
* Remove unused method
* Start adding attestation verification tests
* Add invalid ffg target test
* Add target epoch test
* Add queued attestation test
* Remove old fork choice verification tests
* Tidy, add test
* Move fork choice lock drop
* Rename BeaconForkChoiceStore
* Add comments, tidy BeaconForkChoiceStore
* Update metrics, rename fork_choice_store.rs
* Remove genesis_block_root from ForkChoice
* Tidy
* Update fork_choice comments
* Tidy, add comments
* Tidy, simplify ForkChoice, fix compile issue
* Tidy, removed dead file
* Increase http request timeout
* Fix failing rest_api test
* Set HTTP timeout back to 5s
* Apply fix to get_ancestor
* Address Michael's comments
* Fix typo
* Revert "Fix broken attestation verification test"
This reverts commit 722cdc903b12611de27916a57eeecfa3224f2279.
Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-06-17 01:10:22 +00:00
|
|
|
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec> {
|
|
|
|
&self.indexed_attestation
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedUnaggregatedAttestation<'a, T> {
|
|
|
|
fn attestation(&self) -> &Attestation<T::EthSpec> {
|
|
|
|
self.attestation
|
|
|
|
}
|
|
|
|
|
v0.12 fork choice update (#1229)
* Incomplete scraps
* Add progress on new fork choice impl
* Further progress
* First complete compiling version
* Remove chain reference
* Add new lmd_ghost crate
* Start integrating into beacon chain
* Update `milagro_bls` to new release (#1183)
* Update milagro_bls to new release
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Tidy up fake cryptos
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* move SecretHash to bls and put plaintext back
Signed-off-by: Kirk Baird <baird.k@outlook.com>
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Rough beacon chain impl working
* Remove fork_choice_2
* Remove checkpoint manager
* Half finished ssz impl
* Add missed file
* Add persistence
* Tidy, fix some compile errors
* Remove RwLock from ProtoArrayForkChoice
* Fix store-based compile errors
* Add comments, tidy
* Move function out of ForkChoice struct
* Start testing
* More testing
* Fix compile error
* Tidy beacon_chain::fork_choice
* Queue attestations from the current slot
* Allow fork choice to handle prior-to-genesis start
* Improve error granularity
* Test attestation dequeuing
* Process attestations during block
* Store target root in fork choice
* Move fork choice verification into new crate
* Update tests
* Consensus updates for v0.12 (#1228)
* Update state processing for v0.12
* Fix EF test runners for v0.12
* Fix some tests
* Fix broken attestation verification test
* More test fixes
* Fix typo found in review
* Add `Block` struct to ProtoArray
* Start fixing get_ancestor
* Add rough progress on testing
* Get fork choice tests working
* Progress with testing
* Fix partialeq impl
* Move slot clock from fc_store
* Improve testing
* Add testing for best justified
* Add clone back to SystemTimeSlotClock
* Add balances test
* Start adding balances cache again
* Wire-in balances cache
* Improve tests
* Remove commented-out tests
* Remove beacon_chain::ForkChoice
* Rename crates
* Update wider codebase to new fork_choice layout
* Move advance_slot in test harness
* Tidy ForkChoice::update_time
* Fix verification tests
* Fix compile error with iter::once
* Fix fork choice tests
* Ensure block attestations are processed
* Fix failing beacon_chain tests
* Add first invalid block check
* Add finalized block check
* Progress with testing, new store builder
* Add fixes to get_ancestor
* Fix old genesis justification test
* Fix remaining fork choice tests
* Change root iteration method
* Move on_verified_block
* Remove unused method
* Start adding attestation verification tests
* Add invalid ffg target test
* Add target epoch test
* Add queued attestation test
* Remove old fork choice verification tests
* Tidy, add test
* Move fork choice lock drop
* Rename BeaconForkChoiceStore
* Add comments, tidy BeaconForkChoiceStore
* Update metrics, rename fork_choice_store.rs
* Remove genesis_block_root from ForkChoice
* Tidy
* Update fork_choice comments
* Tidy, add comments
* Tidy, simplify ForkChoice, fix compile issue
* Tidy, removed dead file
* Increase http request timeout
* Fix failing rest_api test
* Set HTTP timeout back to 5s
* Apply fix to get_ancestor
* Address Michael's comments
* Fix typo
* Revert "Fix broken attestation verification test"
This reverts commit 722cdc903b12611de27916a57eeecfa3224f2279.
Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-06-17 01:10:22 +00:00
|
|
|
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec> {
|
|
|
|
&self.indexed_attestation
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
/// Information about invalid attestations which might still be slashable despite being invalid.
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
pub enum AttestationSlashInfo<'a, T: BeaconChainTypes, TErr> {
|
2020-11-23 03:43:22 +00:00
|
|
|
/// The attestation is invalid, but its signature wasn't checked.
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
SignatureNotChecked(&'a Attestation<T::EthSpec>, TErr),
|
2020-11-23 03:43:22 +00:00
|
|
|
/// 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.
|
|
|
|
SignatureInvalid(TErr),
|
|
|
|
/// The signature is valid but the attestation is invalid in some other way.
|
|
|
|
SignatureValid(IndexedAttestation<T::EthSpec>, TErr),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// After processing an attestation normally, optionally process it further for the slasher.
|
|
|
|
///
|
|
|
|
/// This maps an `AttestationSlashInfo` error back into a regular `Error`, performing signature
|
|
|
|
/// checks on attestations that failed verification for other reasons.
|
|
|
|
///
|
|
|
|
/// No substantial extra work will be done if there is no slasher configured.
|
|
|
|
fn process_slash_info<T: BeaconChainTypes>(
|
|
|
|
slash_info: AttestationSlashInfo<T, Error>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Error {
|
|
|
|
use AttestationSlashInfo::*;
|
|
|
|
|
|
|
|
if let Some(slasher) = chain.slasher.as_ref() {
|
|
|
|
let (indexed_attestation, check_signature, err) = match slash_info {
|
|
|
|
SignatureNotChecked(attestation, err) => {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
match obtain_indexed_attestation_and_committees_per_slot(chain, attestation) {
|
2020-11-23 03:43:22 +00:00
|
|
|
Ok((indexed, _)) => (indexed, true, err),
|
|
|
|
Err(e) => {
|
|
|
|
debug!(
|
|
|
|
chain.log,
|
|
|
|
"Unable to obtain indexed form of attestation for slasher";
|
|
|
|
"attestation_root" => format!("{:?}", attestation.tree_hash_root()),
|
|
|
|
"error" => format!("{:?}", e)
|
|
|
|
);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SignatureNotCheckedIndexed(indexed, err) => (indexed, true, err),
|
|
|
|
SignatureInvalid(e) => return e,
|
|
|
|
SignatureValid(indexed, err) => (indexed, false, err),
|
|
|
|
};
|
|
|
|
|
|
|
|
if check_signature {
|
|
|
|
if let Err(e) = verify_attestation_signature(chain, &indexed_attestation) {
|
|
|
|
debug!(
|
|
|
|
chain.log,
|
|
|
|
"Signature verification for slasher failed";
|
|
|
|
"error" => format!("{:?}", e),
|
|
|
|
);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Supply to slasher.
|
|
|
|
slasher.accept_attestation(indexed_attestation);
|
|
|
|
|
|
|
|
err
|
|
|
|
} else {
|
|
|
|
match slash_info {
|
|
|
|
SignatureNotChecked(_, e)
|
|
|
|
| SignatureNotCheckedIndexed(_, e)
|
|
|
|
| SignatureInvalid(e)
|
|
|
|
| SignatureValid(_, e) => e,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Returns `Ok(Self)` if the `signed_aggregate` is valid to be (re)published on the gossip
|
|
|
|
/// network.
|
|
|
|
pub fn verify(
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
|
2020-05-06 11:42:56 +00:00
|
|
|
chain: &BeaconChain<T>,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
) -> Result<Self, Error> {
|
2020-11-23 03:43:22 +00:00
|
|
|
Self::verify_slashable(signed_aggregate, chain)
|
|
|
|
.map(|verified_aggregate| {
|
|
|
|
if let Some(slasher) = chain.slasher.as_ref() {
|
|
|
|
slasher.accept_attestation(verified_aggregate.indexed_attestation.clone());
|
|
|
|
}
|
|
|
|
verified_aggregate
|
|
|
|
})
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
.map_err(|slash_info| process_slash_info(slash_info, chain))
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Run the checks that happen before an indexed attestation is constructed.
|
|
|
|
fn verify_early_checks(
|
|
|
|
signed_aggregate: &SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Result<Hash256, Error> {
|
2020-05-06 11:42:56 +00:00
|
|
|
let attestation = &signed_aggregate.message.aggregate;
|
|
|
|
|
|
|
|
// Ensure attestation is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a
|
|
|
|
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
|
|
|
|
//
|
|
|
|
// We do not queue future attestations for later processing.
|
2022-01-12 02:36:24 +00:00
|
|
|
verify_propagation_slot_range(&chain.slot_clock, attestation)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
2021-04-13 00:24:39 +00:00
|
|
|
// Check the attestation's epoch matches its target.
|
|
|
|
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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-05 01:32:46 +00:00
|
|
|
// Ensure the valid aggregated attestation has not already been seen locally.
|
2020-05-06 11:42:56 +00:00
|
|
|
let attestation_root = attestation.tree_hash_root();
|
|
|
|
if chain
|
|
|
|
.observed_attestations
|
2020-11-22 23:02:51 +00:00
|
|
|
.write()
|
2020-05-06 11:42:56 +00:00
|
|
|
.is_known(attestation, attestation_root)
|
|
|
|
.map_err(|e| Error::BeaconChainError(e.into()))?
|
|
|
|
{
|
|
|
|
return Err(Error::AttestationAlreadyKnown(attestation_root));
|
|
|
|
}
|
|
|
|
|
|
|
|
let aggregator_index = signed_aggregate.message.aggregator_index;
|
|
|
|
|
|
|
|
// Ensure there has been no other observed aggregate for the given `aggregator_index`.
|
|
|
|
//
|
2020-11-28 05:30:57 +00:00
|
|
|
// Note: do not observe yet, only observe once the attestation has been verified.
|
2020-05-06 11:42:56 +00:00
|
|
|
match chain
|
|
|
|
.observed_aggregators
|
2020-11-22 23:02:51 +00:00
|
|
|
.read()
|
2021-07-15 00:52:02 +00:00
|
|
|
.validator_has_been_observed(attestation.data.target.epoch, aggregator_index as usize)
|
2020-05-06 11:42:56 +00:00
|
|
|
{
|
|
|
|
Ok(true) => Err(Error::AggregatorAlreadyKnown(aggregator_index)),
|
|
|
|
Ok(false) => Ok(()),
|
|
|
|
Err(ObservedAttestersError::ValidatorIndexTooHigh(i)) => {
|
|
|
|
Err(Error::ValidatorIndexTooHigh(i))
|
|
|
|
}
|
|
|
|
Err(e) => Err(BeaconChainError::from(e).into()),
|
|
|
|
}?;
|
|
|
|
|
|
|
|
// Ensure the block being voted for (attestation.data.beacon_block_root) passes validation.
|
2020-08-17 10:54:58 +00:00
|
|
|
// Don't enforce the skip slot restriction for aggregates.
|
2020-05-06 11:42:56 +00:00
|
|
|
//
|
|
|
|
// This indirectly checks to see if the `attestation.data.beacon_block_root` is in our fork
|
|
|
|
// choice. Any known, non-finalized, processed block should be in fork choice, so this
|
|
|
|
// check immediately filters out attestations that attest to a block that has not been
|
|
|
|
// processed.
|
|
|
|
//
|
|
|
|
// Attestations must be for a known block. If the block is unknown, we simply drop the
|
|
|
|
// attestation and do not delay consideration for later.
|
2021-07-30 01:11:47 +00:00
|
|
|
let head_block = verify_head_block_is_known(chain, attestation, None)?;
|
2020-10-02 10:46:37 +00:00
|
|
|
|
|
|
|
// Check the attestation target root is consistent with the head root.
|
|
|
|
//
|
|
|
|
// This check is not in the specification, however we guard against it since it opens us up
|
|
|
|
// to weird edge cases during verification.
|
|
|
|
//
|
|
|
|
// Whilst this attestation *technically* could be used to add value to a block, it is
|
|
|
|
// invalid in the spirit of the protocol. Here we choose safety over profit.
|
2021-07-30 01:11:47 +00:00
|
|
|
verify_attestation_target_root::<T::EthSpec>(&head_block, attestation)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
2020-06-05 01:32:46 +00:00
|
|
|
// Ensure that the attestation has participants.
|
|
|
|
if attestation.aggregation_bits.is_zero() {
|
2020-11-23 03:43:22 +00:00
|
|
|
Err(Error::EmptyAggregationBitfield)
|
|
|
|
} else {
|
|
|
|
Ok(attestation_root)
|
2020-06-05 01:32:46 +00:00
|
|
|
}
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
2020-06-05 01:32:46 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Verify the attestation, producing extra information about whether it might be slashable.
|
|
|
|
pub fn verify_slashable(
|
|
|
|
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
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_root = match Self::verify_early_checks(signed_aggregate, chain) {
|
|
|
|
Ok(root) => root,
|
|
|
|
Err(e) => return Err(SignatureNotChecked(&signed_aggregate.message.aggregate, e)),
|
|
|
|
};
|
|
|
|
|
|
|
|
let indexed_attestation =
|
|
|
|
match map_attestation_committee(chain, attestation, |(committee, _)| {
|
|
|
|
// 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());
|
|
|
|
|
|
|
|
if !selection_proof
|
|
|
|
.is_aggregator(committee.committee.len(), &chain.spec)
|
|
|
|
.map_err(|e| Error::BeaconChainError(e.into()))?
|
|
|
|
{
|
|
|
|
return Err(Error::InvalidSelectionProof { aggregator_index });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the aggregator is a member of the committee for which it is aggregating.
|
|
|
|
if !committee.committee.contains(&(aggregator_index as usize)) {
|
|
|
|
return Err(Error::AggregatorNotInCommittee { aggregator_index });
|
|
|
|
}
|
|
|
|
|
|
|
|
get_indexed_attestation(committee.committee, attestation)
|
|
|
|
.map_err(|e| BeaconChainError::from(e).into())
|
|
|
|
}) {
|
|
|
|
Ok(indexed_attestation) => indexed_attestation,
|
|
|
|
Err(e) => return Err(SignatureNotChecked(&signed_aggregate.message.aggregate, e)),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(IndexedAggregatedAttestation {
|
|
|
|
signed_aggregate,
|
|
|
|
indexed_attestation,
|
|
|
|
attestation_root,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> {
|
2020-11-23 03:43:22 +00:00
|
|
|
/// Run the checks that happen after the indexed attestation and signature have been checked.
|
|
|
|
fn verify_late_checks(
|
|
|
|
signed_aggregate: &SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
attestation_root: Hash256,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let attestation = &signed_aggregate.message.aggregate;
|
|
|
|
let aggregator_index = signed_aggregate.message.aggregator_index;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
// Observe the valid attestation so we do not re-process it.
|
|
|
|
//
|
|
|
|
// It's important to double check that the attestation is not already known, otherwise two
|
|
|
|
// attestations processed at the same time could be published.
|
|
|
|
if let ObserveOutcome::AlreadyKnown = chain
|
|
|
|
.observed_attestations
|
2020-11-22 23:02:51 +00:00
|
|
|
.write()
|
2021-07-15 00:52:02 +00:00
|
|
|
.observe_item(attestation, Some(attestation_root))
|
2020-05-06 11:42:56 +00:00
|
|
|
.map_err(|e| Error::BeaconChainError(e.into()))?
|
|
|
|
{
|
|
|
|
return Err(Error::AttestationAlreadyKnown(attestation_root));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Observe the aggregator so we don't process another aggregate from them.
|
|
|
|
//
|
|
|
|
// It's important to double check that the attestation is not already known, otherwise two
|
|
|
|
// attestations processed at the same time could be published.
|
|
|
|
if chain
|
|
|
|
.observed_aggregators
|
2020-11-22 23:02:51 +00:00
|
|
|
.write()
|
2021-07-15 00:52:02 +00:00
|
|
|
.observe_validator(attestation.data.target.epoch, aggregator_index as usize)
|
2020-07-23 14:18:00 +00:00
|
|
|
.map_err(BeaconChainError::from)?
|
2020-05-06 11:42:56 +00:00
|
|
|
{
|
|
|
|
return Err(Error::PriorAttestationKnown {
|
|
|
|
validator_index: aggregator_index,
|
|
|
|
epoch: attestation.data.target.epoch,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Verify the `signed_aggregate`.
|
|
|
|
pub fn verify(
|
|
|
|
signed_aggregate: &'a SignedAggregateAndProof<T::EthSpec>,
|
2020-11-23 03:43:22 +00:00
|
|
|
chain: &BeaconChain<T>,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let indexed = IndexedAggregatedAttestation::verify(signed_aggregate, chain)?;
|
|
|
|
Self::from_indexed(indexed, chain, CheckAttestationSignature::Yes)
|
|
|
|
}
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Complete the verification of an indexed attestation.
|
|
|
|
fn from_indexed(
|
|
|
|
signed_aggregate: IndexedAggregatedAttestation<'a, T>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
check_signature: CheckAttestationSignature,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
Self::verify_slashable(signed_aggregate, chain, check_signature)
|
|
|
|
.map(|verified_aggregate| verified_aggregate.apply_to_slasher(chain))
|
|
|
|
.map_err(|slash_info| process_slash_info(slash_info, chain))
|
|
|
|
}
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
fn apply_to_slasher(self, chain: &BeaconChain<T>) -> Self {
|
|
|
|
if let Some(slasher) = chain.slasher.as_ref() {
|
|
|
|
slasher.accept_attestation(self.indexed_attestation.clone());
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Verify the attestation, producing extra information about whether it might be slashable.
|
|
|
|
fn verify_slashable(
|
|
|
|
signed_aggregate: IndexedAggregatedAttestation<'a, T>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
check_signature: CheckAttestationSignature,
|
|
|
|
) -> Result<Self, AttestationSlashInfo<'a, T, Error>> {
|
|
|
|
use AttestationSlashInfo::*;
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
let IndexedAggregatedAttestation {
|
|
|
|
signed_aggregate,
|
|
|
|
indexed_attestation,
|
|
|
|
attestation_root,
|
|
|
|
} = signed_aggregate;
|
|
|
|
|
|
|
|
match check_signature {
|
|
|
|
CheckAttestationSignature::Yes => {
|
|
|
|
// Ensure that all signatures are valid.
|
|
|
|
if let Err(e) = verify_signed_aggregate_signatures(
|
|
|
|
chain,
|
|
|
|
signed_aggregate,
|
|
|
|
&indexed_attestation,
|
|
|
|
)
|
2020-11-23 03:43:22 +00:00
|
|
|
.and_then(|is_valid| {
|
|
|
|
if !is_valid {
|
|
|
|
Err(Error::InvalidSignature)
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
}) {
|
|
|
|
return Err(SignatureInvalid(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CheckAttestationSignature::No => (),
|
|
|
|
};
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
if let Err(e) = Self::verify_late_checks(signed_aggregate, attestation_root, chain) {
|
|
|
|
return Err(SignatureValid(indexed_attestation, e));
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
Ok(VerifiedAggregatedAttestation {
|
|
|
|
signed_aggregate,
|
|
|
|
indexed_attestation,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the underlying `attestation` for the `signed_aggregate`.
|
|
|
|
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
|
|
|
|
&self.signed_aggregate.message.aggregate
|
|
|
|
}
|
2020-09-29 03:46:54 +00:00
|
|
|
|
|
|
|
/// Returns the underlying `signed_aggregate`.
|
|
|
|
pub fn aggregate(&self) -> &SignedAggregateAndProof<T::EthSpec> {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
self.signed_aggregate
|
2020-09-29 03:46:54 +00:00
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
|
2020-11-23 03:43:22 +00:00
|
|
|
/// Run the checks that happen before an indexed attestation is constructed.
|
|
|
|
pub fn verify_early_checks(
|
|
|
|
attestation: &Attestation<T::EthSpec>,
|
2020-05-06 11:42:56 +00:00
|
|
|
chain: &BeaconChain<T>,
|
2020-11-23 03:43:22 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-09-27 20:59:40 +00:00
|
|
|
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 {
|
|
|
|
return Err(Error::InvalidTargetEpoch {
|
|
|
|
slot: attestation.data.slot,
|
|
|
|
epoch: attestation.data.target.epoch,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
// Ensure attestation is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a
|
|
|
|
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
|
|
|
|
//
|
|
|
|
// We do not queue future attestations for later processing.
|
2022-01-12 02:36:24 +00:00
|
|
|
verify_propagation_slot_range(&chain.slot_clock, attestation)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
// Check to ensure that the attestation is "unaggregated". I.e., it has exactly one
|
|
|
|
// aggregation bit set.
|
2020-09-27 20:59:40 +00:00
|
|
|
let num_aggregation_bits = attestation.aggregation_bits.num_set_bits();
|
|
|
|
if num_aggregation_bits != 1 {
|
|
|
|
return Err(Error::NotExactlyOneAggregationBitSet(num_aggregation_bits));
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Attestations must be for a known block. If the block is unknown, we simply drop the
|
|
|
|
// attestation and do not delay consideration for later.
|
2020-08-17 10:54:58 +00:00
|
|
|
//
|
|
|
|
// Enforce a maximum skip distance for unaggregated attestations.
|
2020-09-27 20:59:40 +00:00
|
|
|
let head_block =
|
2021-07-30 01:11:47 +00:00
|
|
|
verify_head_block_is_known(chain, attestation, chain.config.import_max_skip_slots)?;
|
2020-09-27 20:59:40 +00:00
|
|
|
|
2020-10-02 10:46:37 +00:00
|
|
|
// Check the attestation target root is consistent with the head root.
|
2021-07-30 01:11:47 +00:00
|
|
|
verify_attestation_target_root::<T::EthSpec>(&head_block, attestation)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-06-18 09:11:03 +00:00
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
/// Run the checks that apply to the indexed attestation before the signature is checked.
|
|
|
|
pub fn verify_middle_checks(
|
|
|
|
attestation: &Attestation<T::EthSpec>,
|
|
|
|
indexed_attestation: &IndexedAttestation<T::EthSpec>,
|
|
|
|
committees_per_slot: u64,
|
|
|
|
subnet_id: Option<SubnetId>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Result<(u64, SubnetId), Error> {
|
2020-06-18 09:11:03 +00:00
|
|
|
let expected_subnet_id = SubnetId::compute_subnet_for_attestation_data::<T::EthSpec>(
|
|
|
|
&indexed_attestation.data,
|
|
|
|
committees_per_slot,
|
|
|
|
&chain.spec,
|
|
|
|
)
|
|
|
|
.map_err(BeaconChainError::from)?;
|
|
|
|
|
2020-09-29 03:46:54 +00:00
|
|
|
// If a subnet was specified, ensure that subnet is correct.
|
|
|
|
if let Some(subnet_id) = subnet_id {
|
|
|
|
if subnet_id != expected_subnet_id {
|
|
|
|
return Err(Error::InvalidSubnetId {
|
|
|
|
received: subnet_id,
|
|
|
|
expected: expected_subnet_id,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
let validator_index = *indexed_attestation
|
|
|
|
.attesting_indices
|
|
|
|
.first()
|
2020-12-03 01:10:26 +00:00
|
|
|
.ok_or(Error::NotExactlyOneAggregationBitSet(0))?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The attestation is the first valid attestation received for the participating validator
|
|
|
|
* for the slot, attestation.data.slot.
|
|
|
|
*/
|
|
|
|
if chain
|
2021-08-09 02:43:03 +00:00
|
|
|
.observed_gossip_attesters
|
2020-11-22 23:02:51 +00:00
|
|
|
.read()
|
2021-07-15 00:52:02 +00:00
|
|
|
.validator_has_been_observed(attestation.data.target.epoch, validator_index as usize)
|
2020-07-23 14:18:00 +00:00
|
|
|
.map_err(BeaconChainError::from)?
|
2020-05-06 11:42:56 +00:00
|
|
|
{
|
|
|
|
return Err(Error::PriorAttestationKnown {
|
|
|
|
validator_index,
|
|
|
|
epoch: attestation.data.target.epoch,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
Ok((validator_index, expected_subnet_id))
|
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
|
2020-11-23 03:43:22 +00:00
|
|
|
/// Returns `Ok(Self)` if the `attestation` is valid to be (re)published on the gossip
|
|
|
|
/// network.
|
|
|
|
///
|
|
|
|
/// `subnet_id` is the subnet from which we received this attestation. This function will
|
|
|
|
/// verify that it was received on the correct subnet.
|
|
|
|
pub fn verify(
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
attestation: &'a Attestation<T::EthSpec>,
|
2020-11-23 03:43:22 +00:00
|
|
|
subnet_id: Option<SubnetId>,
|
|
|
|
chain: &BeaconChain<T>,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
) -> Result<Self, Error> {
|
2020-11-23 03:43:22 +00:00
|
|
|
Self::verify_slashable(attestation, subnet_id, chain)
|
|
|
|
.map(|verified_unaggregated| {
|
|
|
|
if let Some(slasher) = chain.slasher.as_ref() {
|
|
|
|
slasher.accept_attestation(verified_unaggregated.indexed_attestation.clone());
|
|
|
|
}
|
|
|
|
verified_unaggregated
|
|
|
|
})
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
.map_err(|slash_info| process_slash_info(slash_info, chain))
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify the attestation, producing extra information about whether it might be slashable.
|
|
|
|
pub fn verify_slashable(
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
attestation: &'a Attestation<T::EthSpec>,
|
2020-11-23 03:43:22 +00:00
|
|
|
subnet_id: Option<SubnetId>,
|
|
|
|
chain: &BeaconChain<T>,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
) -> Result<Self, AttestationSlashInfo<'a, T, Error>> {
|
2020-11-23 03:43:22 +00:00
|
|
|
use AttestationSlashInfo::*;
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
if let Err(e) = Self::verify_early_checks(attestation, chain) {
|
|
|
|
return Err(SignatureNotChecked(attestation, e));
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let (indexed_attestation, committees_per_slot) =
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
match obtain_indexed_attestation_and_committees_per_slot(chain, attestation) {
|
2020-11-23 03:43:22 +00:00
|
|
|
Ok(x) => x,
|
|
|
|
Err(e) => {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
return Err(SignatureNotChecked(attestation, e));
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let (validator_index, expected_subnet_id) = match Self::verify_middle_checks(
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
attestation,
|
2020-11-23 03:43:22 +00:00
|
|
|
&indexed_attestation,
|
|
|
|
committees_per_slot,
|
|
|
|
subnet_id,
|
|
|
|
chain,
|
|
|
|
) {
|
|
|
|
Ok(t) => t,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
Err(e) => return Err(SignatureNotCheckedIndexed(indexed_attestation, e)),
|
2020-11-23 03:43:22 +00:00
|
|
|
};
|
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
Ok(Self {
|
|
|
|
attestation,
|
|
|
|
indexed_attestation,
|
|
|
|
subnet_id: expected_subnet_id,
|
|
|
|
validator_index,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a mutable reference to the underlying attestation.
|
|
|
|
///
|
|
|
|
/// Only use during testing since modifying the `IndexedAttestation` can cause the attestation
|
|
|
|
/// to no-longer be valid.
|
|
|
|
pub fn __indexed_attestation_mut(&mut self) -> &mut IndexedAttestation<T::EthSpec> {
|
|
|
|
&mut self.indexed_attestation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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>,
|
|
|
|
validator_index: u64,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Now that the attestation has been fully verified, store that we have received a valid
|
|
|
|
// attestation from this validator.
|
|
|
|
//
|
|
|
|
// It's important to double check that the attestation still hasn't been observed, since
|
|
|
|
// there can be a race-condition if we receive two attestations at the same time and
|
|
|
|
// process them in different threads.
|
|
|
|
if chain
|
|
|
|
.observed_gossip_attesters
|
|
|
|
.write()
|
|
|
|
.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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify the `unaggregated_attestation`.
|
|
|
|
pub fn verify(
|
|
|
|
unaggregated_attestation: &'a Attestation<T::EthSpec>,
|
|
|
|
subnet_id: Option<SubnetId>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let indexed =
|
|
|
|
IndexedUnaggregatedAttestation::verify(unaggregated_attestation, subnet_id, chain)?;
|
|
|
|
Self::from_indexed(indexed, chain, CheckAttestationSignature::Yes)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Complete the verification of an indexed attestation.
|
|
|
|
fn from_indexed(
|
|
|
|
attestation: IndexedUnaggregatedAttestation<'a, T>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
check_signature: CheckAttestationSignature,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
Self::verify_slashable(attestation, chain, check_signature)
|
|
|
|
.map(|verified_unaggregated| verified_unaggregated.apply_to_slasher(chain))
|
|
|
|
.map_err(|slash_info| process_slash_info(slash_info, chain))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn apply_to_slasher(self, chain: &BeaconChain<T>) -> Self {
|
|
|
|
if let Some(slasher) = chain.slasher.as_ref() {
|
|
|
|
slasher.accept_attestation(self.indexed_attestation.clone());
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
self
|
|
|
|
}
|
2020-11-23 03:43:22 +00:00
|
|
|
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
/// Verify the attestation, producing extra information about whether it might be slashable.
|
|
|
|
fn verify_slashable(
|
|
|
|
attestation: IndexedUnaggregatedAttestation<'a, T>,
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
check_signature: CheckAttestationSignature,
|
|
|
|
) -> Result<Self, AttestationSlashInfo<'a, T, Error>> {
|
|
|
|
use AttestationSlashInfo::*;
|
|
|
|
|
|
|
|
let IndexedUnaggregatedAttestation {
|
|
|
|
attestation,
|
|
|
|
indexed_attestation,
|
|
|
|
subnet_id,
|
|
|
|
validator_index,
|
|
|
|
} = attestation;
|
|
|
|
|
|
|
|
match check_signature {
|
|
|
|
CheckAttestationSignature::Yes => {
|
|
|
|
if let Err(e) = verify_attestation_signature(chain, &indexed_attestation) {
|
|
|
|
return Err(SignatureInvalid(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CheckAttestationSignature::No => (),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Err(e) = Self::verify_late_checks(attestation, validator_index, chain) {
|
|
|
|
return Err(SignatureValid(indexed_attestation, e));
|
2020-11-23 03:43:22 +00:00
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
attestation,
|
|
|
|
indexed_attestation,
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
subnet_id,
|
2020-05-06 11:42:56 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-09-29 03:46:54 +00:00
|
|
|
/// Returns the correct subnet for the attestation.
|
|
|
|
pub fn subnet_id(&self) -> SubnetId {
|
|
|
|
self.subnet_id
|
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Returns the wrapped `attestation`.
|
|
|
|
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
|
Batch BLS verification for attestations (#2399)
## Issue Addressed
NA
## Proposed Changes
Adds the ability to verify batches of aggregated/unaggregated attestations from the network.
When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:
- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.
Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches.
### Batching Details
To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:
- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.
The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.
When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.
Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.
## Additional Info
Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.
I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
|
|
|
self.attestation
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
2021-01-20 19:19:38 +00:00
|
|
|
/// Returns the wrapped `indexed_attestation`.
|
|
|
|
pub fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec> {
|
|
|
|
&self.indexed_attestation
|
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Returns a mutable reference to the underlying attestation.
|
|
|
|
///
|
|
|
|
/// Only use during testing since modifying the `IndexedAttestation` can cause the attestation
|
|
|
|
/// to no-longer be valid.
|
|
|
|
pub fn __indexed_attestation_mut(&mut self) -> &mut IndexedAttestation<T::EthSpec> {
|
|
|
|
&mut self.indexed_attestation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `Ok(())` if the `attestation.data.beacon_block_root` is known to this chain.
|
|
|
|
///
|
|
|
|
/// The block root may not be known for two reasons:
|
|
|
|
///
|
|
|
|
/// 1. The block has never been verified by our application.
|
|
|
|
/// 2. The block is prior to the latest finalized block.
|
|
|
|
///
|
|
|
|
/// Case (1) is the exact thing we're trying to detect. However case (2) is a little different, but
|
|
|
|
/// it's still fine to reject here because there's no need for us to handle attestations that are
|
|
|
|
/// already finalized.
|
|
|
|
fn verify_head_block_is_known<T: BeaconChainTypes>(
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
attestation: &Attestation<T::EthSpec>,
|
2020-08-17 10:54:58 +00:00
|
|
|
max_skip_slots: Option<u64>,
|
2020-09-27 20:59:40 +00:00
|
|
|
) -> Result<ProtoBlock, Error> {
|
2022-01-11 01:35:55 +00:00
|
|
|
let block_opt = chain
|
Use async code when interacting with EL (#3244)
## Overview
This rather extensive PR achieves two primary goals:
1. Uses the finalized/justified checkpoints of fork choice (FC), rather than that of the head state.
2. Refactors fork choice, block production and block processing to `async` functions.
Additionally, it achieves:
- Concurrent forkchoice updates to the EL and cache pruning after a new head is selected.
- Concurrent "block packing" (attestations, etc) and execution payload retrieval during block production.
- Concurrent per-block-processing and execution payload verification during block processing.
- The `Arc`-ification of `SignedBeaconBlock` during block processing (it's never mutated, so why not?):
- I had to do this to deal with sending blocks into spawned tasks.
- Previously we were cloning the beacon block at least 2 times during each block processing, these clones are either removed or turned into cheaper `Arc` clones.
- We were also `Box`-ing and un-`Box`-ing beacon blocks as they moved throughout the networking crate. This is not a big deal, but it's nice to avoid shifting things between the stack and heap.
- Avoids cloning *all the blocks* in *every chain segment* during sync.
- It also has the potential to clean up our code where we need to pass an *owned* block around so we can send it back in the case of an error (I didn't do much of this, my PR is already big enough :sweat_smile:)
- The `BeaconChain::HeadSafetyStatus` struct was removed. It was an old relic from prior merge specs.
For motivation for this change, see https://github.com/sigp/lighthouse/pull/3244#issuecomment-1160963273
## Changes to `canonical_head` and `fork_choice`
Previously, the `BeaconChain` had two separate fields:
```
canonical_head: RwLock<Snapshot>,
fork_choice: RwLock<BeaconForkChoice>
```
Now, we have grouped these values under a single struct:
```
canonical_head: CanonicalHead {
cached_head: RwLock<Arc<Snapshot>>,
fork_choice: RwLock<BeaconForkChoice>
}
```
Apart from ergonomics, the only *actual* change here is wrapping the canonical head snapshot in an `Arc`. This means that we no longer need to hold the `cached_head` (`canonical_head`, in old terms) lock when we want to pull some values from it. This was done to avoid deadlock risks by preventing functions from acquiring (and holding) the `cached_head` and `fork_choice` locks simultaneously.
## Breaking Changes
### The `state` (root) field in the `finalized_checkpoint` SSE event
Consider the scenario where epoch `n` is just finalized, but `start_slot(n)` is skipped. There are two state roots we might in the `finalized_checkpoint` SSE event:
1. The state root of the finalized block, which is `get_block(finalized_checkpoint.root).state_root`.
4. The state root at slot of `start_slot(n)`, which would be the state from (1), but "skipped forward" through any skip slots.
Previously, Lighthouse would choose (2). However, we can see that when [Teku generates that event](https://github.com/ConsenSys/teku/blob/de2b2801c89ef5abf983d6bf37867c37fc47121f/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java#L171-L182) it uses [`getStateRootFromBlockRoot`](https://github.com/ConsenSys/teku/blob/de2b2801c89ef5abf983d6bf37867c37fc47121f/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java#L336-L341) which uses (1).
I have switched Lighthouse from (2) to (1). I think it's a somewhat arbitrary choice between the two, where (1) is easier to compute and is consistent with Teku.
## Notes for Reviewers
I've renamed `BeaconChain::fork_choice` to `BeaconChain::recompute_head`. Doing this helped ensure I broke all previous uses of fork choice and I also find it more descriptive. It describes an action and can't be confused with trying to get a reference to the `ForkChoice` struct.
I've changed the ordering of SSE events when a block is received. It used to be `[block, finalized, head]` and now it's `[block, head, finalized]`. It was easier this way and I don't think we were making any promises about SSE event ordering so it's not "breaking".
I've made it so fork choice will run when it's first constructed. I did this because I wanted to have a cached version of the last call to `get_head`. Ensuring `get_head` has been run *at least once* means that the cached values doesn't need to wrapped in an `Option`. This was fairly simple, it just involved passing a `slot` to the constructor so it knows *when* it's being run. When loading a fork choice from the store and a slot clock isn't handy I've just used the `slot` that was saved in the `fork_choice_store`. That seems like it would be a faithful representation of the slot when we saved it.
I added the `genesis_time: u64` to the `BeaconChain`. It's small, constant and nice to have around.
Since we're using FC for the fin/just checkpoints, we no longer get the `0x00..00` roots at genesis. You can see I had to remove a work-around in `ef-tests` here: b56be3bc2. I can't find any reason why this would be an issue, if anything I think it'll be better since the genesis-alias has caught us out a few times (0x00..00 isn't actually a real root). Edit: I did find a case where the `network` expected the 0x00..00 alias and patched it here: 3f26ac3e2.
You'll notice a lot of changes in tests. Generally, tests should be functionally equivalent. Here are the things creating the most diff-noise in tests:
- Changing tests to be `tokio::async` tests.
- Adding `.await` to fork choice, block processing and block production functions.
- Refactor of the `canonical_head` "API" provided by the `BeaconChain`. E.g., `chain.canonical_head.cached_head()` instead of `chain.canonical_head.read()`.
- Wrapping `SignedBeaconBlock` in an `Arc`.
- In the `beacon_chain/tests/block_verification`, we can't use the `lazy_static` `CHAIN_SEGMENT` variable anymore since it's generated with an async function. We just generate it in each test, not so efficient but hopefully insignificant.
I had to disable `rayon` concurrent tests in the `fork_choice` tests. This is because the use of `rayon` and `block_on` was causing a panic.
Co-authored-by: Mac L <mjladson@pm.me>
2022-07-03 05:36:50 +00:00
|
|
|
.canonical_head
|
|
|
|
.fork_choice_read_lock()
|
2020-08-17 10:54:58 +00:00
|
|
|
.get_block(&attestation.data.beacon_block_root)
|
2022-01-11 01:35:55 +00:00
|
|
|
.or_else(|| {
|
|
|
|
chain
|
|
|
|
.early_attester_cache
|
|
|
|
.get_proto_block(attestation.data.beacon_block_root)
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(block) = block_opt {
|
2020-08-17 10:54:58 +00:00
|
|
|
// Reject any block that exceeds our limit on skipped slots.
|
|
|
|
if let Some(max_skip_slots) = max_skip_slots {
|
2020-08-18 06:28:26 +00:00
|
|
|
if attestation.data.slot > block.slot + max_skip_slots {
|
2020-08-17 10:54:58 +00:00
|
|
|
return Err(Error::TooManySkippedSlots {
|
|
|
|
head_block_slot: block.slot,
|
|
|
|
attestation_slot: attestation.data.slot,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-09-29 03:46:54 +00:00
|
|
|
|
2020-09-27 20:59:40 +00:00
|
|
|
Ok(block)
|
2022-01-27 22:58:32 +00:00
|
|
|
} else if chain.is_pre_finalization_block(attestation.data.beacon_block_root)? {
|
|
|
|
Err(Error::HeadBlockFinalized {
|
|
|
|
beacon_block_root: attestation.data.beacon_block_root,
|
|
|
|
})
|
2020-05-06 11:42:56 +00:00
|
|
|
} else {
|
2022-01-27 22:58:32 +00:00
|
|
|
// The block is either:
|
|
|
|
//
|
|
|
|
// 1) A pre-finalization block that has been pruned. We'll do one network lookup
|
|
|
|
// for it and when it fails we will penalise all involved peers.
|
|
|
|
// 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).
|
2020-05-06 11:42:56 +00:00
|
|
|
Err(Error::UnknownHeadBlock {
|
|
|
|
beacon_block_root: attestation.data.beacon_block_root,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify that the `attestation` is within the acceptable gossip propagation range, with reference
|
|
|
|
/// to the current slot of the `chain`.
|
|
|
|
///
|
|
|
|
/// Accounts for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
2022-01-12 02:36:24 +00:00
|
|
|
pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
|
|
|
|
slot_clock: &S,
|
|
|
|
attestation: &Attestation<E>,
|
2020-05-06 11:42:56 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
let attestation_slot = attestation.data.slot;
|
|
|
|
|
2022-01-12 02:36:24 +00:00
|
|
|
let latest_permissible_slot = slot_clock
|
2020-05-06 11:42:56 +00:00
|
|
|
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
2020-12-03 01:10:26 +00:00
|
|
|
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
if attestation_slot > latest_permissible_slot {
|
|
|
|
return Err(Error::FutureSlot {
|
|
|
|
attestation_slot,
|
|
|
|
latest_permissible_slot,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Taking advantage of saturating subtraction on `Slot`.
|
2022-01-12 02:36:24 +00:00
|
|
|
let earliest_permissible_slot = slot_clock
|
2020-05-06 11:42:56 +00:00
|
|
|
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
2020-12-03 01:10:26 +00:00
|
|
|
.ok_or(BeaconChainError::UnableToReadSlot)?
|
2022-01-12 02:36:24 +00:00
|
|
|
- E::slots_per_epoch();
|
2020-05-06 11:42:56 +00:00
|
|
|
if attestation_slot < earliest_permissible_slot {
|
|
|
|
return Err(Error::PastSlot {
|
|
|
|
attestation_slot,
|
|
|
|
earliest_permissible_slot,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verifies that the signature of the `indexed_attestation` is valid.
|
|
|
|
pub fn verify_attestation_signature<T: BeaconChainTypes>(
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
indexed_attestation: &IndexedAttestation<T::EthSpec>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let signature_setup_timer =
|
|
|
|
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_SIGNATURE_SETUP_TIMES);
|
|
|
|
|
|
|
|
let pubkey_cache = chain
|
|
|
|
.validator_pubkey_cache
|
|
|
|
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
|
2020-12-03 01:10:26 +00:00
|
|
|
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
let fork = chain
|
2021-07-15 00:52:02 +00:00
|
|
|
.spec
|
|
|
|
.fork_at_epoch(indexed_attestation.data.target.epoch);
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
let signature_set = indexed_attestation_signature_set_from_pubkeys(
|
|
|
|
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
|
|
|
|
&indexed_attestation.signature,
|
2021-07-30 01:11:47 +00:00
|
|
|
indexed_attestation,
|
2020-05-06 11:42:56 +00:00
|
|
|
&fork,
|
|
|
|
chain.genesis_validators_root,
|
|
|
|
&chain.spec,
|
|
|
|
)
|
|
|
|
.map_err(BeaconChainError::SignatureSetError)?;
|
|
|
|
|
|
|
|
metrics::stop_timer(signature_setup_timer);
|
|
|
|
|
|
|
|
let _signature_verification_timer =
|
|
|
|
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_SIGNATURE_TIMES);
|
|
|
|
|
2020-07-25 02:03:18 +00:00
|
|
|
if signature_set.verify() {
|
2020-05-06 11:42:56 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::InvalidSignature)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 10:46:37 +00:00
|
|
|
/// Verifies that the `attestation.data.target.root` is indeed the target root of the block at
|
|
|
|
/// `attestation.data.beacon_block_root`.
|
|
|
|
pub fn verify_attestation_target_root<T: EthSpec>(
|
|
|
|
head_block: &ProtoBlock,
|
|
|
|
attestation: &Attestation<T>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Check the attestation target root.
|
|
|
|
let head_block_epoch = head_block.slot.epoch(T::slots_per_epoch());
|
|
|
|
let attestation_epoch = attestation.data.slot.epoch(T::slots_per_epoch());
|
|
|
|
if head_block_epoch > attestation_epoch {
|
|
|
|
// The epoch references an invalid head block from a future epoch.
|
|
|
|
//
|
|
|
|
// This check is not in the specification, however we guard against it since it opens us up
|
|
|
|
// to weird edge cases during verification.
|
|
|
|
//
|
|
|
|
// Whilst this attestation *technically* could be used to add value to a block, it is
|
|
|
|
// invalid in the spirit of the protocol. Here we choose safety over profit.
|
|
|
|
//
|
|
|
|
// Reference:
|
|
|
|
// https://github.com/ethereum/eth2.0-specs/pull/2001#issuecomment-699246659
|
|
|
|
return Err(Error::InvalidTargetRoot {
|
|
|
|
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,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let target_root = if head_block_epoch == attestation_epoch {
|
|
|
|
// If the block is in the same epoch as the attestation, then use the target root
|
|
|
|
// from the block.
|
|
|
|
head_block.target_root
|
|
|
|
} else {
|
|
|
|
// If the head block is from a previous epoch then skip slots will cause the head block
|
|
|
|
// root to become the target block root.
|
|
|
|
//
|
|
|
|
// We know the head block is from a previous epoch due to a previous check.
|
|
|
|
head_block.root
|
|
|
|
};
|
|
|
|
|
|
|
|
// Reject any attestation with an invalid target root.
|
|
|
|
if target_root != attestation.data.target.root {
|
|
|
|
return Err(Error::InvalidTargetRoot {
|
|
|
|
attestation: attestation.data.target.root,
|
|
|
|
expected: Some(target_root),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-05-06 11:42:56 +00:00
|
|
|
/// Verifies all the signatures in a `SignedAggregateAndProof` using BLS batch verification. This
|
|
|
|
/// includes three signatures:
|
|
|
|
///
|
|
|
|
/// - `signed_aggregate.signature`
|
2020-06-05 01:32:46 +00:00
|
|
|
/// - `signed_aggregate.message.selection_proof`
|
|
|
|
/// - `signed_aggregate.message.aggregate.signature`
|
2020-05-06 11:42:56 +00:00
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
///
|
|
|
|
/// - `Ok(true)`: if all signatures are valid.
|
|
|
|
/// - `Ok(false)`: if one or more signatures are invalid.
|
|
|
|
/// - `Err(e)`: if there was an error preventing signature verification.
|
|
|
|
pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
|
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
signed_aggregate: &SignedAggregateAndProof<T::EthSpec>,
|
|
|
|
indexed_attestation: &IndexedAttestation<T::EthSpec>,
|
|
|
|
) -> Result<bool, Error> {
|
|
|
|
let pubkey_cache = chain
|
|
|
|
.validator_pubkey_cache
|
|
|
|
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
|
2020-12-03 01:10:26 +00:00
|
|
|
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
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
|
2021-07-15 00:52:02 +00:00
|
|
|
.spec
|
|
|
|
.fork_at_epoch(indexed_attestation.data.target.epoch);
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
let signature_sets = vec![
|
|
|
|
signed_aggregate_selection_proof_signature_set(
|
|
|
|
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
|
2021-07-30 01:11:47 +00:00
|
|
|
signed_aggregate,
|
2020-05-06 11:42:56 +00:00
|
|
|
&fork,
|
|
|
|
chain.genesis_validators_root,
|
|
|
|
&chain.spec,
|
|
|
|
)
|
|
|
|
.map_err(BeaconChainError::SignatureSetError)?,
|
|
|
|
signed_aggregate_signature_set(
|
|
|
|
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
|
2021-07-30 01:11:47 +00:00
|
|
|
signed_aggregate,
|
2020-05-06 11:42:56 +00:00
|
|
|
&fork,
|
|
|
|
chain.genesis_validators_root,
|
|
|
|
&chain.spec,
|
|
|
|
)
|
|
|
|
.map_err(BeaconChainError::SignatureSetError)?,
|
|
|
|
indexed_attestation_signature_set_from_pubkeys(
|
|
|
|
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
|
|
|
|
&indexed_attestation.signature,
|
2021-07-30 01:11:47 +00:00
|
|
|
indexed_attestation,
|
2020-05-06 11:42:56 +00:00
|
|
|
&fork,
|
|
|
|
chain.genesis_validators_root,
|
|
|
|
&chain.spec,
|
|
|
|
)
|
|
|
|
.map_err(BeaconChainError::SignatureSetError)?,
|
|
|
|
];
|
|
|
|
|
2020-07-25 02:03:18 +00:00
|
|
|
Ok(verify_signature_sets(signature_sets.iter()))
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 09:11:03 +00:00
|
|
|
/// Assists in readability.
|
|
|
|
type CommitteesPerSlot = u64;
|
|
|
|
|
|
|
|
/// Returns the `indexed_attestation` and committee count per slot for the `attestation` using the
|
|
|
|
/// public keys cached in the `chain`.
|
2021-11-08 07:29:04 +00:00
|
|
|
pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
|
2020-05-06 11:42:56 +00:00
|
|
|
chain: &BeaconChain<T>,
|
|
|
|
attestation: &Attestation<T::EthSpec>,
|
2020-06-18 09:11:03 +00:00
|
|
|
) -> Result<(IndexedAttestation<T::EthSpec>, CommitteesPerSlot), Error> {
|
|
|
|
map_attestation_committee(chain, attestation, |(committee, committees_per_slot)| {
|
2021-07-30 01:11:47 +00:00
|
|
|
get_indexed_attestation(committee.committee, attestation)
|
2020-06-18 09:11:03 +00:00
|
|
|
.map(|attestation| (attestation, committees_per_slot))
|
2020-09-27 20:59:40 +00:00
|
|
|
.map_err(Error::Invalid)
|
2020-05-06 11:42:56 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-18 09:11:03 +00:00
|
|
|
/// Runs the `map_fn` with the committee and committee count per slot for the given `attestation`.
|
2020-05-06 11:42:56 +00:00
|
|
|
///
|
|
|
|
/// 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
|
|
|
|
/// `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
|
|
|
|
/// from disk and then update the `shuffling_cache`.
|
2020-09-29 03:46:54 +00:00
|
|
|
fn map_attestation_committee<T, F, R>(
|
|
|
|
chain: &BeaconChain<T>,
|
2020-05-06 11:42:56 +00:00
|
|
|
attestation: &Attestation<T::EthSpec>,
|
|
|
|
map_fn: F,
|
|
|
|
) -> Result<R, Error>
|
|
|
|
where
|
|
|
|
T: BeaconChainTypes,
|
2020-06-18 09:11:03 +00:00
|
|
|
F: Fn((BeaconCommittee, CommitteesPerSlot)) -> Result<R, Error>,
|
2020-05-06 11:42:56 +00:00
|
|
|
{
|
|
|
|
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.
|
|
|
|
//
|
|
|
|
// We use fork choice to find the target root, which means that we reject any attestation
|
|
|
|
// that has a `target.root` earlier than our latest finalized root. There's no point in
|
|
|
|
// processing an attestation that does not include our latest finalized block in its chain.
|
|
|
|
//
|
|
|
|
// We do not delay consideration for later, we simply drop the attestation.
|
Use async code when interacting with EL (#3244)
## Overview
This rather extensive PR achieves two primary goals:
1. Uses the finalized/justified checkpoints of fork choice (FC), rather than that of the head state.
2. Refactors fork choice, block production and block processing to `async` functions.
Additionally, it achieves:
- Concurrent forkchoice updates to the EL and cache pruning after a new head is selected.
- Concurrent "block packing" (attestations, etc) and execution payload retrieval during block production.
- Concurrent per-block-processing and execution payload verification during block processing.
- The `Arc`-ification of `SignedBeaconBlock` during block processing (it's never mutated, so why not?):
- I had to do this to deal with sending blocks into spawned tasks.
- Previously we were cloning the beacon block at least 2 times during each block processing, these clones are either removed or turned into cheaper `Arc` clones.
- We were also `Box`-ing and un-`Box`-ing beacon blocks as they moved throughout the networking crate. This is not a big deal, but it's nice to avoid shifting things between the stack and heap.
- Avoids cloning *all the blocks* in *every chain segment* during sync.
- It also has the potential to clean up our code where we need to pass an *owned* block around so we can send it back in the case of an error (I didn't do much of this, my PR is already big enough :sweat_smile:)
- The `BeaconChain::HeadSafetyStatus` struct was removed. It was an old relic from prior merge specs.
For motivation for this change, see https://github.com/sigp/lighthouse/pull/3244#issuecomment-1160963273
## Changes to `canonical_head` and `fork_choice`
Previously, the `BeaconChain` had two separate fields:
```
canonical_head: RwLock<Snapshot>,
fork_choice: RwLock<BeaconForkChoice>
```
Now, we have grouped these values under a single struct:
```
canonical_head: CanonicalHead {
cached_head: RwLock<Arc<Snapshot>>,
fork_choice: RwLock<BeaconForkChoice>
}
```
Apart from ergonomics, the only *actual* change here is wrapping the canonical head snapshot in an `Arc`. This means that we no longer need to hold the `cached_head` (`canonical_head`, in old terms) lock when we want to pull some values from it. This was done to avoid deadlock risks by preventing functions from acquiring (and holding) the `cached_head` and `fork_choice` locks simultaneously.
## Breaking Changes
### The `state` (root) field in the `finalized_checkpoint` SSE event
Consider the scenario where epoch `n` is just finalized, but `start_slot(n)` is skipped. There are two state roots we might in the `finalized_checkpoint` SSE event:
1. The state root of the finalized block, which is `get_block(finalized_checkpoint.root).state_root`.
4. The state root at slot of `start_slot(n)`, which would be the state from (1), but "skipped forward" through any skip slots.
Previously, Lighthouse would choose (2). However, we can see that when [Teku generates that event](https://github.com/ConsenSys/teku/blob/de2b2801c89ef5abf983d6bf37867c37fc47121f/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java#L171-L182) it uses [`getStateRootFromBlockRoot`](https://github.com/ConsenSys/teku/blob/de2b2801c89ef5abf983d6bf37867c37fc47121f/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java#L336-L341) which uses (1).
I have switched Lighthouse from (2) to (1). I think it's a somewhat arbitrary choice between the two, where (1) is easier to compute and is consistent with Teku.
## Notes for Reviewers
I've renamed `BeaconChain::fork_choice` to `BeaconChain::recompute_head`. Doing this helped ensure I broke all previous uses of fork choice and I also find it more descriptive. It describes an action and can't be confused with trying to get a reference to the `ForkChoice` struct.
I've changed the ordering of SSE events when a block is received. It used to be `[block, finalized, head]` and now it's `[block, head, finalized]`. It was easier this way and I don't think we were making any promises about SSE event ordering so it's not "breaking".
I've made it so fork choice will run when it's first constructed. I did this because I wanted to have a cached version of the last call to `get_head`. Ensuring `get_head` has been run *at least once* means that the cached values doesn't need to wrapped in an `Option`. This was fairly simple, it just involved passing a `slot` to the constructor so it knows *when* it's being run. When loading a fork choice from the store and a slot clock isn't handy I've just used the `slot` that was saved in the `fork_choice_store`. That seems like it would be a faithful representation of the slot when we saved it.
I added the `genesis_time: u64` to the `BeaconChain`. It's small, constant and nice to have around.
Since we're using FC for the fin/just checkpoints, we no longer get the `0x00..00` roots at genesis. You can see I had to remove a work-around in `ef-tests` here: b56be3bc2. I can't find any reason why this would be an issue, if anything I think it'll be better since the genesis-alias has caught us out a few times (0x00..00 isn't actually a real root). Edit: I did find a case where the `network` expected the 0x00..00 alias and patched it here: 3f26ac3e2.
You'll notice a lot of changes in tests. Generally, tests should be functionally equivalent. Here are the things creating the most diff-noise in tests:
- Changing tests to be `tokio::async` tests.
- Adding `.await` to fork choice, block processing and block production functions.
- Refactor of the `canonical_head` "API" provided by the `BeaconChain`. E.g., `chain.canonical_head.cached_head()` instead of `chain.canonical_head.read()`.
- Wrapping `SignedBeaconBlock` in an `Arc`.
- In the `beacon_chain/tests/block_verification`, we can't use the `lazy_static` `CHAIN_SEGMENT` variable anymore since it's generated with an async function. We just generate it in each test, not so efficient but hopefully insignificant.
I had to disable `rayon` concurrent tests in the `fork_choice` tests. This is because the use of `rayon` and `block_on` was causing a panic.
Co-authored-by: Mac L <mjladson@pm.me>
2022-07-03 05:36:50 +00:00
|
|
|
if !chain
|
|
|
|
.canonical_head
|
|
|
|
.fork_choice_read_lock()
|
|
|
|
.contains_block(&target.root)
|
2022-01-11 01:35:55 +00:00
|
|
|
&& !chain.early_attester_cache.contains_block(target.root)
|
|
|
|
{
|
2020-09-29 03:46:54 +00:00
|
|
|
return Err(Error::UnknownTargetRoot(target.root));
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|
2020-09-29 03:46:54 +00:00
|
|
|
|
|
|
|
chain
|
2021-03-17 05:09:57 +00:00
|
|
|
.with_committee_cache(target.root, attestation_epoch, |committee_cache, _| {
|
2020-09-29 03:46:54 +00:00
|
|
|
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(|| {
|
|
|
|
Err(Error::NoCommitteeForSlotAndIndex {
|
|
|
|
slot: attestation.data.slot,
|
|
|
|
index: attestation.data.index,
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
.map_err(BeaconChainError::from)?
|
2020-05-06 11:42:56 +00:00
|
|
|
}
|