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:
Michael Sproul
2021-08-06 00:47:31 +00:00
parent 350b6f19de
commit 17a2c778e3
44 changed files with 3144 additions and 705 deletions

View File

@@ -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"]

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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;

View File

@@ -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.

View File

@@ -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 {

View 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,
)
}
}