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

@@ -65,101 +65,6 @@ impl<T: EthSpec> BeaconBlock<T> {
}
}
/// Return a block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> BeaconBlock<T> {
let header = BeaconBlockHeader {
slot: Slot::new(1),
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body_root: Hash256::zero(),
};
let signed_header = SignedBeaconBlockHeader {
message: header,
signature: Signature::empty(),
};
let indexed_attestation: IndexedAttestation<T> = IndexedAttestation {
attesting_indices: VariableList::new(vec![
0_u64;
T::MaxValidatorsPerCommittee::to_usize()
])
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let deposit_data = DepositData {
pubkey: PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::zero(),
amount: 0,
signature: SignatureBytes::empty(),
};
let proposer_slashing = ProposerSlashing {
signed_header_1: signed_header.clone(),
signed_header_2: signed_header,
};
let attester_slashing = AttesterSlashing {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
let attestation: Attestation<T> = Attestation {
aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize())
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let deposit = Deposit {
proof: FixedVector::from_elem(Hash256::zero()),
data: deposit_data,
};
let voluntary_exit = VoluntaryExit {
epoch: Epoch::new(1),
validator_index: 1,
};
let signed_voluntary_exit = SignedVoluntaryExit {
message: voluntary_exit,
signature: Signature::empty(),
};
// FIXME(altair): use an Altair block (they're bigger)
let mut block = BeaconBlockBase::<T>::empty(spec);
for _ in 0..T::MaxProposerSlashings::to_usize() {
block
.body
.proposer_slashings
.push(proposer_slashing.clone())
.unwrap();
}
for _ in 0..T::MaxDeposits::to_usize() {
block.body.deposits.push(deposit.clone()).unwrap();
}
for _ in 0..T::MaxVoluntaryExits::to_usize() {
block
.body
.voluntary_exits
.push(signed_voluntary_exit.clone())
.unwrap();
}
for _ in 0..T::MaxAttesterSlashings::to_usize() {
block
.body
.attester_slashings
.push(attester_slashing.clone())
.unwrap();
}
for _ in 0..T::MaxAttestations::to_usize() {
block.body.attestations.push(attestation.clone()).unwrap();
}
BeaconBlock::Base(block)
}
/// Custom SSZ decoder that takes a `ChainSpec` as context.
pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
let slot_len = <Slot as Decode>::ssz_fixed_len();
@@ -314,10 +219,104 @@ impl<T: EthSpec> BeaconBlockBase<T> {
},
}
}
/// Return a block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let header = BeaconBlockHeader {
slot: Slot::new(1),
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body_root: Hash256::zero(),
};
let signed_header = SignedBeaconBlockHeader {
message: header,
signature: Signature::empty(),
};
let indexed_attestation: IndexedAttestation<T> = IndexedAttestation {
attesting_indices: VariableList::new(vec![
0_u64;
T::MaxValidatorsPerCommittee::to_usize()
])
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let deposit_data = DepositData {
pubkey: PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::zero(),
amount: 0,
signature: SignatureBytes::empty(),
};
let proposer_slashing = ProposerSlashing {
signed_header_1: signed_header.clone(),
signed_header_2: signed_header,
};
let attester_slashing = AttesterSlashing {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
let attestation: Attestation<T> = Attestation {
aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize())
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let deposit = Deposit {
proof: FixedVector::from_elem(Hash256::zero()),
data: deposit_data,
};
let voluntary_exit = VoluntaryExit {
epoch: Epoch::new(1),
validator_index: 1,
};
let signed_voluntary_exit = SignedVoluntaryExit {
message: voluntary_exit,
signature: Signature::empty(),
};
let mut block = BeaconBlockBase::<T>::empty(spec);
for _ in 0..T::MaxProposerSlashings::to_usize() {
block
.body
.proposer_slashings
.push(proposer_slashing.clone())
.unwrap();
}
for _ in 0..T::MaxDeposits::to_usize() {
block.body.deposits.push(deposit.clone()).unwrap();
}
for _ in 0..T::MaxVoluntaryExits::to_usize() {
block
.body
.voluntary_exits
.push(signed_voluntary_exit.clone())
.unwrap();
}
for _ in 0..T::MaxAttesterSlashings::to_usize() {
block
.body
.attester_slashings
.push(attester_slashing.clone())
.unwrap();
}
for _ in 0..T::MaxAttestations::to_usize() {
block.body.attestations.push(attestation.clone()).unwrap();
}
block
}
}
impl<T: EthSpec> BeaconBlockAltair<T> {
/// Returns an empty block to be used during genesis.
/// Returns an empty Altair block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
BeaconBlockAltair {
slot: spec.genesis_slot,
@@ -341,6 +340,36 @@ impl<T: EthSpec> BeaconBlockAltair<T> {
},
}
}
/// Return an Altair block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let base_block = BeaconBlockBase::full(spec);
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
};
BeaconBlockAltair {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyAltair {
proposer_slashings: base_block.body.proposer_slashings,
attester_slashings: base_block.body.attester_slashings,
attestations: base_block.body.attestations,
deposits: base_block.body.deposits,
voluntary_exits: base_block.body.voluntary_exits,
sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
},
}
}
}
#[cfg(test)]

