mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
Altair validator client and HTTP API (#2404)
## Proposed Changes * Implement the validator client and HTTP API changes necessary to support Altair Co-authored-by: realbigsean <seananderson33@gmail.com> Co-authored-by: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
@@ -50,6 +50,7 @@ superstruct = "0.2.0"
|
||||
serde_json = "1.0.58"
|
||||
criterion = "0.3.3"
|
||||
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||
eth2_interop_keypairs = { path = "../../common/eth2_interop_keypairs" }
|
||||
|
||||
[features]
|
||||
default = ["sqlite", "legacy-arith"]
|
||||
|
||||
@@ -149,6 +149,27 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> BeaconBlockRef<'a, T> {
|
||||
/// Returns the name of the fork pertaining to `self`.
|
||||
///
|
||||
/// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
|
||||
/// dictated by `self.slot()`.
|
||||
pub fn fork_name(&self, spec: &ChainSpec) -> Result<ForkName, InconsistentFork> {
|
||||
let fork_at_slot = spec.fork_name_at_slot::<T>(self.slot());
|
||||
let object_fork = match self {
|
||||
BeaconBlockRef::Base { .. } => ForkName::Base,
|
||||
BeaconBlockRef::Altair { .. } => ForkName::Altair,
|
||||
};
|
||||
|
||||
if fork_at_slot == object_fork {
|
||||
Ok(object_fork)
|
||||
} else {
|
||||
Err(InconsistentFork {
|
||||
fork_at_slot,
|
||||
object_fork,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
|
||||
pub fn body(&self) -> BeaconBlockBodyRef<'a, T> {
|
||||
match self {
|
||||
|
||||
@@ -837,6 +837,32 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the sync committee duties for a list of validator indices.
|
||||
///
|
||||
/// Will return a `SyncCommitteeNotKnown` error if the `epoch` is out of bounds with respect
|
||||
/// to the current or next sync committee periods.
|
||||
pub fn get_sync_committee_duties(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
validator_indices: &[u64],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<Option<SyncDuty>>, Error> {
|
||||
let sync_committee = self.get_built_sync_committee(epoch, spec)?;
|
||||
|
||||
validator_indices
|
||||
.iter()
|
||||
.map(|&validator_index| {
|
||||
let pubkey = self.get_validator(validator_index as usize)?.pubkey;
|
||||
|
||||
Ok(SyncDuty::from_sync_committee(
|
||||
validator_index,
|
||||
pubkey,
|
||||
sync_committee,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the canonical root of the `latest_block_header`, filling in its state root if necessary.
|
||||
///
|
||||
/// It needs filling in on all slots where there isn't a skip.
|
||||
|
||||
@@ -685,6 +685,8 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use safe_arith::SafeArith;
|
||||
|
||||
#[test]
|
||||
fn test_mainnet_spec_can_be_constructed() {
|
||||
@@ -745,6 +747,33 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that `next_fork_epoch` is consistent with the other functions.
|
||||
#[test]
|
||||
fn next_fork_epoch_consistency() {
|
||||
type E = MainnetEthSpec;
|
||||
let spec = ChainSpec::mainnet();
|
||||
|
||||
let mut last_fork_slot = Slot::new(0);
|
||||
|
||||
for (_, fork) in ForkName::list_all().into_iter().tuple_windows() {
|
||||
if let Some(fork_epoch) = spec.fork_epoch(fork) {
|
||||
last_fork_slot = fork_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
// Fork is activated at non-zero epoch: check that `next_fork_epoch` returns
|
||||
// the correct result.
|
||||
if let Ok(prior_slot) = last_fork_slot.safe_sub(1) {
|
||||
let (next_fork, next_fork_epoch) =
|
||||
spec.next_fork_epoch::<E>(prior_slot).unwrap();
|
||||
assert_eq!(fork, next_fork);
|
||||
assert_eq!(spec.fork_epoch(fork).unwrap(), next_fork_epoch);
|
||||
}
|
||||
} else {
|
||||
// Fork is not activated, check that `next_fork_epoch` returns `None`.
|
||||
assert_eq!(spec.next_fork_epoch::<E>(last_fork_slot), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use crate::{ChainSpec, Epoch};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
#[serde(into = "String")]
|
||||
pub enum ForkName {
|
||||
Base,
|
||||
Altair,
|
||||
@@ -48,7 +54,7 @@ impl ForkName {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ForkName {
|
||||
impl FromStr for ForkName {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(fork_name: &str) -> Result<Self, ()> {
|
||||
@@ -60,6 +66,29 @@ impl std::str::FromStr for ForkName {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ForkName {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
ForkName::Base => "phase0".fmt(f),
|
||||
ForkName::Altair => "altair".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ForkName> for String {
|
||||
fn from(fork: ForkName) -> String {
|
||||
fork.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ForkName {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Self::from_str(&s).map_err(|()| format!("Invalid fork name: {}", s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InconsistentFork {
|
||||
pub fork_at_slot: ForkName,
|
||||
|
||||
@@ -56,6 +56,7 @@ pub mod signed_contribution_and_proof;
|
||||
pub mod signed_voluntary_exit;
|
||||
pub mod signing_data;
|
||||
pub mod sync_committee_subscription;
|
||||
pub mod sync_duty;
|
||||
pub mod validator;
|
||||
pub mod validator_subscription;
|
||||
pub mod voluntary_exit;
|
||||
@@ -137,9 +138,10 @@ pub use crate::subnet_id::SubnetId;
|
||||
pub use crate::sync_aggregate::SyncAggregate;
|
||||
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_contribution::{SyncCommitteeContribution, SyncContributionData};
|
||||
pub use crate::sync_committee_message::SyncCommitteeMessage;
|
||||
pub use crate::sync_committee_subscription::SyncCommitteeSubscription;
|
||||
pub use crate::sync_duty::SyncDuty;
|
||||
pub use crate::sync_selection_proof::SyncSelectionProof;
|
||||
pub use crate::sync_subnet_id::SyncSubnetId;
|
||||
pub use crate::validator::Validator;
|
||||
|
||||
@@ -71,20 +71,7 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
/// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
|
||||
/// dictated by `self.slot()`.
|
||||
pub fn fork_name(&self, spec: &ChainSpec) -> Result<ForkName, InconsistentFork> {
|
||||
let fork_at_slot = spec.fork_name_at_slot::<E>(self.slot());
|
||||
let object_fork = match self {
|
||||
SignedBeaconBlock::Base { .. } => ForkName::Base,
|
||||
SignedBeaconBlock::Altair { .. } => ForkName::Altair,
|
||||
};
|
||||
|
||||
if fork_at_slot == object_fork {
|
||||
Ok(object_fork)
|
||||
} else {
|
||||
Err(InconsistentFork {
|
||||
fork_at_slot,
|
||||
object_fork,
|
||||
})
|
||||
}
|
||||
self.message().fork_name(spec)
|
||||
}
|
||||
|
||||
/// SSZ decode.
|
||||
|
||||
@@ -77,9 +77,9 @@ impl SignedRoot for Hash256 {}
|
||||
/// This is not in the spec, but useful for determining uniqueness of sync committee contributions
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SyncContributionData {
|
||||
slot: Slot,
|
||||
beacon_block_root: Hash256,
|
||||
subcommittee_index: u64,
|
||||
pub slot: Slot,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub subcommittee_index: u64,
|
||||
}
|
||||
|
||||
impl SyncContributionData {
|
||||
|
||||
83
consensus/types/src/sync_duty.rs
Normal file
83
consensus/types/src/sync_duty.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::{EthSpec, SyncCommittee, SyncSubnetId};
|
||||
use bls::PublicKeyBytes;
|
||||
use safe_arith::ArithError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SyncDuty {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub validator_index: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64_vec")]
|
||||
pub validator_sync_committee_indices: Vec<u64>,
|
||||
}
|
||||
|
||||
impl SyncDuty {
|
||||
/// Create a new `SyncDuty` from the list of validator indices in a sync committee.
|
||||
pub fn from_sync_committee_indices(
|
||||
validator_index: u64,
|
||||
pubkey: PublicKeyBytes,
|
||||
sync_committee_indices: &[usize],
|
||||
) -> Option<Self> {
|
||||
// Positions of the `validator_index` within the committee.
|
||||
let validator_sync_committee_indices = sync_committee_indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, &v)| {
|
||||
if validator_index == v as u64 {
|
||||
Some(i as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Self::new(validator_index, pubkey, validator_sync_committee_indices)
|
||||
}
|
||||
|
||||
/// Create a new `SyncDuty` from a `SyncCommittee`, which contains the pubkeys but not the
|
||||
/// indices.
|
||||
pub fn from_sync_committee<T: EthSpec>(
|
||||
validator_index: u64,
|
||||
pubkey: PublicKeyBytes,
|
||||
sync_committee: &SyncCommittee<T>,
|
||||
) -> Option<Self> {
|
||||
let validator_sync_committee_indices = sync_committee
|
||||
.pubkeys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, committee_pubkey)| {
|
||||
if &pubkey == committee_pubkey {
|
||||
Some(i as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Self::new(validator_index, pubkey, validator_sync_committee_indices)
|
||||
}
|
||||
|
||||
/// Create a duty if the `validator_sync_committee_indices` is non-empty.
|
||||
fn new(
|
||||
validator_index: u64,
|
||||
pubkey: PublicKeyBytes,
|
||||
validator_sync_committee_indices: Vec<u64>,
|
||||
) -> Option<Self> {
|
||||
if !validator_sync_committee_indices.is_empty() {
|
||||
Some(SyncDuty {
|
||||
pubkey,
|
||||
validator_index,
|
||||
validator_sync_committee_indices,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the set of subnet IDs for this duty.
|
||||
pub fn subnet_ids<E: EthSpec>(&self) -> Result<HashSet<SyncSubnetId>, ArithError> {
|
||||
SyncSubnetId::compute_subnets_for_sync_committee::<E>(
|
||||
&self.validator_sync_committee_indices,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user