Altair networking (#2300)

## Issue Addressed

Resolves #2278 

## Proposed Changes

Implements the networking components for the Altair hard fork https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/p2p-interface.md

## Additional Info

This PR acts as the base branch for networking changes and tracks https://github.com/sigp/lighthouse/pull/2279 . Changes to gossip, rpc and discovery can be separate PRs to be merged here for ease of review.

Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Pawan Dhananjay
2021-08-04 01:44:57 +00:00
parent 6a620a31da
commit e8c0d1f19b
51 changed files with 4038 additions and 1354 deletions

View File

@@ -57,7 +57,8 @@ use task_executor::TaskExecutor;
use tokio::sync::{mpsc, oneshot};
use types::{
Attestation, AttesterSlashing, Hash256, ProposerSlashing, SignedAggregateAndProof,
SignedBeaconBlock, SignedVoluntaryExit, SubnetId,
SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, SubnetId,
SyncCommitteeMessage, SyncSubnetId,
};
use work_reprocessing_queue::{
spawn_reprocess_scheduler, QueuedAggregate, QueuedBlock, QueuedUnaggregate, ReadyWork,
@@ -121,6 +122,14 @@ const MAX_GOSSIP_PROPOSER_SLASHING_QUEUE_LEN: usize = 4_096;
/// before we start dropping them.
const MAX_GOSSIP_ATTESTER_SLASHING_QUEUE_LEN: usize = 4_096;
/// The maximum number of queued `SyncCommitteeMessage` objects that will be stored before we start dropping
/// them.
const MAX_SYNC_MESSAGE_QUEUE_LEN: usize = 2048;
/// The maximum number of queued `SignedContributionAndProof` objects that will be stored before we
/// start dropping them.
const MAX_SYNC_CONTRIBUTION_QUEUE_LEN: usize = 1024;
/// The maximum number of queued `SignedBeaconBlock` objects received from the network RPC that
/// will be stored before we start dropping them.
const MAX_RPC_BLOCK_QUEUE_LEN: usize = 1_024;
@@ -160,6 +169,8 @@ pub const DELAYED_IMPORT_BLOCK: &str = "delayed_import_block";
pub const GOSSIP_VOLUNTARY_EXIT: &str = "gossip_voluntary_exit";
pub const GOSSIP_PROPOSER_SLASHING: &str = "gossip_proposer_slashing";
pub const GOSSIP_ATTESTER_SLASHING: &str = "gossip_attester_slashing";
pub const GOSSIP_SYNC_SIGNATURE: &str = "gossip_sync_signature";
pub const GOSSIP_SYNC_CONTRIBUTION: &str = "gossip_sync_contribution";
pub const RPC_BLOCK: &str = "rpc_block";
pub const CHAIN_SEGMENT: &str = "chain_segment";
pub const STATUS_PROCESSING: &str = "status_processing";
@@ -327,6 +338,44 @@ impl<T: BeaconChainTypes> WorkEvent<T> {
}
}
/// Create a new `Work` event for some sync committee signature.
pub fn gossip_sync_signature(
message_id: MessageId,
peer_id: PeerId,
sync_signature: SyncCommitteeMessage,
subnet_id: SyncSubnetId,
seen_timestamp: Duration,
) -> Self {
Self {
drop_during_sync: true,
work: Work::GossipSyncSignature {
message_id,
peer_id,
sync_signature: Box::new(sync_signature),
subnet_id,
seen_timestamp,
},
}
}
/// Create a new `Work` event for some sync committee contribution.
pub fn gossip_sync_contribution(
message_id: MessageId,
peer_id: PeerId,
sync_contribution: SignedContributionAndProof<T::EthSpec>,
seen_timestamp: Duration,
) -> Self {
Self {
drop_during_sync: true,
work: Work::GossipSyncContribution {
message_id,
peer_id,
sync_contribution: Box::new(sync_contribution),
seen_timestamp,
},
}
}
/// Create a new `Work` event for some exit.
pub fn gossip_voluntary_exit(
message_id: MessageId,
@@ -553,6 +602,19 @@ pub enum Work<T: BeaconChainTypes> {
peer_id: PeerId,
attester_slashing: Box<AttesterSlashing<T::EthSpec>>,
},
GossipSyncSignature {
message_id: MessageId,
peer_id: PeerId,
sync_signature: Box<SyncCommitteeMessage>,
subnet_id: SyncSubnetId,
seen_timestamp: Duration,
},
GossipSyncContribution {
message_id: MessageId,
peer_id: PeerId,
sync_contribution: Box<SignedContributionAndProof<T::EthSpec>>,
seen_timestamp: Duration,
},
RpcBlock {
block: Box<SignedBeaconBlock<T::EthSpec>>,
result_tx: BlockResultSender<T::EthSpec>,
@@ -588,6 +650,8 @@ impl<T: BeaconChainTypes> Work<T> {
Work::GossipVoluntaryExit { .. } => GOSSIP_VOLUNTARY_EXIT,
Work::GossipProposerSlashing { .. } => GOSSIP_PROPOSER_SLASHING,
Work::GossipAttesterSlashing { .. } => GOSSIP_ATTESTER_SLASHING,
Work::GossipSyncSignature { .. } => GOSSIP_SYNC_SIGNATURE,
Work::GossipSyncContribution { .. } => GOSSIP_SYNC_CONTRIBUTION,
Work::RpcBlock { .. } => RPC_BLOCK,
Work::ChainSegment { .. } => CHAIN_SEGMENT,
Work::Status { .. } => STATUS_PROCESSING,
@@ -730,6 +794,9 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
let mut unknown_block_attestation_queue =
LifoQueue::new(MAX_UNAGGREGATED_ATTESTATION_REPROCESS_QUEUE_LEN);
let mut sync_message_queue = LifoQueue::new(MAX_SYNC_MESSAGE_QUEUE_LEN);
let mut sync_contribution_queue = LifoQueue::new(MAX_SYNC_CONTRIBUTION_QUEUE_LEN);
// Using a FIFO queue for voluntary exits since it prevents exit censoring. I don't have
// a strong feeling about queue type for exits.
let mut gossip_voluntary_exit_queue = FifoQueue::new(MAX_GOSSIP_EXIT_QUEUE_LEN);
@@ -859,6 +926,12 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
self.spawn_worker(item, toolbox);
} else if let Some(item) = attestation_queue.pop() {
self.spawn_worker(item, 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() {
self.spawn_worker(item, toolbox);
} else if let Some(item) = sync_message_queue.pop() {
self.spawn_worker(item, toolbox);
// Aggregates and unaggregates queued for re-processing are older and we
// care about fresher ones, so check those first.
} else if let Some(item) = unknown_block_aggregate_queue.pop() {
@@ -952,6 +1025,10 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
Work::GossipAttesterSlashing { .. } => {
gossip_attester_slashing_queue.push(work, work_id, &self.log)
}
Work::GossipSyncSignature { .. } => sync_message_queue.push(work),
Work::GossipSyncContribution { .. } => {
sync_contribution_queue.push(work)
}
Work::RpcBlock { .. } => rpc_block_queue.push(work, work_id, &self.log),
Work::ChainSegment { .. } => {
chain_segment_queue.push(work, work_id, &self.log)
@@ -985,6 +1062,14 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_QUEUE_TOTAL,
aggregate_queue.len() as i64,
);
metrics::set_gauge(
&metrics::BEACON_PROCESSOR_SYNC_MESSAGE_QUEUE_TOTAL,
sync_message_queue.len() as i64,
);
metrics::set_gauge(
&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_QUEUE_TOTAL,
sync_contribution_queue.len() as i64,
);
metrics::set_gauge(
&metrics::BEACON_PROCESSOR_GOSSIP_BLOCK_QUEUE_TOTAL,
gossip_block_queue.len() as i64,
@@ -1188,6 +1273,36 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
peer_id,
*attester_slashing,
),
/*
* Sync committee message verification.
*/
Work::GossipSyncSignature {
message_id,
peer_id,
sync_signature,
subnet_id,
seen_timestamp,
} => worker.process_gossip_sync_committee_signature(
message_id,
peer_id,
*sync_signature,
subnet_id,
seen_timestamp,
),
/*
* Syn contribution verification.
*/
Work::GossipSyncContribution {
message_id,
peer_id,
sync_contribution,
seen_timestamp,
} => worker.process_sync_committee_contribution(
message_id,
peer_id,
*sync_contribution,
seen_timestamp,
),
/*
* Verification for beacon blocks received during syncing via RPC.
*/

View File

@@ -9,8 +9,12 @@ use beacon_chain::test_utils::{
};
use beacon_chain::{BeaconChain, MAXIMUM_GOSSIP_CLOCK_DISPARITY};
use environment::{null_logger, Environment, EnvironmentBuilder};
use eth2_libp2p::discv5::enr::{CombinedKey, EnrBuilder};
use eth2_libp2p::{rpc::methods::MetaData, types::EnrBitfield, MessageId, NetworkGlobals, PeerId};
use eth2_libp2p::{
discv5::enr::{CombinedKey, EnrBuilder},
rpc::methods::{MetaData, MetaDataV2},
types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield},
MessageId, NetworkGlobals, PeerId,
};
use slot_clock::SlotClock;
use std::cmp;
use std::iter::Iterator;
@@ -163,10 +167,11 @@ impl TestRig {
let (sync_tx, _sync_rx) = mpsc::unbounded_channel();
// Default metadata
let meta_data = MetaData {
let meta_data = MetaData::V2(MetaDataV2 {
seq_number: SEQ_NUMBER,
attnets: EnrBitfield::<MainnetEthSpec>::default(),
};
attnets: EnrAttestationBitfield::<MainnetEthSpec>::default(),
syncnets: EnrSyncCommitteeBitfield::<MainnetEthSpec>::default(),
});
let enr_key = CombinedKey::generate_secp256k1();
let enr = EnrBuilder::new("v4").build(&enr_key).unwrap();
let network_globals = Arc::new(NetworkGlobals::new(

View File

@@ -3,6 +3,7 @@ use crate::{metrics, service::NetworkMessage, sync::SyncMessage};
use beacon_chain::{
attestation_verification::{Error as AttnError, SignatureVerifiedAttestation},
observed_operations::ObservationOutcome,
sync_committee_verification::Error as SyncCommitteeError,
validator_monitor::get_block_delay_ms,
BeaconChainError, BeaconChainTypes, BlockError, ForkChoiceError, GossipVerifiedBlock,
};
@@ -14,7 +15,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::mpsc;
use types::{
Attestation, AttesterSlashing, EthSpec, Hash256, ProposerSlashing, SignedAggregateAndProof,
SignedBeaconBlock, SignedVoluntaryExit, SubnetId,
SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, SubnetId,
SyncCommitteeMessage, SyncSubnetId,
};
use super::{
@@ -688,6 +690,131 @@ impl<T: BeaconChainTypes> Worker<T> {
}
}
/// Process the sync committee signature received from the gossip network and:
///
/// - If it passes gossip propagation criteria, tell the network thread to forward it.
/// - Attempt to add it to the naive aggregation pool.
///
/// Raises a log if there are errors.
pub fn process_gossip_sync_committee_signature(
self,
message_id: MessageId,
peer_id: PeerId,
sync_signature: SyncCommitteeMessage,
subnet_id: SyncSubnetId,
_seen_timestamp: Duration,
) {
let sync_signature = match self
.chain
.verify_sync_committee_message_for_gossip(sync_signature, subnet_id)
{
Ok(sync_signature) => sync_signature,
Err(e) => {
self.handle_sync_committee_message_failure(
peer_id,
message_id,
"sync_signature",
e,
);
return;
}
};
/*TODO:
// Register the sync signature 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);
metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_MESSAGE_VERIFIED_TOTAL);
if let Err(e) = self
.chain
.add_to_naive_sync_aggregation_pool(sync_signature)
{
debug!(
self.log,
"Sync committee signature invalid for agg pool";
"reason" => ?e,
"peer" => %peer_id,
)
}
metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_MESSAGE_IMPORTED_TOTAL);
}
/// Process the sync committee contribution received from the gossip network and:
///
/// - If it passes gossip propagation criteria, tell the network thread to forward it.
/// - Attempt to add it to the block inclusion pool.
///
/// Raises a log if there are errors.
pub fn process_sync_committee_contribution(
self,
message_id: MessageId,
peer_id: PeerId,
sync_contribution: SignedContributionAndProof<T::EthSpec>,
_seen_timestamp: Duration,
) {
let sync_contribution = match self
.chain
.verify_sync_contribution_for_gossip(sync_contribution)
{
Ok(sync_contribution) => sync_contribution,
Err(e) => {
// Report the failure to gossipsub
self.handle_sync_committee_message_failure(
peer_id,
message_id,
"sync_contribution",
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);
/* TODO
// 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,
);
metrics::inc_counter(&metrics::BEACON_PROCESSOR_AGGREGATED_ATTESTATION_VERIFIED_TOTAL);
*/
if let Err(e) = self
.chain
.add_contribution_to_block_inclusion_pool(sync_contribution)
{
debug!(
self.log,
"Sync contribution invalid for op pool";
"reason" => ?e,
"peer" => %peer_id,
)
}
metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_IMPORTED_TOTAL);
}
/// Handle an error whilst verifying an `Attestation` or `SignedAggregateAndProof` from the
/// network.
fn handle_attestation_verification_failure(
@@ -740,8 +867,7 @@ impl<T: BeaconChainTypes> Worker<T> {
/*
* The aggregate had no signatures and is therefore worthless.
*
* Whilst we don't gossip this attestation, this act is **not** a clear
* violation of the spec nor indication of fault.
* This is forbidden by the p2p spec. Reject the message.
*
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
@@ -1079,4 +1205,242 @@ impl<T: BeaconChainTypes> Worker<T> {
"type" => ?attestation_type,
);
}
/// Handle an error whilst verifying a `SyncCommitteeMessage` or `SignedContributionAndProof` from the
/// network.
pub fn handle_sync_committee_message_failure(
&self,
peer_id: PeerId,
message_id: MessageId,
message_type: &str,
error: SyncCommitteeError,
) {
metrics::register_sync_committee_error(&error);
match &error {
SyncCommitteeError::FutureSlot { .. } | SyncCommitteeError::PastSlot { .. } => {
/*
* These errors can be triggered by a mismatch between our slot and the peer.
*
*
* The peer has published an invalid consensus message, _only_ if we trust our own clock.
*/
trace!(
self.log,
"Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots";
"peer_id" => %peer_id,
"type" => ?message_type,
);
// Peers that are slow or not to spec can spam us with these messages draining our
// bandwidth. We therefore penalize these peers when they do this.
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
// Do not propagate these messages.
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
}
SyncCommitteeError::EmptyAggregationBitfield => {
/*
* The aggregate had no signatures and is therefore worthless.
*
* This is forbidden by the p2p spec. Reject the message.
*
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::InvalidSelectionProof { .. }
| SyncCommitteeError::InvalidSignature => {
/*
* These errors are caused by invalid signatures.
*
* The peer has published an invalid consensus message.
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::AggregatorNotInCommittee { .. }
| SyncCommitteeError::AggregatorPubkeyUnknown(_) => {
/*
* The aggregator is not in the committee for the given `ContributionAndSync` OR
The aggregator index was higher than any known validator index
*
* The peer has published an invalid consensus message.
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::SyncContributionAlreadyKnown(_)
| SyncCommitteeError::AggregatorAlreadyKnown(_) => {
/*
* The sync committee message already been observed on the network or in
* a block.
*
* The peer is not necessarily faulty.
*/
trace!(
self.log,
"Sync committee message is already known";
"peer_id" => %peer_id,
"type" => ?message_type,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
return;
}
SyncCommitteeError::UnknownValidatorIndex(_) => {
/*
* The aggregator index (or similar field) was higher than the maximum
* possible number of validators.
*
* The peer has published an invalid consensus message.
*/
debug!(
self.log,
"Validation Index too high";
"peer_id" => %peer_id,
"type" => ?message_type,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::UnknownValidatorPubkey(_) => {
debug!(
self.log,
"Validator pubkey is unknown";
"peer_id" => %peer_id,
"type" => ?message_type,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::InvalidSubnetId { received, expected } => {
/*
* The sync committee message was received on an incorrect subnet id.
*/
debug!(
self.log,
"Received sync committee message on incorrect subnet";
"expected" => ?expected,
"received" => ?received,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::Invalid(_) => {
/*
* The sync committee message failed the state_processing verification.
*
* The peer has published an invalid consensus message.
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::PriorSyncCommitteeMessageKnown { .. } => {
/*
* We have already seen a sync committee message from this validator for this epoch.
*
* The peer is not necessarily faulty.
*/
debug!(
self.log,
"Prior sync committee message known";
"peer_id" => %peer_id,
"type" => ?message_type,
);
// We still penalize the peer slightly. We don't want this to be a recurring
// behaviour.
self.gossip_penalize_peer(peer_id, PeerAction::HighToleranceError);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
return;
}
SyncCommitteeError::BeaconChainError(e) => {
/*
* Lighthouse hit an unexpected error whilst processing the sync committee message. It
* should be impossible to trigger a `BeaconChainError` from the network,
* so we have a bug.
*
* It's not clear if the message is invalid/malicious.
*/
error!(
self.log,
"Unable to validate sync committee message";
"peer_id" => %peer_id,
"error" => ?e,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
// Penalize the peer slightly
self.gossip_penalize_peer(peer_id, PeerAction::HighToleranceError);
}
SyncCommitteeError::BeaconStateError(e) => {
/*
* Lighthouse hit an unexpected error whilst processing the sync committee message. It
* should be impossible to trigger a `BeaconStateError` from the network,
* so we have a bug.
*
* It's not clear if the message is invalid/malicious.
*/
error!(
self.log,
"Unable to validate sync committee message";
"peer_id" => %peer_id,
"error" => ?e,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
// Penalize the peer slightly
self.gossip_penalize_peer(peer_id, PeerAction::HighToleranceError);
}
SyncCommitteeError::ContributionError(e) => {
error!(
self.log,
"Error while processing sync contribution";
"peer_id" => %peer_id,
"error" => ?e,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
// Penalize the peer slightly
self.gossip_penalize_peer(peer_id, PeerAction::HighToleranceError);
}
SyncCommitteeError::SyncCommitteeError(e) => {
error!(
self.log,
"Error while processing sync committee message";
"peer_id" => %peer_id,
"error" => ?e,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
// Penalize the peer slightly
self.gossip_penalize_peer(peer_id, PeerAction::HighToleranceError);
}
SyncCommitteeError::ArithError(e) => {
/*
This would most likely imply incompatible configs or an invalid message.
*/
error!(
self.log,
"Arithematic error while processing sync committee message";
"peer_id" => %peer_id,
"error" => ?e,
);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
SyncCommitteeError::InvalidSubcommittee { .. } => {
/*
The subcommittee index is higher than `SYNC_COMMITTEE_SUBNET_COUNT`. This would imply
an invalid message.
*/
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(peer_id, PeerAction::LowToleranceError);
}
}
debug!(
self.log,
"Invalid sync committee message from network";
"reason" => ?error,
"peer_id" => %peer_id,
"type" => ?message_type,
);
}
}