mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 05:14:35 +00:00
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:
@@ -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)]
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
|
||||
91
consensus/types/src/fork_context.rs
Normal file
91
consensus/types/src/fork_context.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
15
consensus/types/src/sync_committee_subscription.rs
Normal file
15
consensus/types/src/sync_committee_subscription.rs
Normal 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,
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user