mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 10:11:44 +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.
This commit is contained in:
@@ -46,7 +46,8 @@ use eth2_libp2p::{
|
||||
};
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use futures::task::Poll;
|
||||
use slog::{debug, error, trace, warn, Logger};
|
||||
use slog::{crit, debug, error, trace, warn, Logger};
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
@@ -70,7 +71,7 @@ mod tests;
|
||||
mod work_reprocessing_queue;
|
||||
mod worker;
|
||||
|
||||
pub use worker::ProcessId;
|
||||
pub use worker::{GossipAggregatePackage, GossipAttestationPackage, ProcessId};
|
||||
|
||||
/// The maximum size of the channel for work events to the `BeaconProcessor`.
|
||||
///
|
||||
@@ -159,11 +160,27 @@ const WORKER_TASK_NAME: &str = "beacon_processor_worker";
|
||||
/// The minimum interval between log messages indicating that a queue is full.
|
||||
const LOG_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
|
||||
/// The `MAX_..._BATCH_SIZE` variables define how many attestations can be included in a single
|
||||
/// batch.
|
||||
///
|
||||
/// Choosing these values is difficult since there is a trade-off between:
|
||||
///
|
||||
/// - It is faster to verify one large batch than multiple smaller batches.
|
||||
/// - "Poisoning" attacks have a larger impact as the batch size increases.
|
||||
///
|
||||
/// Poisoning occurs when an invalid signature is included in a batch of attestations. A single
|
||||
/// invalid signature causes the entire batch to fail. When a batch fails, we fall-back to
|
||||
/// individually verifying each attestation signature.
|
||||
const MAX_GOSSIP_ATTESTATION_BATCH_SIZE: usize = 64;
|
||||
const MAX_GOSSIP_AGGREGATE_BATCH_SIZE: usize = 64;
|
||||
|
||||
/// Unique IDs used for metrics and testing.
|
||||
pub const WORKER_FREED: &str = "worker_freed";
|
||||
pub const NOTHING_TO_DO: &str = "nothing_to_do";
|
||||
pub const GOSSIP_ATTESTATION: &str = "gossip_attestation";
|
||||
pub const GOSSIP_ATTESTATION_BATCH: &str = "gossip_attestation_batch";
|
||||
pub const GOSSIP_AGGREGATE: &str = "gossip_aggregate";
|
||||
pub const GOSSIP_AGGREGATE_BATCH: &str = "gossip_aggregate_batch";
|
||||
pub const GOSSIP_BLOCK: &str = "gossip_block";
|
||||
pub const DELAYED_IMPORT_BLOCK: &str = "delayed_import_block";
|
||||
pub const GOSSIP_VOLUNTARY_EXIT: &str = "gossip_voluntary_exit";
|
||||
@@ -564,6 +581,9 @@ pub enum Work<T: BeaconChainTypes> {
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
},
|
||||
GossipAttestationBatch {
|
||||
packages: Vec<GossipAttestationPackage<T::EthSpec>>,
|
||||
},
|
||||
GossipAggregate {
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
@@ -576,6 +596,9 @@ pub enum Work<T: BeaconChainTypes> {
|
||||
aggregate: Box<SignedAggregateAndProof<T::EthSpec>>,
|
||||
seen_timestamp: Duration,
|
||||
},
|
||||
GossipAggregateBatch {
|
||||
packages: Vec<GossipAggregatePackage<T::EthSpec>>,
|
||||
},
|
||||
GossipBlock {
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
@@ -644,7 +667,9 @@ impl<T: BeaconChainTypes> Work<T> {
|
||||
fn str_id(&self) -> &'static str {
|
||||
match self {
|
||||
Work::GossipAttestation { .. } => GOSSIP_ATTESTATION,
|
||||
Work::GossipAttestationBatch { .. } => GOSSIP_ATTESTATION_BATCH,
|
||||
Work::GossipAggregate { .. } => GOSSIP_AGGREGATE,
|
||||
Work::GossipAggregateBatch { .. } => GOSSIP_AGGREGATE_BATCH,
|
||||
Work::GossipBlock { .. } => GOSSIP_BLOCK,
|
||||
Work::DelayedImportBlock { .. } => DELAYED_IMPORT_BLOCK,
|
||||
Work::GossipVoluntaryExit { .. } => GOSSIP_VOLUNTARY_EXIT,
|
||||
@@ -922,10 +947,103 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
// Check the aggregates, *then* the unaggregates since we assume that
|
||||
// aggregates are more valuable to local validators and effectively give us
|
||||
// more information with less signature verification time.
|
||||
} else if let Some(item) = aggregate_queue.pop() {
|
||||
self.spawn_worker(item, toolbox);
|
||||
} else if let Some(item) = attestation_queue.pop() {
|
||||
self.spawn_worker(item, toolbox);
|
||||
} else if aggregate_queue.len() > 0 {
|
||||
let batch_size =
|
||||
cmp::min(aggregate_queue.len(), MAX_GOSSIP_AGGREGATE_BATCH_SIZE);
|
||||
|
||||
if batch_size < 2 {
|
||||
// One single aggregate is in the queue, process it individually.
|
||||
if let Some(item) = aggregate_queue.pop() {
|
||||
self.spawn_worker(item, toolbox);
|
||||
}
|
||||
} else {
|
||||
// Collect two or more aggregates into a batch, so they can take
|
||||
// advantage of batch signature verification.
|
||||
//
|
||||
// Note: this will convert the `Work::GossipAggregate` item into a
|
||||
// `Work::GossipAggregateBatch` item.
|
||||
let mut packages = Vec::with_capacity(batch_size);
|
||||
for _ in 0..batch_size {
|
||||
if let Some(item) = aggregate_queue.pop() {
|
||||
match item {
|
||||
Work::GossipAggregate {
|
||||
message_id,
|
||||
peer_id,
|
||||
aggregate,
|
||||
seen_timestamp,
|
||||
} => {
|
||||
packages.push(GossipAggregatePackage::new(
|
||||
message_id,
|
||||
peer_id,
|
||||
aggregate,
|
||||
seen_timestamp,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
error!(self.log, "Invalid item in aggregate queue")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process all aggregates with a single worker.
|
||||
self.spawn_worker(Work::GossipAggregateBatch { packages }, toolbox)
|
||||
}
|
||||
// Check the unaggregated attestation queue.
|
||||
//
|
||||
// Potentially use batching.
|
||||
} else if attestation_queue.len() > 0 {
|
||||
let batch_size = cmp::min(
|
||||
attestation_queue.len(),
|
||||
MAX_GOSSIP_ATTESTATION_BATCH_SIZE,
|
||||
);
|
||||
|
||||
if batch_size < 2 {
|
||||
// One single attestation is in the queue, process it individually.
|
||||
if let Some(item) = attestation_queue.pop() {
|
||||
self.spawn_worker(item, toolbox);
|
||||
}
|
||||
} else {
|
||||
// Collect two or more attestations into a batch, so they can take
|
||||
// advantage of batch signature verification.
|
||||
//
|
||||
// Note: this will convert the `Work::GossipAttestation` item into a
|
||||
// `Work::GossipAttestationBatch` item.
|
||||
let mut packages = Vec::with_capacity(batch_size);
|
||||
for _ in 0..batch_size {
|
||||
if let Some(item) = attestation_queue.pop() {
|
||||
match item {
|
||||
Work::GossipAttestation {
|
||||
message_id,
|
||||
peer_id,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
} => {
|
||||
packages.push(GossipAttestationPackage::new(
|
||||
message_id,
|
||||
peer_id,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
));
|
||||
}
|
||||
_ => error!(
|
||||
self.log,
|
||||
"Invalid item in attestation queue"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process all attestations with a single worker.
|
||||
self.spawn_worker(
|
||||
Work::GossipAttestationBatch { packages },
|
||||
toolbox,
|
||||
)
|
||||
}
|
||||
// Check sync committee messages after attestations as their rewards are lesser
|
||||
// and they don't influence fork choice.
|
||||
} else if let Some(item) = sync_contribution_queue.pop() {
|
||||
@@ -1009,7 +1127,21 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
match work {
|
||||
_ if can_spawn => self.spawn_worker(work, toolbox),
|
||||
Work::GossipAttestation { .. } => attestation_queue.push(work),
|
||||
// Attestation batches are formed internally within the
|
||||
// `BeaconProcessor`, they are not sent from external services.
|
||||
Work::GossipAttestationBatch { .. } => crit!(
|
||||
self.log,
|
||||
"Unsupported inbound event";
|
||||
"type" => "GossipAttestationBatch"
|
||||
),
|
||||
Work::GossipAggregate { .. } => aggregate_queue.push(work),
|
||||
// Aggregate batches are formed internally within the `BeaconProcessor`,
|
||||
// they are not sent from external services.
|
||||
Work::GossipAggregateBatch { .. } => crit!(
|
||||
self.log,
|
||||
"Unsupported inbound event";
|
||||
"type" => "GossipAggregateBatch"
|
||||
),
|
||||
Work::GossipBlock { .. } => {
|
||||
gossip_block_queue.push(work, work_id, &self.log)
|
||||
}
|
||||
@@ -1180,7 +1312,7 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
|
||||
match work {
|
||||
/*
|
||||
* Unaggregated attestation verification.
|
||||
* Individual unaggregated attestation verification.
|
||||
*/
|
||||
Work::GossipAttestation {
|
||||
message_id,
|
||||
@@ -1192,14 +1324,19 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
} => worker.process_gossip_attestation(
|
||||
message_id,
|
||||
peer_id,
|
||||
*attestation,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
Some(work_reprocessing_tx),
|
||||
seen_timestamp,
|
||||
),
|
||||
/*
|
||||
* Aggregated attestation verification.
|
||||
* Batched unaggregated attestation verification.
|
||||
*/
|
||||
Work::GossipAttestationBatch { packages } => worker
|
||||
.process_gossip_attestation_batch(packages, Some(work_reprocessing_tx)),
|
||||
/*
|
||||
* Individual aggregated attestation verification.
|
||||
*/
|
||||
Work::GossipAggregate {
|
||||
message_id,
|
||||
@@ -1209,10 +1346,16 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
} => worker.process_gossip_aggregate(
|
||||
message_id,
|
||||
peer_id,
|
||||
*aggregate,
|
||||
aggregate,
|
||||
Some(work_reprocessing_tx),
|
||||
seen_timestamp,
|
||||
),
|
||||
/*
|
||||
* Batched aggregated attestation verification.
|
||||
*/
|
||||
Work::GossipAggregateBatch { packages } => {
|
||||
worker.process_gossip_aggregate_batch(packages, Some(work_reprocessing_tx))
|
||||
}
|
||||
/*
|
||||
* Verification for beacon blocks received on gossip.
|
||||
*/
|
||||
@@ -1345,7 +1488,7 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
} => worker.process_gossip_attestation(
|
||||
message_id,
|
||||
peer_id,
|
||||
*attestation,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
None, // Do not allow this attestation to be re-processed beyond this point.
|
||||
@@ -1359,7 +1502,7 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
|
||||
} => worker.process_gossip_aggregate(
|
||||
message_id,
|
||||
peer_id,
|
||||
*aggregate,
|
||||
aggregate,
|
||||
None,
|
||||
seen_timestamp,
|
||||
),
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
use crate::{metrics, service::NetworkMessage, sync::SyncMessage};
|
||||
|
||||
use beacon_chain::{
|
||||
attestation_verification::{Error as AttnError, SignatureVerifiedAttestation},
|
||||
attestation_verification::{Error as AttnError, VerifiedAttestation},
|
||||
observed_operations::ObservationOutcome,
|
||||
sync_committee_verification::Error as SyncCommitteeError,
|
||||
validator_monitor::get_block_delay_ms,
|
||||
BeaconChainError, BeaconChainTypes, BlockError, ForkChoiceError, GossipVerifiedBlock,
|
||||
};
|
||||
use eth2_libp2p::{MessageAcceptance, MessageId, PeerAction, PeerId, ReportSource};
|
||||
use slog::{debug, error, info, trace, warn};
|
||||
use slog::{crit, debug, error, info, trace, warn};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::Encode;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tokio::sync::mpsc;
|
||||
use types::{
|
||||
Attestation, AttesterSlashing, EthSpec, Hash256, ProposerSlashing, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, SubnetId,
|
||||
SyncCommitteeMessage, SyncSubnetId,
|
||||
Attestation, AttesterSlashing, EthSpec, Hash256, IndexedAttestation, ProposerSlashing,
|
||||
SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit,
|
||||
SubnetId, SyncCommitteeMessage, SyncSubnetId,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -26,6 +26,60 @@ use super::{
|
||||
Worker,
|
||||
};
|
||||
|
||||
/// An attestation that has been validated by the `BeaconChain`.
|
||||
///
|
||||
/// Since this struct implements `beacon_chain::VerifiedAttestation`, it would be a logic error to
|
||||
/// construct this from components which have not passed `BeaconChain` validation.
|
||||
struct VerifiedUnaggregate<T: BeaconChainTypes> {
|
||||
attestation: Box<Attestation<T::EthSpec>>,
|
||||
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
||||
}
|
||||
|
||||
/// This implementation allows `Self` to be imported to fork choice and other functions on the
|
||||
/// `BeaconChain`.
|
||||
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedUnaggregate<T> {
|
||||
fn attestation(&self) -> &Attestation<T::EthSpec> {
|
||||
&self.attestation
|
||||
}
|
||||
|
||||
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec> {
|
||||
&self.indexed_attestation
|
||||
}
|
||||
}
|
||||
|
||||
/// An attestation that failed validation by the `BeaconChain`.
|
||||
struct RejectedUnaggregate<T: EthSpec> {
|
||||
attestation: Box<Attestation<T>>,
|
||||
error: AttnError,
|
||||
}
|
||||
|
||||
/// An aggregate that has been validated by the `BeaconChain`.
|
||||
///
|
||||
/// Since this struct implements `beacon_chain::VerifiedAttestation`, it would be a logic error to
|
||||
/// construct this from components which have not passed `BeaconChain` validation.
|
||||
struct VerifiedAggregate<T: BeaconChainTypes> {
|
||||
signed_aggregate: Box<SignedAggregateAndProof<T::EthSpec>>,
|
||||
indexed_attestation: IndexedAttestation<T::EthSpec>,
|
||||
}
|
||||
|
||||
/// This implementation allows `Self` to be imported to fork choice and other functions on the
|
||||
/// `BeaconChain`.
|
||||
impl<'a, T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedAggregate<T> {
|
||||
fn attestation(&self) -> &Attestation<T::EthSpec> {
|
||||
&self.signed_aggregate.message.aggregate
|
||||
}
|
||||
|
||||
fn indexed_attestation(&self) -> &IndexedAttestation<T::EthSpec> {
|
||||
&self.indexed_attestation
|
||||
}
|
||||
}
|
||||
|
||||
/// An attestation that failed validation by the `BeaconChain`.
|
||||
struct RejectedAggregate<T: EthSpec> {
|
||||
signed_aggregate: Box<SignedAggregateAndProof<T>>,
|
||||
error: AttnError,
|
||||
}
|
||||
|
||||
/// Data for an aggregated or unaggregated attestation that failed verification.
|
||||
enum FailedAtt<T: EthSpec> {
|
||||
Unaggregate {
|
||||
@@ -41,7 +95,7 @@ enum FailedAtt<T: EthSpec> {
|
||||
}
|
||||
|
||||
impl<T: EthSpec> FailedAtt<T> {
|
||||
pub fn root(&self) -> &Hash256 {
|
||||
pub fn beacon_block_root(&self) -> &Hash256 {
|
||||
match self {
|
||||
FailedAtt::Unaggregate { attestation, .. } => &attestation.data.beacon_block_root,
|
||||
FailedAtt::Aggregate { attestation, .. } => {
|
||||
@@ -58,6 +112,66 @@ impl<T: EthSpec> FailedAtt<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Items required to verify a batch of unaggregated gossip attestations.
|
||||
#[derive(Debug)]
|
||||
pub struct GossipAttestationPackage<E: EthSpec> {
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
attestation: Box<Attestation<E>>,
|
||||
subnet_id: SubnetId,
|
||||
beacon_block_root: Hash256,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> GossipAttestationPackage<E> {
|
||||
pub fn new(
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
attestation: Box<Attestation<E>>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
message_id,
|
||||
peer_id,
|
||||
beacon_block_root: attestation.data.beacon_block_root,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Items required to verify a batch of aggregated gossip attestations.
|
||||
#[derive(Debug)]
|
||||
pub struct GossipAggregatePackage<E: EthSpec> {
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
aggregate: Box<SignedAggregateAndProof<E>>,
|
||||
beacon_block_root: Hash256,
|
||||
seen_timestamp: Duration,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> GossipAggregatePackage<E> {
|
||||
pub fn new(
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
aggregate: Box<SignedAggregateAndProof<E>>,
|
||||
seen_timestamp: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
message_id,
|
||||
peer_id,
|
||||
beacon_block_root: aggregate.message.aggregate.data.beacon_block_root,
|
||||
aggregate,
|
||||
seen_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> Worker<T> {
|
||||
/* Auxiliary functions */
|
||||
|
||||
@@ -103,88 +217,200 @@ impl<T: BeaconChainTypes> Worker<T> {
|
||||
self,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
attestation: Attestation<T::EthSpec>,
|
||||
attestation: Box<Attestation<T::EthSpec>>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
seen_timestamp: Duration,
|
||||
) {
|
||||
let beacon_block_root = attestation.data.beacon_block_root;
|
||||
|
||||
let attestation = match self
|
||||
let result = match self
|
||||
.chain
|
||||
.verify_unaggregated_attestation_for_gossip(attestation, Some(subnet_id))
|
||||
.verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id))
|
||||
{
|
||||
Ok(attestation) => attestation,
|
||||
Err((e, attestation)) => {
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::Unaggregate {
|
||||
attestation: Box::new(attestation),
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
},
|
||||
reprocess_tx,
|
||||
e,
|
||||
Ok(verified_attestation) => Ok(VerifiedUnaggregate {
|
||||
indexed_attestation: verified_attestation.into_indexed_attestation(),
|
||||
attestation,
|
||||
}),
|
||||
Err(error) => Err(RejectedUnaggregate { attestation, error }),
|
||||
};
|
||||
|
||||
self.process_gossip_attestation_result(
|
||||
result,
|
||||
message_id,
|
||||
peer_id,
|
||||
subnet_id,
|
||||
reprocess_tx,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn process_gossip_attestation_batch(
|
||||
self,
|
||||
packages: Vec<GossipAttestationPackage<T::EthSpec>>,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
) {
|
||||
let attestations_and_subnets = packages
|
||||
.iter()
|
||||
.map(|package| (package.attestation.as_ref(), Some(package.subnet_id)));
|
||||
|
||||
let results = match self
|
||||
.chain
|
||||
.batch_verify_unaggregated_attestations_for_gossip(attestations_and_subnets)
|
||||
{
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
error!(
|
||||
self.log,
|
||||
"Batch unagg. attn verification failed";
|
||||
"error" => ?e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Register the attestation with any monitored validators.
|
||||
self.chain
|
||||
.validator_monitor
|
||||
.read()
|
||||
.register_gossip_unaggregated_attestation(
|
||||
seen_timestamp,
|
||||
attestation.indexed_attestation(),
|
||||
&self.chain.slot_clock,
|
||||
);
|
||||
|
||||
// Indicate to the `Network` service that this message is valid and can be
|
||||
// propagated on the gossip network.
|
||||
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept);
|
||||
|
||||
if !should_import {
|
||||
return;
|
||||
// Sanity check.
|
||||
if results.len() != packages.len() {
|
||||
// The log is `crit` since in this scenario we might be penalizing/rewarding the wrong
|
||||
// peer.
|
||||
crit!(
|
||||
self.log,
|
||||
"Batch attestation result mismatch";
|
||||
"results" => results.len(),
|
||||
"packages" => packages.len(),
|
||||
)
|
||||
}
|
||||
|
||||
metrics::inc_counter(&metrics::BEACON_PROCESSOR_UNAGGREGATED_ATTESTATION_VERIFIED_TOTAL);
|
||||
// Map the results into a new `Vec` so that `results` no longer holds a reference to
|
||||
// `packages`.
|
||||
#[allow(clippy::needless_collect)] // The clippy suggestion fails the borrow checker.
|
||||
let results = results
|
||||
.into_iter()
|
||||
.map(|result| result.map(|verified| verified.into_indexed_attestation()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Err(e) = self.chain.apply_attestation_to_fork_choice(&attestation) {
|
||||
match e {
|
||||
BeaconChainError::ForkChoiceError(ForkChoiceError::InvalidAttestation(e)) => {
|
||||
for (result, package) in results.into_iter().zip(packages.into_iter()) {
|
||||
let result = match result {
|
||||
Ok(indexed_attestation) => Ok(VerifiedUnaggregate {
|
||||
indexed_attestation,
|
||||
attestation: package.attestation,
|
||||
}),
|
||||
Err(error) => Err(RejectedUnaggregate {
|
||||
attestation: package.attestation,
|
||||
error,
|
||||
}),
|
||||
};
|
||||
|
||||
self.process_gossip_attestation_result(
|
||||
result,
|
||||
package.message_id,
|
||||
package.peer_id,
|
||||
package.subnet_id,
|
||||
reprocess_tx.clone(),
|
||||
package.should_import,
|
||||
package.seen_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Clippy warning is is ignored since the arguments are all of a different type (i.e., they
|
||||
// cant' be mixed-up) and creating a struct would result in more complexity.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_gossip_attestation_result(
|
||||
&self,
|
||||
result: Result<VerifiedUnaggregate<T>, RejectedUnaggregate<T::EthSpec>>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
subnet_id: SubnetId,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
) {
|
||||
match result {
|
||||
Ok(verified_attestation) => {
|
||||
let indexed_attestation = &verified_attestation.indexed_attestation;
|
||||
let beacon_block_root = indexed_attestation.data.beacon_block_root;
|
||||
|
||||
// Register the attestation with any monitored validators.
|
||||
self.chain
|
||||
.validator_monitor
|
||||
.read()
|
||||
.register_gossip_unaggregated_attestation(
|
||||
seen_timestamp,
|
||||
indexed_attestation,
|
||||
&self.chain.slot_clock,
|
||||
);
|
||||
|
||||
// Indicate to the `Network` service that this message is valid and can be
|
||||
// propagated on the gossip network.
|
||||
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept);
|
||||
|
||||
if !should_import {
|
||||
return;
|
||||
}
|
||||
|
||||
metrics::inc_counter(
|
||||
&metrics::BEACON_PROCESSOR_UNAGGREGATED_ATTESTATION_VERIFIED_TOTAL,
|
||||
);
|
||||
|
||||
if let Err(e) = self
|
||||
.chain
|
||||
.apply_attestation_to_fork_choice(&verified_attestation)
|
||||
{
|
||||
match e {
|
||||
BeaconChainError::ForkChoiceError(ForkChoiceError::InvalidAttestation(
|
||||
e,
|
||||
)) => {
|
||||
debug!(
|
||||
self.log,
|
||||
"Attestation invalid for fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
e => error!(
|
||||
self.log,
|
||||
"Error applying attestation to fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self
|
||||
.chain
|
||||
.add_to_naive_aggregation_pool(&verified_attestation)
|
||||
{
|
||||
debug!(
|
||||
self.log,
|
||||
"Attestation invalid for fork choice";
|
||||
"Attestation invalid for agg pool";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
e => error!(
|
||||
self.log,
|
||||
"Error applying attestation to fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
),
|
||||
|
||||
metrics::inc_counter(
|
||||
&metrics::BEACON_PROCESSOR_UNAGGREGATED_ATTESTATION_IMPORTED_TOTAL,
|
||||
);
|
||||
}
|
||||
Err(RejectedUnaggregate { attestation, error }) => {
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::Unaggregate {
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
},
|
||||
reprocess_tx,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.chain.add_to_naive_aggregation_pool(attestation) {
|
||||
debug!(
|
||||
self.log,
|
||||
"Attestation invalid for agg pool";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
|
||||
metrics::inc_counter(&metrics::BEACON_PROCESSOR_UNAGGREGATED_ATTESTATION_IMPORTED_TOTAL);
|
||||
}
|
||||
|
||||
/// Process the aggregated attestation received from the gossip network and:
|
||||
@@ -198,82 +424,191 @@ impl<T: BeaconChainTypes> Worker<T> {
|
||||
self,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
aggregate: SignedAggregateAndProof<T::EthSpec>,
|
||||
aggregate: Box<SignedAggregateAndProof<T::EthSpec>>,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
seen_timestamp: Duration,
|
||||
) {
|
||||
let beacon_block_root = aggregate.message.aggregate.data.beacon_block_root;
|
||||
|
||||
let aggregate = match self
|
||||
let result = match self
|
||||
.chain
|
||||
.verify_aggregated_attestation_for_gossip(aggregate)
|
||||
.verify_aggregated_attestation_for_gossip(&aggregate)
|
||||
{
|
||||
Ok(aggregate) => aggregate,
|
||||
Err((e, attestation)) => {
|
||||
// Report the failure to gossipsub
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::Aggregate {
|
||||
attestation: Box::new(attestation),
|
||||
seen_timestamp,
|
||||
},
|
||||
reprocess_tx,
|
||||
e,
|
||||
Ok(verified_aggregate) => Ok(VerifiedAggregate {
|
||||
indexed_attestation: verified_aggregate.into_indexed_attestation(),
|
||||
signed_aggregate: aggregate,
|
||||
}),
|
||||
Err(error) => Err(RejectedAggregate {
|
||||
signed_aggregate: aggregate,
|
||||
error,
|
||||
}),
|
||||
};
|
||||
|
||||
self.process_gossip_aggregate_result(
|
||||
result,
|
||||
beacon_block_root,
|
||||
message_id,
|
||||
peer_id,
|
||||
reprocess_tx,
|
||||
seen_timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn process_gossip_aggregate_batch(
|
||||
self,
|
||||
packages: Vec<GossipAggregatePackage<T::EthSpec>>,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
) {
|
||||
let aggregates = packages.iter().map(|package| package.aggregate.as_ref());
|
||||
|
||||
let results = match self
|
||||
.chain
|
||||
.batch_verify_aggregated_attestations_for_gossip(aggregates)
|
||||
{
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
error!(
|
||||
self.log,
|
||||
"Batch agg. attn verification failed";
|
||||
"error" => ?e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Indicate to the `Network` service that this message is valid and can be
|
||||
// propagated on the gossip network.
|
||||
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept);
|
||||
// Sanity check.
|
||||
if results.len() != packages.len() {
|
||||
// The log is `crit` since in this scenario we might be penalizing/rewarding the wrong
|
||||
// peer.
|
||||
crit!(
|
||||
self.log,
|
||||
"Batch agg. attestation result mismatch";
|
||||
"results" => results.len(),
|
||||
"packages" => packages.len(),
|
||||
)
|
||||
}
|
||||
|
||||
// Register the attestation with any monitored validators.
|
||||
self.chain
|
||||
.validator_monitor
|
||||
.read()
|
||||
.register_gossip_aggregated_attestation(
|
||||
seen_timestamp,
|
||||
aggregate.aggregate(),
|
||||
aggregate.indexed_attestation(),
|
||||
&self.chain.slot_clock,
|
||||
// Map the results into a new `Vec` so that `results` no longer holds a reference to
|
||||
// `packages`.
|
||||
#[allow(clippy::needless_collect)] // The clippy suggestion fails the borrow checker.
|
||||
let results = results
|
||||
.into_iter()
|
||||
.map(|result| result.map(|verified| verified.into_indexed_attestation()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (result, package) in results.into_iter().zip(packages.into_iter()) {
|
||||
let result = match result {
|
||||
Ok(indexed_attestation) => Ok(VerifiedAggregate {
|
||||
indexed_attestation,
|
||||
signed_aggregate: package.aggregate,
|
||||
}),
|
||||
Err(error) => Err(RejectedAggregate {
|
||||
signed_aggregate: package.aggregate,
|
||||
error,
|
||||
}),
|
||||
};
|
||||
|
||||
self.process_gossip_aggregate_result(
|
||||
result,
|
||||
package.beacon_block_root,
|
||||
package.message_id,
|
||||
package.peer_id,
|
||||
reprocess_tx.clone(),
|
||||
package.seen_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
metrics::inc_counter(&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_VERIFIED_TOTAL);
|
||||
fn process_gossip_aggregate_result(
|
||||
&self,
|
||||
result: Result<VerifiedAggregate<T>, RejectedAggregate<T::EthSpec>>,
|
||||
beacon_block_root: Hash256,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
seen_timestamp: Duration,
|
||||
) {
|
||||
match result {
|
||||
Ok(verified_aggregate) => {
|
||||
let aggregate = &verified_aggregate.signed_aggregate;
|
||||
let indexed_attestation = &verified_aggregate.indexed_attestation;
|
||||
|
||||
if let Err(e) = self.chain.apply_attestation_to_fork_choice(&aggregate) {
|
||||
match e {
|
||||
BeaconChainError::ForkChoiceError(ForkChoiceError::InvalidAttestation(e)) => {
|
||||
// Indicate to the `Network` service that this message is valid and can be
|
||||
// propagated on the gossip network.
|
||||
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept);
|
||||
|
||||
// Register the attestation with any monitored validators.
|
||||
self.chain
|
||||
.validator_monitor
|
||||
.read()
|
||||
.register_gossip_aggregated_attestation(
|
||||
seen_timestamp,
|
||||
aggregate,
|
||||
indexed_attestation,
|
||||
&self.chain.slot_clock,
|
||||
);
|
||||
|
||||
metrics::inc_counter(
|
||||
&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_VERIFIED_TOTAL,
|
||||
);
|
||||
|
||||
if let Err(e) = self
|
||||
.chain
|
||||
.apply_attestation_to_fork_choice(&verified_aggregate)
|
||||
{
|
||||
match e {
|
||||
BeaconChainError::ForkChoiceError(ForkChoiceError::InvalidAttestation(
|
||||
e,
|
||||
)) => {
|
||||
debug!(
|
||||
self.log,
|
||||
"Aggregate invalid for fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
e => error!(
|
||||
self.log,
|
||||
"Error applying aggregate to fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.chain.add_to_block_inclusion_pool(&verified_aggregate) {
|
||||
debug!(
|
||||
self.log,
|
||||
"Aggregate invalid for fork choice";
|
||||
"Attestation invalid for op pool";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
e => error!(
|
||||
self.log,
|
||||
"Error applying aggregate to fork choice";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
),
|
||||
|
||||
metrics::inc_counter(
|
||||
&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_IMPORTED_TOTAL,
|
||||
);
|
||||
}
|
||||
Err(RejectedAggregate {
|
||||
signed_aggregate,
|
||||
error,
|
||||
}) => {
|
||||
// Report the failure to gossipsub
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::Aggregate {
|
||||
attestation: signed_aggregate,
|
||||
seen_timestamp,
|
||||
},
|
||||
reprocess_tx,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.chain.add_to_block_inclusion_pool(aggregate) {
|
||||
debug!(
|
||||
self.log,
|
||||
"Attestation invalid for op pool";
|
||||
"reason" => ?e,
|
||||
"peer" => %peer_id,
|
||||
"beacon_block_root" => ?beacon_block_root
|
||||
)
|
||||
}
|
||||
|
||||
metrics::inc_counter(&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_IMPORTED_TOTAL);
|
||||
}
|
||||
|
||||
/// Process the beacon block received from the gossip network and:
|
||||
@@ -834,7 +1169,7 @@ impl<T: BeaconChainTypes> Worker<T> {
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage<T>>>,
|
||||
error: AttnError,
|
||||
) {
|
||||
let beacon_block_root = failed_att.root();
|
||||
let beacon_block_root = failed_att.beacon_block_root();
|
||||
let attestation_type = failed_att.kind();
|
||||
metrics::register_attestation_error(&error);
|
||||
match &error {
|
||||
|
||||
@@ -9,6 +9,7 @@ mod gossip_methods;
|
||||
mod rpc_methods;
|
||||
mod sync_methods;
|
||||
|
||||
pub use gossip_methods::{GossipAggregatePackage, GossipAttestationPackage};
|
||||
pub use sync_methods::ProcessId;
|
||||
|
||||
pub(crate) const FUTURE_SLOT_TOLERANCE: u64 = 1;
|
||||
|
||||
Reference in New Issue
Block a user