View File

@@ -148,26 +148,49 @@ impl ChainSpec {
}
/// Returns an `EnrForkId` for the given `slot`.
///
/// Presently, we don't have any forks so we just ignore the slot. In the future this function
/// may return something different based upon the slot.
pub fn enr_fork_id(&self, _slot: Slot, genesis_validators_root: Hash256) -> EnrForkId {
pub fn enr_fork_id<T: EthSpec>(
&self,
slot: Slot,
genesis_validators_root: Hash256,
) -> EnrForkId {
EnrForkId {
fork_digest: Self::compute_fork_digest(
self.genesis_fork_version,
genesis_validators_root,
),
next_fork_version: self.genesis_fork_version,
next_fork_epoch: self.far_future_epoch,
fork_digest: self.fork_digest::<T>(slot, genesis_validators_root),
next_fork_version: self.next_fork_version(),
next_fork_epoch: self
.next_fork_epoch::<T>(slot)
.map(|(_, e)| e)
.unwrap_or(self.far_future_epoch),
}
}
/// Returns the epoch of the next scheduled change in the `fork.current_version`.
/// Returns the `ForkDigest` for the given slot.
///
/// There are no future forks scheduled so this function always returns `None`. This may not
/// always be the case in the future, though.
pub fn next_fork_epoch(&self) -> Option<Epoch> {
None
/// If `self.altair_fork_epoch == None`, then this function returns the genesis fork digest
/// otherwise, returns the fork digest based on the slot.
pub fn fork_digest<T: EthSpec>(&self, slot: Slot, genesis_validators_root: Hash256) -> [u8; 4] {
let fork_name = self.fork_name_at_slot::<T>(slot);
Self::compute_fork_digest(
self.fork_version_for_name(fork_name),
genesis_validators_root,
)
}
/// Returns the `next_fork_version`.
///
/// Since `next_fork_version = current_fork_version` if no future fork is planned,
/// this function returns `altair_fork_version` until the next fork is planned.
pub fn next_fork_version(&self) -> [u8; 4] {
self.altair_fork_version
}
/// Returns the epoch of the next scheduled fork along with its corresponding `ForkName`.
///
/// If no future forks are scheduled, this function returns `None`.
pub fn next_fork_epoch<T: EthSpec>(&self, slot: Slot) -> Option<(ForkName, Epoch)> {
let current_fork_name = self.fork_name_at_slot::<T>(slot);
let next_fork_name = current_fork_name.next_fork()?;
let fork_epoch = self.fork_epoch(next_fork_name)?;
Some((next_fork_name, fork_epoch))
}
/// Returns the name of the fork which is active at `slot`.

View File

@@ -78,6 +78,8 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
* New in Altair
*/
type SyncCommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/// The number of `sync_committee` subnets.
type SyncCommitteeSubnetCount: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* Derived values (set these CAREFULLY)
*/
@@ -218,6 +220,7 @@ impl EthSpec for MainnetEthSpec {
type MaxDeposits = U16;
type MaxVoluntaryExits = U16;
type SyncCommitteeSize = U512;
type SyncCommitteeSubnetCount = U4;
type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
@@ -250,6 +253,7 @@ impl EthSpec for MinimalEthSpec {
params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength,
SubnetBitfieldLength,
SyncCommitteeSubnetCount,
MaxValidatorsPerCommittee,
GenesisEpoch,
HistoricalRootsLimit,

View File

@@ -0,0 +1,91 @@
use parking_lot::RwLock;
use crate::{ChainSpec, EthSpec, ForkName, Hash256, Slot};
use std::collections::HashMap;
/// Provides fork specific info like the current fork name and the fork digests corresponding to every valid fork.
#[derive(Debug)]
pub struct ForkContext {
current_fork: RwLock<ForkName>,
fork_to_digest: HashMap<ForkName, [u8; 4]>,
digest_to_fork: HashMap<[u8; 4], ForkName>,
}
impl ForkContext {
/// Creates a new `ForkContext` object by enumerating all enabled forks and computing their
/// fork digest.
///
/// A fork is disabled in the `ChainSpec` if the activation slot corresponding to that fork is `None`.
pub fn new<T: EthSpec>(
current_slot: Slot,
genesis_validators_root: Hash256,
spec: &ChainSpec,
) -> Self {
let mut fork_to_digest = vec![(
ForkName::Base,
ChainSpec::compute_fork_digest(spec.genesis_fork_version, genesis_validators_root),
)];
// Only add Altair to list of forks if it's enabled (i.e. spec.altair_fork_epoch != None)
if spec.altair_fork_epoch.is_some() {
fork_to_digest.push((
ForkName::Altair,
ChainSpec::compute_fork_digest(spec.altair_fork_version, genesis_validators_root),
))
}
let fork_to_digest: HashMap<ForkName, [u8; 4]> = fork_to_digest.into_iter().collect();
let digest_to_fork = fork_to_digest
.clone()
.into_iter()
.map(|(k, v)| (v, k))
.collect();
Self {
current_fork: RwLock::new(spec.fork_name_at_slot::<T>(current_slot)),
fork_to_digest,
digest_to_fork,
}
}
/// Returns `true` if the provided `fork_name` exists in the `ForkContext` object.
pub fn fork_exists(&self, fork_name: ForkName) -> bool {
self.fork_to_digest.contains_key(&fork_name)
}
/// Returns the `current_fork`.
pub fn current_fork(&self) -> ForkName {
*self.current_fork.read()
}
/// Updates the `current_fork` field to a new fork.
pub fn update_current_fork(&self, new_fork: ForkName) {
*self.current_fork.write() = new_fork;
}
/// Returns the context bytes/fork_digest corresponding to the genesis fork version.
pub fn genesis_context_bytes(&self) -> [u8; 4] {
*self
.fork_to_digest
.get(&ForkName::Base)
.expect("ForkContext must contain genesis context bytes")
}
/// Returns the fork type given the context bytes/fork_digest.
/// Returns `None` if context bytes doesn't correspond to any valid `ForkName`.
pub fn from_context_bytes(&self, context: [u8; 4]) -> Option<&ForkName> {
self.digest_to_fork.get(&context)
}
/// Returns the context bytes/fork_digest corresponding to a fork name.
/// Returns `None` if the `ForkName` has not been initialized.
pub fn to_context_bytes(&self, fork_name: ForkName) -> Option<[u8; 4]> {
self.fork_to_digest.get(&fork_name).cloned()
}
/// Returns all `fork_digest`s that are currently in the `ForkContext` object.
pub fn all_fork_digests(&self) -> Vec<[u8; 4]> {
self.digest_to_fork.keys().cloned().collect()
}
}

View File

@@ -55,12 +55,14 @@ pub mod signed_beacon_block_header;
pub mod signed_contribution_and_proof;
pub mod signed_voluntary_exit;
pub mod signing_data;
pub mod sync_committee_subscription;
pub mod validator;
pub mod validator_subscription;
pub mod voluntary_exit;
#[macro_use]
pub mod slot_epoch_macros;
pub mod config_and_preset;
pub mod fork_context;
pub mod participation_flags;
pub mod participation_list;
pub mod preset;
@@ -107,6 +109,7 @@ pub use crate::enr_fork_id::EnrForkId;
pub use crate::eth1_data::Eth1Data;
pub use crate::eth_spec::EthSpecId;
pub use crate::fork::Fork;
pub use crate::fork_context::ForkContext;
pub use crate::fork_data::ForkData;
pub use crate::fork_name::{ForkName, InconsistentFork};
pub use crate::free_attestation::FreeAttestation;
@@ -136,6 +139,7 @@ pub use crate::sync_aggregator_selection_data::SyncAggregatorSelectionData;
pub use crate::sync_committee::SyncCommittee;
pub use crate::sync_committee_contribution::SyncCommitteeContribution;
pub use crate::sync_committee_message::SyncCommitteeMessage;
pub use crate::sync_committee_subscription::SyncCommitteeSubscription;
pub use crate::sync_selection_proof::SyncSelectionProof;
pub use crate::sync_subnet_id::SyncSubnetId;
pub use crate::validator::Validator;

View File

@@ -0,0 +1,15 @@
use crate::Epoch;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
/// A sync committee subscription created when a validator subscribes to sync committee subnets to perform
/// sync committee duties.
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct SyncCommitteeSubscription {
/// The validators index.
pub validator_index: u64,
/// The sync committee indices.
pub sync_committee_indices: Vec<u64>,
/// Epoch until which this subscription is required.
pub until_epoch: Epoch,
}

View File

@@ -1,6 +1,11 @@
//! Identifies each sync committee subnet by an integer identifier.
use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
use crate::EthSpec;
use safe_arith::{ArithError, SafeArith};
use serde_derive::{Deserialize, Serialize};
use ssz_types::typenum::Unsigned;
use std::collections::HashSet;
use std::fmt::{self, Display};
use std::ops::{Deref, DerefMut};
lazy_static! {
@@ -33,6 +38,24 @@ impl SyncSubnetId {
pub fn new(id: u64) -> Self {
id.into()
}
/// Compute required subnets to subscribe to given the sync committee indices.
pub fn compute_subnets_for_sync_committee<T: EthSpec>(
sync_committee_indices: &[u64],
) -> Result<HashSet<Self>, ArithError> {
let subcommittee_size = T::SyncSubcommitteeSize::to_u64();
sync_committee_indices
.iter()
.map(|index| index.safe_div(subcommittee_size).map(Self::new))
.collect()
}
}
impl Display for SyncSubnetId {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self.0)
}
}
impl Deref for SyncSubnetId {