Implement standard eth2.0 API (#1569)

- Resolves #1550
- Resolves #824
- Resolves #825
- Resolves #1131
- Resolves #1411
- Resolves #1256
- Resolve #1177

- Includes the `ShufflingId` struct initially defined in #1492. That PR is now closed and the changes are included here, with significant bug fixes.
- Implement the https://github.com/ethereum/eth2.0-APIs in a new `http_api` crate using `warp`. This replaces the `rest_api` crate.
- Add a new `common/eth2` crate which provides a wrapper around `reqwest`, providing the HTTP client that is used by the validator client and for testing. This replaces the `common/remote_beacon_node` crate.
- Create a `http_metrics` crate which is a dedicated server for Prometheus metrics (they are no longer served on the same port as the REST API). We now have flags for `--metrics`, `--metrics-address`, etc.
- Allow the `subnet_id` to be an optional parameter for `VerifiedUnaggregatedAttestation::verify`. This means it does not need to be provided unnecessarily by the validator client.
- Move `fn map_attestation_committee` in `mod beacon_chain::attestation_verification` to a new `fn with_committee_cache` on the `BeaconChain` so the same cache can be used for obtaining validator duties.
- Add some other helpers to `BeaconChain` to assist with common API duties (e.g., `block_root_at_slot`, `head_beacon_block_root`).
- Change the `NaiveAggregationPool` so it can index attestations by `hash_tree_root(attestation.data)`. This is a requirement of the API.
- Add functions to `BeaconChainHarness` to allow it to create slashings and exits.
- Allow for `eth1::Eth1NetworkId` to go to/from a `String`.
- Add functions to the `OperationPool` to allow getting all objects in the pool.
- Add function to `BeaconState` to check if a committee cache is initialized.
- Fix bug where `seconds_per_eth1_block` was not transferring over from `YamlConfig` to `ChainSpec`.
- Add the `deposit_contract_address` to `YamlConfig` and `ChainSpec`. We needed to be able to return it in an API response.
- Change some uses of serde `serialize_with` and `deserialize_with` to a single use of `with` (code quality).
- Impl `Display` and `FromStr` for several BLS fields.
- Check for clock discrepancy when VC polls BN for sync state (with +/- 1 slot tolerance). This is not intended to be comprehensive, it was just easy to do.

- See #1434 for a per-endpoint overview.
- Seeking clarity here: https://github.com/ethereum/eth2.0-APIs/issues/75

- [x] Add docs for prom port to close #1256
- [x] Follow up on this #1177
- [x] ~~Follow up with #1424~~ Will fix in future PR.
- [x] Follow up with #1411
- [x] ~~Follow up with  #1260~~ Will fix in future PR.
- [x] Add quotes to all integers.
- [x] Remove `rest_types`
- [x] Address missing beacon block error. (#1629)
- [x] ~~Add tests for lighthouse/peers endpoints~~ Wontfix
- [x] ~~Follow up with validator status proposal~~ Tracked in #1434
- [x] Unify graffiti structs
- [x] ~~Start server when waiting for genesis?~~ Will fix in future PR.
- [x] TODO in http_api tests
- [x] Move lighthouse endpoints off /eth/v1
- [x] Update docs to link to standard

- ~~Blocked on #1586~~

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Paul Hauner
2020-09-29 03:46:54 +00:00
parent 8e20176337
commit cdec3cec18
156 changed files with 8862 additions and 8916 deletions

View File

@@ -58,4 +58,3 @@ environment = { path = "../../lighthouse/environment" }
bus = "2.2.3"
derivative = "2.1.1"
itertools = "0.9.0"
regex = "1.3.9"

View File

@@ -28,8 +28,7 @@
use crate::{
beacon_chain::{
ATTESTATION_CACHE_LOCK_TIMEOUT, HEAD_LOCK_TIMEOUT, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
HEAD_LOCK_TIMEOUT, MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
},
metrics,
observed_attestations::ObserveOutcome,
@@ -38,12 +37,10 @@ use crate::{
};
use bls::verify_signature_sets;
use proto_array::Block as ProtoBlock;
use slog::debug;
use slot_clock::SlotClock;
use state_processing::{
common::get_indexed_attestation,
per_block_processing::errors::AttestationValidationError,
per_slot_processing,
signature_sets::{
indexed_attestation_signature_set_from_pubkeys,
signed_aggregate_selection_proof_signature_set, signed_aggregate_signature_set,
@@ -53,7 +50,7 @@ use std::borrow::Cow;
use tree_hash::TreeHash;
use types::{
Attestation, BeaconCommittee, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation,
RelativeEpoch, SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
};
/// Returned when an attestation was not successfully verified. It might not have been verified for
@@ -267,6 +264,7 @@ pub struct VerifiedAggregatedAttestation<T: BeaconChainTypes> {
pub struct VerifiedUnaggregatedAttestation<T: BeaconChainTypes> {
attestation: Attestation<T::EthSpec>,
indexed_attestation: IndexedAttestation<T::EthSpec>,
subnet_id: SubnetId,
}
/// Custom `Clone` implementation is to avoid the restrictive trait bounds applied by the usual derive
@@ -276,6 +274,7 @@ impl<T: BeaconChainTypes> Clone for VerifiedUnaggregatedAttestation<T> {
Self {
attestation: self.attestation.clone(),
indexed_attestation: self.indexed_attestation.clone(),
subnet_id: self.subnet_id,
}
}
}
@@ -428,6 +427,11 @@ impl<T: BeaconChainTypes> VerifiedAggregatedAttestation<T> {
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
&self.signed_aggregate.message.aggregate
}
/// Returns the underlying `signed_aggregate`.
pub fn aggregate(&self) -> &SignedAggregateAndProof<T::EthSpec> {
&self.signed_aggregate
}
}
impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
@@ -438,7 +442,7 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
/// verify that it was received on the correct subnet.
pub fn verify(
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
subnet_id: Option<SubnetId>,
chain: &BeaconChain<T>,
) -> Result<Self, Error> {
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
@@ -513,13 +517,15 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
)
.map_err(BeaconChainError::from)?;
// Ensure the attestation is from the correct subnet.
if subnet_id != expected_subnet_id {
return Err(Error::InvalidSubnetId {
received: subnet_id,
expected: expected_subnet_id,
});
}
// If a subnet was specified, ensure that subnet is correct.
if let Some(subnet_id) = subnet_id {
if subnet_id != expected_subnet_id {
return Err(Error::InvalidSubnetId {
received: subnet_id,
expected: expected_subnet_id,
});
}
};
let validator_index = *indexed_attestation
.attesting_indices
@@ -564,6 +570,7 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
Ok(Self {
attestation,
indexed_attestation,
subnet_id: expected_subnet_id,
})
}
@@ -572,6 +579,11 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
chain.add_to_naive_aggregation_pool(self)
}
/// Returns the correct subnet for the attestation.
pub fn subnet_id(&self) -> SubnetId {
self.subnet_id
}
/// Returns the wrapped `attestation`.
pub fn attestation(&self) -> &Attestation<T::EthSpec> {
&self.attestation
@@ -587,6 +599,7 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
}
/// Returns `Ok(())` if the `attestation.data.beacon_block_root` is known to this chain.
/// You can use this `shuffling_id` to read from the shuffling cache.
///
/// The block root may not be known for two reasons:
///
@@ -615,6 +628,7 @@ fn verify_head_block_is_known<T: BeaconChainTypes>(
});
}
}
Ok(block)
} else {
Err(Error::UnknownHeadBlock {
@@ -770,7 +784,7 @@ type CommitteesPerSlot = u64;
/// Returns the `indexed_attestation` and committee count per slot for the `attestation` using the
/// public keys cached in the `chain`.
pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
) -> Result<(IndexedAttestation<T::EthSpec>, CommitteesPerSlot), Error> {
@@ -790,8 +804,8 @@ pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
///
/// If the committee for `attestation` isn't found in the `shuffling_cache`, we will read a state
/// from disk and then update the `shuffling_cache`.
pub fn map_attestation_committee<'a, T, F, R>(
chain: &'a BeaconChain<T>,
fn map_attestation_committee<T, F, R>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
map_fn: F,
) -> Result<R, Error>
@@ -809,104 +823,23 @@ where
// processing an attestation that does not include our latest finalized block in its chain.
//
// We do not delay consideration for later, we simply drop the attestation.
let target_block = chain
.fork_choice
.read()
.get_block(&target.root)
.ok_or_else(|| Error::UnknownTargetRoot(target.root))?;
// Obtain the shuffling cache, timing how long we wait.
let cache_wait_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_SHUFFLING_CACHE_WAIT_TIMES);
let mut shuffling_cache = chain
.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| BeaconChainError::AttestationCacheLockTimeout)?;
metrics::stop_timer(cache_wait_timer);
if let Some(committee_cache) = shuffling_cache.get(attestation_epoch, target.root) {
let committees_per_slot = committee_cache.committees_per_slot();
committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,
index: attestation.data.index,
})
})
} else {
// Drop the shuffling cache to avoid holding the lock for any longer than
// required.
drop(shuffling_cache);
debug!(
chain.log,
"Attestation processing cache miss";
"attn_epoch" => attestation_epoch.as_u64(),
"target_block_epoch" => target_block.slot.epoch(T::EthSpec::slots_per_epoch()).as_u64(),
);
let state_read_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_READ_TIMES);
let mut state = chain
.store
.get_inconsistent_state_for_attestation_verification_only(
&target_block.state_root,
Some(target_block.slot),
)
.map_err(BeaconChainError::from)?
.ok_or_else(|| BeaconChainError::MissingBeaconState(target_block.state_root))?;
metrics::stop_timer(state_read_timer);
let state_skip_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_SKIP_TIMES);
while state.current_epoch() + 1 < attestation_epoch {
// Here we tell `per_slot_processing` to skip hashing the state and just
// use the zero hash instead.
//
// The state roots are not useful for the shuffling, so there's no need to
// compute them.
per_slot_processing(&mut state, Some(Hash256::zero()), &chain.spec)
.map_err(BeaconChainError::from)?;
}
metrics::stop_timer(state_skip_timer);
let committee_building_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_COMMITTEE_BUILDING_TIMES);
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), attestation_epoch)
.map_err(BeaconChainError::IncorrectStateForAttestation)?;
state
.build_committee_cache(relative_epoch, &chain.spec)
.map_err(BeaconChainError::from)?;
let committee_cache = state
.committee_cache(relative_epoch)
.map_err(BeaconChainError::from)?;
chain
.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| BeaconChainError::AttestationCacheLockTimeout)?
.insert(attestation_epoch, target.root, committee_cache);
metrics::stop_timer(committee_building_timer);
let committees_per_slot = committee_cache.committees_per_slot();
committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,
index: attestation.data.index,
})
})
if !chain.fork_choice.read().contains_block(&target.root) {
return Err(Error::UnknownTargetRoot(target.root));
}
chain
.with_committee_cache(target.root, attestation_epoch, |committee_cache| {
let committees_per_slot = committee_cache.committees_per_slot();
Ok(committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,
index: attestation.data.index,
})
}))
})
.map_err(BeaconChainError::from)?
}

View File

@@ -21,7 +21,7 @@ use crate::observed_block_producers::ObservedBlockProducers;
use crate::observed_operations::{ObservationOutcome, ObservedOperations};
use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::persisted_fork_choice::PersistedForkChoice;
use crate::shuffling_cache::ShufflingCache;
use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache};
use crate::snapshot_cache::SnapshotCache;
use crate::timeout_rw_lock::TimeoutRwLock;
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
@@ -31,7 +31,6 @@ use fork_choice::ForkChoice;
use itertools::process_results;
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::RwLock;
use regex::bytes::Regex;
use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock;
use state_processing::{
@@ -201,6 +200,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
pub(crate) canonical_head: TimeoutRwLock<BeaconSnapshot<T::EthSpec>>,
/// The root of the genesis block.
pub genesis_block_root: Hash256,
/// The root of the genesis state.
pub genesis_state_root: Hash256,
/// The root of the list of genesis validators, used during syncing.
pub genesis_validators_root: Hash256,
@@ -459,6 +460,30 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
}
/// Returns the block at the given slot, if any. Only returns blocks in the canonical chain.
///
/// ## Errors
///
/// May return a database error.
pub fn state_root_at_slot(&self, slot: Slot) -> Result<Option<Hash256>, Error> {
process_results(self.rev_iter_state_roots()?, |mut iter| {
iter.find(|(_, this_slot)| *this_slot == slot)
.map(|(root, _)| root)
})
}
/// Returns the block root at the given slot, if any. Only returns roots in the canonical chain.
///
/// ## Errors
///
/// May return a database error.
pub fn block_root_at_slot(&self, slot: Slot) -> Result<Option<Hash256>, Error> {
process_results(self.rev_iter_block_roots()?, |mut iter| {
iter.find(|(_, this_slot)| *this_slot == slot)
.map(|(root, _)| root)
})
}
/// Returns the block at the given root, if any.
///
/// ## Errors
@@ -506,6 +531,30 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
f(&head_lock)
}
/// Returns the beacon block root at the head of the canonical chain.
///
/// See `Self::head` for more information.
pub fn head_beacon_block_root(&self) -> Result<Hash256, Error> {
self.with_head(|s| Ok(s.beacon_block_root))
}
/// Returns the beacon block at the head of the canonical chain.
///
/// See `Self::head` for more information.
pub fn head_beacon_block(&self) -> Result<SignedBeaconBlock<T::EthSpec>, Error> {
self.with_head(|s| Ok(s.beacon_block.clone()))
}
/// Returns the beacon state at the head of the canonical chain.
///
/// See `Self::head` for more information.
pub fn head_beacon_state(&self) -> Result<BeaconState<T::EthSpec>, Error> {
self.with_head(|s| {
Ok(s.beacon_state
.clone_with(CloneConfig::committee_caches_only()))
})
}
/// Returns info representing the head block and state.
///
/// A summarized version of `Self::head` that involves less cloning.
@@ -719,46 +768,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map_err(Into::into)
}
/// Returns the attestation slot and committee index for a given validator index.
/// Returns the attestation duties for a given validator index.
///
/// Information is read from the current state, so only information from the present and prior
/// epoch is available.
pub fn validator_attestation_slot_and_index(
pub fn validator_attestation_duty(
&self,
validator_index: usize,
epoch: Epoch,
) -> Result<Option<(Slot, u64)>, Error> {
let as_epoch = |slot: Slot| slot.epoch(T::EthSpec::slots_per_epoch());
let head_state = &self.head()?.beacon_state;
) -> Result<Option<AttestationDuty>, Error> {
let head_block_root = self.head_beacon_block_root()?;
let mut state = if epoch == as_epoch(head_state.slot) {
self.head()?.beacon_state
} else {
// The block proposer shuffling is not affected by the state roots, so we don't need to
// calculate them.
self.state_at_slot(
epoch.start_slot(T::EthSpec::slots_per_epoch()),
StateSkipConfig::WithoutStateRoots,
)?
};
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
if as_epoch(state.slot) != epoch {
return Err(Error::InvariantViolated(format!(
"Epochs in consistent in attestation duties lookup: state: {}, requested: {}",
as_epoch(state.slot),
epoch
)));
}
if let Some(attestation_duty) =
state.get_attestation_duties(validator_index, RelativeEpoch::Current)?
{
Ok(Some((attestation_duty.slot, attestation_duty.index)))
} else {
Ok(None)
}
self.with_committee_cache(head_block_root, epoch, |committee_cache| {
Ok(committee_cache.get_attestation_duties(validator_index))
})
}
/// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`.
@@ -767,11 +790,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn get_aggregated_attestation(
&self,
data: &AttestationData,
) -> Result<Option<Attestation<T::EthSpec>>, Error> {
) -> Option<Attestation<T::EthSpec>> {
self.naive_aggregation_pool.read().get(data)
}
/// Returns an aggregated `Attestation`, if any, that has a matching
/// `attestation.data.tree_hash_root()`.
///
/// The attestation will be obtained from `self.naive_aggregation_pool`.
pub fn get_aggregated_attestation_by_slot_and_root(
&self,
slot: Slot,
attestation_data_root: &Hash256,
) -> Option<Attestation<T::EthSpec>> {
self.naive_aggregation_pool
.read()
.get(data)
.map_err(Into::into)
.get_by_slot_and_root(slot, attestation_data_root)
}
/// Produce an unaggregated `Attestation` that is valid for the given `slot` and `index`.
@@ -898,7 +932,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn verify_unaggregated_attestation_for_gossip(
&self,
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
subnet_id: Option<SubnetId>,
) -> Result<VerifiedUnaggregatedAttestation<T>, AttestationError> {
metrics::inc_counter(&metrics::UNAGGREGATED_ATTESTATION_PROCESSING_REQUESTS);
let _timer =
@@ -1320,11 +1354,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
block: SignedBeaconBlock<T::EthSpec>,
) -> Result<GossipVerifiedBlock<T>, BlockError<T::EthSpec>> {
let slot = block.message.slot;
#[allow(clippy::invalid_regex)]
let re = Regex::new("\\p{C}").expect("regex is valid");
let graffiti_string =
String::from_utf8_lossy(&re.replace_all(&block.message.body.graffiti[..], &b""[..]))
.to_string();
let graffiti_string = block.message.body.graffiti.as_utf8_lossy();
match GossipVerifiedBlock::new(block, self) {
Ok(verified) => {
@@ -1449,8 +1479,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
) -> Result<Hash256, BlockError<T::EthSpec>> {
let signed_block = fully_verified_block.block;
let block_root = fully_verified_block.block_root;
let state = fully_verified_block.state;
let parent_block = fully_verified_block.parent_block;
let mut state = fully_verified_block.state;
let current_slot = self.slot()?;
let mut ops = fully_verified_block.intermediate_states;
@@ -1482,29 +1511,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.ok_or_else(|| Error::ValidatorPubkeyCacheLockTimeout)?
.import_new_pubkeys(&state)?;
// If the imported block is in the previous or current epochs (according to the
// wall-clock), check to see if this is the first block of the epoch. If so, add the
// committee to the shuffling cache.
if state.current_epoch() + 1 >= self.epoch()?
&& parent_block.slot().epoch(T::EthSpec::slots_per_epoch()) != state.current_epoch()
{
let mut shuffling_cache = self
// For the current and next epoch of this state, ensure we have the shuffling from this
// block in our cache.
for relative_epoch in &[RelativeEpoch::Current, RelativeEpoch::Next] {
let shuffling_id = ShufflingId::new(block_root, &state, *relative_epoch)?;
let shuffling_is_cached = self
.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| Error::AttestationCacheLockTimeout)?;
.try_read_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| Error::AttestationCacheLockTimeout)?
.contains(&shuffling_id);
let committee_cache = state.committee_cache(RelativeEpoch::Current)?;
let epoch_start_slot = state
.current_epoch()
.start_slot(T::EthSpec::slots_per_epoch());
let target_root = if state.slot == epoch_start_slot {
block_root
} else {
*state.get_block_root(epoch_start_slot)?
};
shuffling_cache.insert(state.current_epoch(), target_root, committee_cache);
if !shuffling_is_cached {
state.build_committee_cache(*relative_epoch, &self.spec)?;
let committee_cache = state.committee_cache(*relative_epoch)?;
self.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| Error::AttestationCacheLockTimeout)?
.insert(shuffling_id, committee_cache);
}
}
let mut fork_choice = self.fork_choice.write();
@@ -1992,6 +2017,129 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(())
}
/// Runs the `map_fn` with the committee cache for `shuffling_epoch` from the chain with head
/// `head_block_root`.
///
/// It's not necessary that `head_block_root` matches our current view of the chain, it can be
/// any block that is:
///
/// - Known to us.
/// - The finalized block or a descendant of the finalized block.
///
/// It would be quite common for attestation verification operations to use a `head_block_root`
/// that differs from our view of the head.
///
/// ## Important
///
/// This function is **not** suitable for determining proposer duties.
///
/// ## Notes
///
/// This function exists in this odd "map" pattern because efficiently obtaining a committee
/// can be complex. It might involve reading straight from the `beacon_chain.shuffling_cache`
/// or it might involve reading it from a state from the DB. Due to the complexities of
/// `RwLock`s on the shuffling cache, a simple `Cow` isn't suitable here.
///
/// If the committee for `(head_block_root, shuffling_epoch)` isn't found in the
/// `shuffling_cache`, we will read a state from disk and then update the `shuffling_cache`.
pub(crate) fn with_committee_cache<F, R>(
&self,
head_block_root: Hash256,
shuffling_epoch: Epoch,
map_fn: F,
) -> Result<R, Error>
where
F: Fn(&CommitteeCache) -> Result<R, Error>,
{
let head_block = self
.fork_choice
.read()
.get_block(&head_block_root)
.ok_or_else(|| Error::MissingBeaconBlock(head_block_root))?;
let shuffling_id = BlockShufflingIds {
current: head_block.current_epoch_shuffling_id.clone(),
next: head_block.next_epoch_shuffling_id.clone(),
block_root: head_block.root,
}
.id_for_epoch(shuffling_epoch)
.ok_or_else(|| Error::InvalidShufflingId {
shuffling_epoch,
head_block_epoch: head_block.slot.epoch(T::EthSpec::slots_per_epoch()),
})?;
// Obtain the shuffling cache, timing how long we wait.
let cache_wait_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_SHUFFLING_CACHE_WAIT_TIMES);
let mut shuffling_cache = self
.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| Error::AttestationCacheLockTimeout)?;
metrics::stop_timer(cache_wait_timer);
if let Some(committee_cache) = shuffling_cache.get(&shuffling_id) {
map_fn(committee_cache)
} else {
// Drop the shuffling cache to avoid holding the lock for any longer than
// required.
drop(shuffling_cache);
debug!(
self.log,
"Committee cache miss";
"shuffling_epoch" => shuffling_epoch.as_u64(),
"head_block_root" => head_block_root.to_string(),
);
let state_read_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_READ_TIMES);
let mut state = self
.store
.get_inconsistent_state_for_attestation_verification_only(
&head_block.state_root,
Some(head_block.slot),
)?
.ok_or_else(|| Error::MissingBeaconState(head_block.state_root))?;
metrics::stop_timer(state_read_timer);
let state_skip_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_SKIP_TIMES);
while state.current_epoch() + 1 < shuffling_epoch {
// Here we tell `per_slot_processing` to skip hashing the state and just
// use the zero hash instead.
//
// The state roots are not useful for the shuffling, so there's no need to
// compute them.
per_slot_processing(&mut state, Some(Hash256::zero()), &self.spec)
.map_err(Error::from)?;
}
metrics::stop_timer(state_skip_timer);
let committee_building_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_COMMITTEE_BUILDING_TIMES);
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), shuffling_epoch)
.map_err(Error::IncorrectStateForAttestation)?;
state.build_committee_cache(relative_epoch, &self.spec)?;
let committee_cache = state.committee_cache(relative_epoch)?;
self.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or_else(|| Error::AttestationCacheLockTimeout)?
.insert(shuffling_id, committee_cache);
metrics::stop_timer(committee_building_timer);
map_fn(&committee_cache)
}
}
/// Returns `true` if the given block root has not been processed.
pub fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result<bool, Error> {
Ok(!self

View File

@@ -374,8 +374,13 @@ where
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis);
let fork_choice = ForkChoice::from_genesis(fc_store, &genesis.beacon_block.message)
.map_err(|e| format!("Unable to build initialize ForkChoice: {:?}", e))?;
let fork_choice = ForkChoice::from_genesis(
fc_store,
genesis.beacon_block_root,
&genesis.beacon_block.message,
&genesis.beacon_state,
)
.map_err(|e| format!("Unable to build initialize ForkChoice: {:?}", e))?;
self.fork_choice = Some(fork_choice);
self.genesis_time = Some(genesis.beacon_state.genesis_time);
@@ -561,6 +566,7 @@ where
observed_attester_slashings: <_>::default(),
eth1_chain: self.eth1_chain,
genesis_validators_root: canonical_head.beacon_state.genesis_validators_root,
genesis_state_root: canonical_head.beacon_state_root,
canonical_head: TimeoutRwLock::new(canonical_head.clone()),
genesis_block_root,
fork_choice: RwLock::new(fork_choice),

View File

@@ -83,6 +83,10 @@ pub enum BeaconChainError {
ObservedBlockProducersError(ObservedBlockProducersError),
PruningError(PruningError),
ArithError(ArithError),
InvalidShufflingId {
shuffling_epoch: Epoch,
head_block_epoch: Epoch,
},
}
easy_from_to!(SlotProcessingError, BeaconChainError);

View File

@@ -1,7 +1,9 @@
use crate::metrics;
use std::collections::HashMap;
use types::{Attestation, AttestationData, EthSpec, Slot};
use tree_hash::TreeHash;
use types::{Attestation, AttestationData, EthSpec, Hash256, Slot};
type AttestationDataRoot = Hash256;
/// The number of slots that will be stored in the pool.
///
/// For example, if `SLOTS_RETAINED == 3` and the pool is pruned at slot `6`, then all attestations
@@ -53,7 +55,7 @@ pub enum Error {
/// A collection of `Attestation` objects, keyed by their `attestation.data`. Enforces that all
/// `attestation` are from the same slot.
struct AggregatedAttestationMap<E: EthSpec> {
map: HashMap<AttestationData, Attestation<E>>,
map: HashMap<AttestationDataRoot, Attestation<E>>,
}
impl<E: EthSpec> AggregatedAttestationMap<E> {
@@ -87,7 +89,9 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
return Err(Error::MoreThanOneAggregationBitSet(set_bits.len()));
}
if let Some(existing_attestation) = self.map.get_mut(&a.data) {
let attestation_data_root = a.data.tree_hash_root();
if let Some(existing_attestation) = self.map.get_mut(&attestation_data_root) {
if existing_attestation
.aggregation_bits
.get(committee_index)
@@ -107,7 +111,7 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
));
}
self.map.insert(a.data.clone(), a.clone());
self.map.insert(attestation_data_root, a.clone());
Ok(InsertOutcome::NewAttestationData { committee_index })
}
}
@@ -115,8 +119,13 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
/// Returns an aggregated `Attestation` with the given `data`, if any.
///
/// The given `a.data.slot` must match the slot that `self` was initialized with.
pub fn get(&self, data: &AttestationData) -> Result<Option<Attestation<E>>, Error> {
Ok(self.map.get(data).cloned())
pub fn get(&self, data: &AttestationData) -> Option<Attestation<E>> {
self.map.get(&data.tree_hash_root()).cloned()
}
/// Returns an aggregated `Attestation` with the given `root`, if any.
pub fn get_by_root(&self, root: &AttestationDataRoot) -> Option<&Attestation<E>> {
self.map.get(root)
}
/// Iterate all attestations in `self`.
@@ -220,12 +229,19 @@ impl<E: EthSpec> NaiveAggregationPool<E> {
}
/// Returns an aggregated `Attestation` with the given `data`, if any.
pub fn get(&self, data: &AttestationData) -> Result<Option<Attestation<E>>, Error> {
pub fn get(&self, data: &AttestationData) -> Option<Attestation<E>> {
self.maps.get(&data.slot).and_then(|map| map.get(data))
}
/// Returns an aggregated `Attestation` with the given `data`, if any.
pub fn get_by_slot_and_root(
&self,
slot: Slot,
root: &AttestationDataRoot,
) -> Option<Attestation<E>> {
self.maps
.iter()
.find(|(slot, _map)| **slot == data.slot)
.map(|(_slot, map)| map.get(data))
.unwrap_or_else(|| Ok(None))
.get(&slot)
.and_then(|map| map.get_by_root(root).cloned())
}
/// Iterate all attestations in all slots of `self`.
@@ -338,8 +354,7 @@ mod tests {
let retrieved = pool
.get(&a.data)
.expect("should not error while getting attestation")
.expect("should get an attestation");
.expect("should not error while getting attestation");
assert_eq!(
retrieved, a,
"retrieved attestation should equal the one inserted"
@@ -378,8 +393,7 @@ mod tests {
let retrieved = pool
.get(&a_0.data)
.expect("should not error while getting attestation")
.expect("should get an attestation");
.expect("should not error while getting attestation");
let mut a_01 = a_0.clone();
a_01.aggregate(&a_1);
@@ -408,8 +422,7 @@ mod tests {
assert_eq!(
pool.get(&a_0.data)
.expect("should not error while getting attestation")
.expect("should get an attestation"),
.expect("should not error while getting attestation"),
retrieved,
"should not have aggregated different attestation data"
);

View File

@@ -1,6 +1,6 @@
use crate::metrics;
use lru::LruCache;
use types::{beacon_state::CommitteeCache, Epoch, Hash256};
use types::{beacon_state::CommitteeCache, Epoch, Hash256, ShufflingId};
/// The size of the LRU cache that stores committee caches for quicker verification.
///
@@ -14,7 +14,7 @@ const CACHE_SIZE: usize = 16;
/// It has been named `ShufflingCache` because `CommitteeCacheCache` is a bit weird and looks like
/// a find/replace error.
pub struct ShufflingCache {
cache: LruCache<(Epoch, Hash256), CommitteeCache>,
cache: LruCache<ShufflingId, CommitteeCache>,
}
impl ShufflingCache {
@@ -24,8 +24,8 @@ impl ShufflingCache {
}
}
pub fn get(&mut self, epoch: Epoch, root: Hash256) -> Option<&CommitteeCache> {
let opt = self.cache.get(&(epoch, root));
pub fn get(&mut self, key: &ShufflingId) -> Option<&CommitteeCache> {
let opt = self.cache.get(key);
if opt.is_some() {
metrics::inc_counter(&metrics::SHUFFLING_CACHE_HITS);
@@ -36,11 +36,37 @@ impl ShufflingCache {
opt
}
pub fn insert(&mut self, epoch: Epoch, root: Hash256, committee_cache: &CommitteeCache) {
let key = (epoch, root);
pub fn contains(&self, key: &ShufflingId) -> bool {
self.cache.contains(key)
}
pub fn insert(&mut self, key: ShufflingId, committee_cache: &CommitteeCache) {
if !self.cache.contains(&key) {
self.cache.put(key, committee_cache.clone());
}
}
}
/// Contains the shuffling IDs for a beacon block.
pub struct BlockShufflingIds {
pub current: ShufflingId,
pub next: ShufflingId,
pub block_root: Hash256,
}
impl BlockShufflingIds {
/// Returns the shuffling ID for the given epoch.
///
/// Returns `None` if `epoch` is prior to `self.current.shuffling_epoch`.
pub fn id_for_epoch(&self, epoch: Epoch) -> Option<ShufflingId> {
if epoch == self.current.shuffling_epoch {
Some(self.current.clone())
} else if epoch == self.next.shuffling_epoch {
Some(self.next.clone())
} else if epoch > self.next.shuffling_epoch {
Some(ShufflingId::from_components(epoch, self.block_root))
} else {
None
}
}
}

View File

@@ -26,9 +26,11 @@ use store::{config::StoreConfig, BlockReplay, HotColdDB, ItemStore, LevelDB, Mem
use tempfile::{tempdir, TempDir};
use tree_hash::TreeHash;
use types::{
AggregateSignature, Attestation, BeaconState, BeaconStateHash, ChainSpec, Domain, Epoch,
EthSpec, Hash256, Keypair, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock,
SignedBeaconBlockHash, SignedRoot, Slot, SubnetId,
AggregateSignature, Attestation, AttestationData, AttesterSlashing, BeaconState,
BeaconStateHash, ChainSpec, Checkpoint, Domain, Epoch, EthSpec, Hash256, IndexedAttestation,
Keypair, ProposerSlashing, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock,
SignedBeaconBlockHash, SignedRoot, SignedVoluntaryExit, Slot, SubnetId, VariableList,
VoluntaryExit,
};
pub use types::test_utils::generate_deterministic_keypairs;
@@ -129,7 +131,7 @@ impl<E: EthSpec> BeaconChainHarness<BlockingMigratorEphemeralHarnessType<E>> {
let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
let drain = slog_term::FullFormat::new(decorator).build();
let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug);
let debug_level = slog::LevelFilter::new(drain, slog::Level::Critical);
let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!());
let config = StoreConfig::default();
@@ -193,7 +195,7 @@ impl<E: EthSpec> BeaconChainHarness<NullMigratorEphemeralHarnessType<E>> {
let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
let drain = slog_term::FullFormat::new(decorator).build();
let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug);
let debug_level = slog::LevelFilter::new(drain, slog::Level::Critical);
let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!());
let store = HotColdDB::open_ephemeral(config, spec.clone(), log.clone()).unwrap();
@@ -238,7 +240,7 @@ impl<E: EthSpec> BeaconChainHarness<BlockingMigratorDiskHarnessType<E>> {
let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
let drain = slog_term::FullFormat::new(decorator).build();
let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug);
let debug_level = slog::LevelFilter::new(drain, slog::Level::Critical);
let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!());
let chain = BeaconChainBuilder::new(eth_spec_instance)
@@ -397,7 +399,7 @@ where
// If we produce two blocks for the same slot, they hash up to the same value and
// BeaconChain errors out with `BlockIsAlreadyKnown`. Vary the graffiti so that we produce
// different blocks each time.
self.chain.set_graffiti(self.rng.gen::<[u8; 32]>());
self.chain.set_graffiti(self.rng.gen::<[u8; 32]>().into());
let randao_reveal = {
let epoch = slot.epoch(E::slots_per_epoch());
@@ -442,8 +444,8 @@ where
let committee_count = state.get_committee_count_at_slot(state.slot).unwrap();
state
.get_beacon_committees_at_slot(state.slot)
.unwrap()
.get_beacon_committees_at_slot(attestation_slot)
.expect("should get committees")
.iter()
.map(|bc| {
bc.committee
@@ -570,7 +572,6 @@ where
let aggregate = self
.chain
.get_aggregated_attestation(&attestation.data)
.unwrap()
.unwrap_or_else(|| {
committee_attestations.iter().skip(1).fold(attestation.clone(), |mut agg, (att, _)| {
agg.aggregate(att);
@@ -601,6 +602,94 @@ where
.collect()
}
pub fn make_attester_slashing(&self, validator_indices: Vec<u64>) -> AttesterSlashing<E> {
let mut attestation_1 = IndexedAttestation {
attesting_indices: VariableList::new(validator_indices).unwrap(),
data: AttestationData {
slot: Slot::new(0),
index: 0,
beacon_block_root: Hash256::zero(),
target: Checkpoint {
root: Hash256::zero(),
epoch: Epoch::new(0),
},
source: Checkpoint {
root: Hash256::zero(),
epoch: Epoch::new(0),
},
},
signature: AggregateSignature::infinity(),
};
let mut attestation_2 = attestation_1.clone();
attestation_2.data.index += 1;
for attestation in &mut [&mut attestation_1, &mut attestation_2] {
for &i in &attestation.attesting_indices {
let sk = &self.validators_keypairs[i as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let genesis_validators_root = self.chain.genesis_validators_root;
let domain = self.chain.spec.get_domain(
attestation.data.target.epoch,
Domain::BeaconAttester,
&fork,
genesis_validators_root,
);
let message = attestation.data.signing_root(domain);
attestation.signature.add_assign(&sk.sign(message));
}
}
AttesterSlashing {
attestation_1,
attestation_2,
}
}
pub fn make_proposer_slashing(&self, validator_index: u64) -> ProposerSlashing {
let mut block_header_1 = self
.chain
.head_beacon_block()
.unwrap()
.message
.block_header();
block_header_1.proposer_index = validator_index;
let mut block_header_2 = block_header_1.clone();
block_header_2.state_root = Hash256::zero();
let sk = &self.validators_keypairs[validator_index as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let genesis_validators_root = self.chain.genesis_validators_root;
let mut signed_block_headers = vec![block_header_1, block_header_2]
.into_iter()
.map(|block_header| {
block_header.sign::<E>(&sk, &fork, genesis_validators_root, &self.chain.spec)
})
.collect::<Vec<_>>();
ProposerSlashing {
signed_header_2: signed_block_headers.remove(1),
signed_header_1: signed_block_headers.remove(0),
}
}
pub fn make_voluntary_exit(&self, validator_index: u64, epoch: Epoch) -> SignedVoluntaryExit {
let sk = &self.validators_keypairs[validator_index as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let genesis_validators_root = self.chain.genesis_validators_root;
VoluntaryExit {
epoch,
validator_index,
}
.sign(sk, &fork, genesis_validators_root, &self.chain.spec)
}
pub fn process_block(&self, slot: Slot, block: SignedBeaconBlock<E>) -> SignedBeaconBlockHash {
assert_eq!(self.chain.slot().unwrap(), slot);
let block_hash: SignedBeaconBlockHash = self.chain.process_block(block).unwrap().into();
@@ -612,7 +701,10 @@ where
for (unaggregated_attestations, maybe_signed_aggregate) in attestations.into_iter() {
for (attestation, subnet_id) in unaggregated_attestations {
self.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id)
.verify_unaggregated_attestation_for_gossip(
attestation.clone(),
Some(subnet_id),
)
.unwrap()
.add_to_pool(&self.chain)
.unwrap();

View File

@@ -570,7 +570,7 @@ fn unaggregated_gossip_verification() {
matches!(
harness
.chain
.verify_unaggregated_attestation_for_gossip($attn_getter, $subnet_getter)
.verify_unaggregated_attestation_for_gossip($attn_getter, Some($subnet_getter))
.err()
.expect(&format!(
"{} should error during verify_unaggregated_attestation_for_gossip",
@@ -837,7 +837,7 @@ fn unaggregated_gossip_verification() {
harness
.chain
.verify_unaggregated_attestation_for_gossip(valid_attestation.clone(), subnet_id)
.verify_unaggregated_attestation_for_gossip(valid_attestation.clone(), Some(subnet_id))
.expect("valid attestation should be verified");
/*
@@ -926,6 +926,6 @@ fn attestation_that_skips_epochs() {
harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation, subnet_id)
.verify_unaggregated_attestation_for_gossip(attestation, Some(subnet_id))
.expect("should gossip verify attestation that skips slots");
}

View File

@@ -326,7 +326,7 @@ fn epoch_boundary_state_attestation_processing() {
let res = harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id);
.verify_unaggregated_attestation_for_gossip(attestation.clone(), Some(subnet_id));
let current_slot = harness.chain.slot().expect("should get slot");
let expected_attestation_slot = attestation.data.slot;

View File

@@ -463,7 +463,7 @@ fn attestations_with_increasing_slots() {
for (attestation, subnet_id) in attestations.into_iter().flatten() {
let res = harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id);
.verify_unaggregated_attestation_for_gossip(attestation.clone(), Some(subnet_id));
let current_slot = harness.chain.slot().expect("should get slot");
let expected_attestation_slot = attestation.data.slot;

View File

@@ -14,7 +14,6 @@ store = { path = "../store" }
network = { path = "../network" }
timer = { path = "../timer" }
eth2_libp2p = { path = "../eth2_libp2p" }
rest_api = { path = "../rest_api" }
parking_lot = "0.11.0"
websocket_server = { path = "../websocket_server" }
prometheus = "0.9.0"
@@ -42,3 +41,5 @@ lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
time = "0.2.16"
bus = "2.2.3"
directory = {path = "../../common/directory"}
http_api = { path = "../http_api" }
http_metrics = { path = "../http_metrics" }

View File

@@ -13,15 +13,14 @@ use beacon_chain::{
use bus::Bus;
use environment::RuntimeContext;
use eth1::{Config as Eth1Config, Service as Eth1Service};
use eth2_config::Eth2Config;
use eth2_libp2p::NetworkGlobals;
use genesis::{interop_genesis_state, Eth1GenesisService};
use network::{NetworkConfig, NetworkMessage, NetworkService};
use parking_lot::Mutex;
use slog::info;
use slog::{debug, info};
use ssz::Decode;
use std::net::SocketAddr;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use timer::spawn_timer;
@@ -61,7 +60,10 @@ pub struct ClientBuilder<T: BeaconChainTypes> {
event_handler: Option<T::EventHandler>,
network_globals: Option<Arc<NetworkGlobals<T::EthSpec>>>,
network_send: Option<UnboundedSender<NetworkMessage<T::EthSpec>>>,
http_listen_addr: Option<SocketAddr>,
db_path: Option<PathBuf>,
freezer_db_path: Option<PathBuf>,
http_api_config: http_api::Config,
http_metrics_config: http_metrics::Config,
websocket_listen_addr: Option<SocketAddr>,
eth_spec_instance: T::EthSpec,
}
@@ -103,7 +105,10 @@ where
event_handler: None,
network_globals: None,
network_send: None,
http_listen_addr: None,
db_path: None,
freezer_db_path: None,
http_api_config: <_>::default(),
http_metrics_config: <_>::default(),
websocket_listen_addr: None,
eth_spec_instance,
}
@@ -280,55 +285,16 @@ where
Ok(self)
}
/// Immediately starts the beacon node REST API http server.
pub fn http_server(
mut self,
client_config: &ClientConfig,
eth2_config: &Eth2Config,
events: Arc<Mutex<Bus<SignedBeaconBlockHash>>>,
) -> Result<Self, String> {
let beacon_chain = self
.beacon_chain
.clone()
.ok_or_else(|| "http_server requires a beacon chain")?;
let context = self
.runtime_context
.as_ref()
.ok_or_else(|| "http_server requires a runtime_context")?
.service_context("http".into());
let network_globals = self
.network_globals
.clone()
.ok_or_else(|| "http_server requires a libp2p network")?;
let network_send = self
.network_send
.clone()
.ok_or_else(|| "http_server requires a libp2p network sender")?;
/// Provides configuration for the HTTP API.
pub fn http_api_config(mut self, config: http_api::Config) -> Self {
self.http_api_config = config;
self
}
let network_info = rest_api::NetworkInfo {
network_globals,
network_chan: network_send,
};
let listening_addr = rest_api::start_server(
context.executor,
&client_config.rest_api,
beacon_chain,
network_info,
client_config
.create_db_path()
.map_err(|_| "unable to read data dir")?,
client_config
.create_freezer_db_path()
.map_err(|_| "unable to read freezer DB dir")?,
eth2_config.clone(),
events,
)
.map_err(|e| format!("Failed to start HTTP API: {:?}", e))?;
self.http_listen_addr = Some(listening_addr);
Ok(self)
/// Provides configuration for the HTTP server that serves Prometheus metrics.
pub fn http_metrics_config(mut self, config: http_metrics::Config) -> Self {
self.http_metrics_config = config;
self
}
/// Immediately starts the service that periodically logs information each slot.
@@ -367,25 +333,85 @@ where
/// specified.
///
/// If type inference errors are being raised, see the comment on the definition of `Self`.
#[allow(clippy::type_complexity)]
pub fn build(
self,
) -> Client<
Witness<
TStoreMigrator,
TSlotClock,
TEth1Backend,
TEthSpec,
TEventHandler,
THotStore,
TColdStore,
) -> Result<
Client<
Witness<
TStoreMigrator,
TSlotClock,
TEth1Backend,
TEthSpec,
TEventHandler,
THotStore,
TColdStore,
>,
>,
String,
> {
Client {
let runtime_context = self
.runtime_context
.as_ref()
.ok_or_else(|| "build requires a runtime context".to_string())?;
let log = runtime_context.log().clone();
let http_api_listen_addr = if self.http_api_config.enabled {
let ctx = Arc::new(http_api::Context {
config: self.http_api_config.clone(),
chain: self.beacon_chain.clone(),
network_tx: self.network_send.clone(),
network_globals: self.network_globals.clone(),
log: log.clone(),
});
let exit = runtime_context.executor.exit();
let (listen_addr, server) = http_api::serve(ctx, exit)
.map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?;
runtime_context
.clone()
.executor
.spawn_without_exit(async move { server.await }, "http-api");
Some(listen_addr)
} else {
info!(log, "HTTP server is disabled");
None
};
let http_metrics_listen_addr = if self.http_metrics_config.enabled {
let ctx = Arc::new(http_metrics::Context {
config: self.http_metrics_config.clone(),
chain: self.beacon_chain.clone(),
db_path: self.db_path.clone(),
freezer_db_path: self.freezer_db_path.clone(),
log: log.clone(),
});
let exit = runtime_context.executor.exit();
let (listen_addr, server) = http_metrics::serve(ctx, exit)
.map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?;
runtime_context
.executor
.spawn_without_exit(async move { server.await }, "http-api");
Some(listen_addr)
} else {
debug!(log, "Metrics server is disabled");
None
};
Ok(Client {
beacon_chain: self.beacon_chain,
network_globals: self.network_globals,
http_listen_addr: self.http_listen_addr,
http_api_listen_addr,
http_metrics_listen_addr,
websocket_listen_addr: self.websocket_listen_addr,
}
})
}
}
@@ -520,6 +546,9 @@ where
.clone()
.ok_or_else(|| "disk_store requires a chain spec".to_string())?;
self.db_path = Some(hot_path.into());
self.freezer_db_path = Some(cold_path.into());
let store = HotColdDB::open(hot_path, cold_path, config, spec, context.log().clone())
.map_err(|e| format!("Unable to open database: {:?}", e))?;
self.store = Some(Arc::new(store));

View File

@@ -62,10 +62,11 @@ pub struct Config {
pub genesis: ClientGenesis,
pub store: store::StoreConfig,
pub network: network::NetworkConfig,
pub rest_api: rest_api::Config,
pub chain: beacon_chain::ChainConfig,
pub websocket_server: websocket_server::Config,
pub eth1: eth1::Config,
pub http_api: http_api::Config,
pub http_metrics: http_metrics::Config,
}
impl Default for Config {
@@ -79,7 +80,6 @@ impl Default for Config {
store: <_>::default(),
network: NetworkConfig::default(),
chain: <_>::default(),
rest_api: <_>::default(),
websocket_server: <_>::default(),
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
dummy_eth1_backend: false,
@@ -87,6 +87,8 @@ impl Default for Config {
eth1: <_>::default(),
disabled_forks: Vec::new(),
graffiti: Graffiti::default(),
http_api: <_>::default(),
http_metrics: <_>::default(),
}
}
}

View File

@@ -23,7 +23,10 @@ pub use eth2_config::Eth2Config;
pub struct Client<T: BeaconChainTypes> {
beacon_chain: Option<Arc<BeaconChain<T>>>,
network_globals: Option<Arc<NetworkGlobals<T::EthSpec>>>,
http_listen_addr: Option<SocketAddr>,
/// Listen address for the standard eth2.0 API, if the service was started.
http_api_listen_addr: Option<SocketAddr>,
/// Listen address for the HTTP server which serves Prometheus metrics.
http_metrics_listen_addr: Option<SocketAddr>,
websocket_listen_addr: Option<SocketAddr>,
}
@@ -33,9 +36,14 @@ impl<T: BeaconChainTypes> Client<T> {
self.beacon_chain.clone()
}
/// Returns the address of the client's HTTP API server, if it was started.
pub fn http_listen_addr(&self) -> Option<SocketAddr> {
self.http_listen_addr
/// Returns the address of the client's standard eth2.0 API server, if it was started.
pub fn http_api_listen_addr(&self) -> Option<SocketAddr> {
self.http_api_listen_addr
}
/// Returns the address of the client's HTTP Prometheus metrics server, if it was started.
pub fn http_metrics_listen_addr(&self) -> Option<SocketAddr> {
self.http_metrics_listen_addr
}
/// Returns the address of the client's WebSocket API server, if it was started.

View File

@@ -39,19 +39,34 @@ pub enum Eth1NetworkId {
Custom(u64),
}
impl Into<u64> for Eth1NetworkId {
fn into(self) -> u64 {
match self {
Eth1NetworkId::Mainnet => 1,
Eth1NetworkId::Goerli => 5,
Eth1NetworkId::Custom(id) => id,
}
}
}
impl From<u64> for Eth1NetworkId {
fn from(id: u64) -> Self {
let into = |x: Eth1NetworkId| -> u64 { x.into() };
match id {
id if id == into(Eth1NetworkId::Mainnet) => Eth1NetworkId::Mainnet,
id if id == into(Eth1NetworkId::Goerli) => Eth1NetworkId::Goerli,
id => Eth1NetworkId::Custom(id),
}
}
}
impl FromStr for Eth1NetworkId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1" => Ok(Eth1NetworkId::Mainnet),
"5" => Ok(Eth1NetworkId::Goerli),
custom => {
let network_id = u64::from_str_radix(custom, 10)
.map_err(|e| format!("Failed to parse eth1 network id {}", e))?;
Ok(Eth1NetworkId::Custom(network_id))
}
}
u64::from_str_radix(s, 10)
.map(Into::into)
.map_err(|e| format!("Failed to parse eth1 network id {}", e))
}
}

View File

@@ -13,4 +13,6 @@ pub use block_cache::{BlockCache, Eth1Block};
pub use deposit_cache::DepositCache;
pub use deposit_log::DepositLog;
pub use inner::SszEth1Cache;
pub use service::{BlockCacheUpdateOutcome, Config, DepositCacheUpdateOutcome, Error, Service};
pub use service::{
BlockCacheUpdateOutcome, Config, DepositCacheUpdateOutcome, Error, Service, DEFAULT_NETWORK_ID,
};

View File

@@ -1,50 +1,34 @@
[package]
name = "rest_api"
version = "0.2.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com>", "Luke Anderson <luke@sigmaprime.io>"]
name = "http_api"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls = { path = "../../crypto/bls" }
rest_types = { path = "../../common/rest_types" }
warp = "0.2.5"
serde = { version = "1.0.110", features = ["derive"] }
tokio = { version = "0.2.21", features = ["sync"] }
parking_lot = "0.11.0"
types = { path = "../../consensus/types" }
hex = "0.4.2"
beacon_chain = { path = "../beacon_chain" }
eth2 = { path = "../../common/eth2", features = ["lighthouse"] }
slog = "2.5.2"
network = { path = "../network" }
eth2_libp2p = { path = "../eth2_libp2p" }
store = { path = "../store" }
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.52"
serde_yaml = "0.8.11"
slog = "2.5.2"
slog-term = "2.5.0"
slog-async = "2.5.0"
eth2_ssz = "0.1.2"
eth2_ssz_derive = "0.1.0"
eth1 = { path = "../eth1" }
fork_choice = { path = "../../consensus/fork_choice" }
state_processing = { path = "../../consensus/state_processing" }
types = { path = "../../consensus/types" }
http = "0.2.1"
hyper = "0.13.5"
tokio = { version = "0.2.21", features = ["sync"] }
url = "2.1.1"
lazy_static = "1.4.0"
eth2_config = { path = "../../common/eth2_config" }
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
slot_clock = { path = "../../common/slot_clock" }
hex = "0.4.2"
parking_lot = "0.11.0"
futures = "0.3.5"
operation_pool = { path = "../operation_pool" }
environment = { path = "../../lighthouse/environment" }
uhttp_sse = "0.5.1"
bus = "2.2.3"
itertools = "0.9.0"
lighthouse_version = { path = "../../common/lighthouse_version" }
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
lazy_static = "1.4.0"
warp_utils = { path = "../../common/warp_utils" }
slot_clock = { path = "../../common/slot_clock" }
[dev-dependencies]
assert_matches = "1.3.0"
remote_beacon_node = { path = "../../common/remote_beacon_node" }
node_test_rig = { path = "../../testing/node_test_rig" }
tree_hash = "0.1.0"
[features]
fake_crypto = []
store = { path = "../store" }
environment = { path = "../../lighthouse/environment" }
tree_hash = { path = "../../consensus/tree_hash" }
discv5 = { version = "0.1.0-alpha.10", features = ["libp2p"] }

View File

@@ -0,0 +1,185 @@
use crate::metrics;
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
use eth2::types::ProposerData;
use fork_choice::ProtoBlock;
use slot_clock::SlotClock;
use state_processing::per_slot_processing;
use types::{BeaconState, Epoch, EthSpec, Hash256, PublicKeyBytes};
/// This sets a maximum bound on the number of epochs to skip whilst instantiating the cache for
/// the first time.
const EPOCHS_TO_SKIP: u64 = 2;
/// Caches the beacon block proposers for a given `epoch` and `epoch_boundary_root`.
///
/// This cache is only able to contain a single set of proposers and is only
/// intended to cache the proposers for the current epoch according to the head
/// of the chain. A change in epoch or re-org to a different chain may cause a
/// cache miss and rebuild.
pub struct BeaconProposerCache {
epoch: Epoch,
decision_block_root: Hash256,
proposers: Vec<ProposerData>,
}
impl BeaconProposerCache {
/// Create a new cache for the current epoch of the `chain`.
pub fn new<T: BeaconChainTypes>(chain: &BeaconChain<T>) -> Result<Self, BeaconChainError> {
let head_root = chain.head_beacon_block_root()?;
let head_block = chain
.fork_choice
.read()
.get_block(&head_root)
.ok_or_else(|| BeaconChainError::MissingBeaconBlock(head_root))?;
// If the head epoch is more than `EPOCHS_TO_SKIP` in the future, just build the cache at
// the epoch of the head. This prevents doing a massive amount of skip slots when starting
// a new database from genesis.
let epoch = {
let epoch_now = chain
.epoch()
.unwrap_or_else(|_| chain.spec.genesis_slot.epoch(T::EthSpec::slots_per_epoch()));
let head_epoch = head_block.slot.epoch(T::EthSpec::slots_per_epoch());
if epoch_now > head_epoch + EPOCHS_TO_SKIP {
head_epoch
} else {
epoch_now
}
};
Self::for_head_block(chain, epoch, head_root, head_block)
}
/// Create a new cache that contains the shuffling for `current_epoch`,
/// assuming that `head_root` and `head_block` represents the most recent
/// canonical block.
fn for_head_block<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
current_epoch: Epoch,
head_root: Hash256,
head_block: ProtoBlock,
) -> Result<Self, BeaconChainError> {
let _timer = metrics::start_timer(&metrics::HTTP_API_BEACON_PROPOSER_CACHE_TIMES);
let mut head_state = chain
.get_state(&head_block.state_root, Some(head_block.slot))?
.ok_or_else(|| BeaconChainError::MissingBeaconState(head_block.state_root))?;
let decision_block_root = Self::decision_block_root(current_epoch, head_root, &head_state)?;
// We *must* skip forward to the current epoch to obtain valid proposer
// duties. We cannot skip to the previous epoch, like we do with
// attester duties.
while head_state.current_epoch() < current_epoch {
// Skip slots until the current epoch, providing `Hash256::zero()` as the state root
// since we don't require it to be valid to identify producers.
per_slot_processing(&mut head_state, Some(Hash256::zero()), &chain.spec)?;
}
let proposers = current_epoch
.slot_iter(T::EthSpec::slots_per_epoch())
.map(|slot| {
head_state
.get_beacon_proposer_index(slot, &chain.spec)
.map_err(BeaconChainError::from)
.and_then(|i| {
let pubkey = chain
.validator_pubkey(i)?
.ok_or_else(|| BeaconChainError::ValidatorPubkeyCacheIncomplete(i))?;
Ok(ProposerData {
pubkey: PublicKeyBytes::from(pubkey),
slot,
})
})
})
.collect::<Result<_, _>>()?;
Ok(Self {
epoch: current_epoch,
decision_block_root,
proposers,
})
}
/// Returns a block root which can be used to key the shuffling obtained from the following
/// parameters:
///
/// - `shuffling_epoch`: the epoch for which the shuffling pertains.
/// - `head_block_root`: the block root at the head of the chain.
/// - `head_block_state`: the state of `head_block_root`.
pub fn decision_block_root<E: EthSpec>(
shuffling_epoch: Epoch,
head_block_root: Hash256,
head_block_state: &BeaconState<E>,
) -> Result<Hash256, BeaconChainError> {
let decision_slot = shuffling_epoch
.start_slot(E::slots_per_epoch())
.saturating_sub(1_u64);
// If decision slot is equal to or ahead of the head, the block root is the head block root
if decision_slot >= head_block_state.slot {
Ok(head_block_root)
} else {
head_block_state
.get_block_root(decision_slot)
.map(|root| *root)
.map_err(Into::into)
}
}
/// Return the proposers for the given `Epoch`.
///
/// The cache may be rebuilt if:
///
/// - The epoch has changed since the last cache build.
/// - There has been a re-org that crosses an epoch boundary.
pub fn get_proposers<T: BeaconChainTypes>(
&mut self,
chain: &BeaconChain<T>,
epoch: Epoch,
) -> Result<Vec<ProposerData>, warp::Rejection> {
let current_epoch = chain
.slot_clock
.now_or_genesis()
.ok_or_else(|| {
warp_utils::reject::custom_server_error("unable to read slot clock".to_string())
})?
.epoch(T::EthSpec::slots_per_epoch());
// Disallow requests that are outside the current epoch. This ensures the cache doesn't get
// washed-out with old values.
if current_epoch != epoch {
return Err(warp_utils::reject::custom_bad_request(format!(
"requested epoch is {} but only current epoch {} is allowed",
epoch, current_epoch
)));
}
let (head_block_root, head_decision_block_root) = chain
.with_head(|head| {
Self::decision_block_root(current_epoch, head.beacon_block_root, &head.beacon_state)
.map(|decision_root| (head.beacon_block_root, decision_root))
})
.map_err(warp_utils::reject::beacon_chain_error)?;
let head_block = chain
.fork_choice
.read()
.get_block(&head_block_root)
.ok_or_else(|| BeaconChainError::MissingBeaconBlock(head_block_root))
.map_err(warp_utils::reject::beacon_chain_error)?;
// Rebuild the cache if this call causes a cache-miss.
if self.epoch != current_epoch || self.decision_block_root != head_decision_block_root {
metrics::inc_counter(&metrics::HTTP_API_BEACON_PROPOSER_CACHE_MISSES_TOTAL);
*self = Self::for_head_block(chain, current_epoch, head_block_root, head_block)
.map_err(warp_utils::reject::beacon_chain_error)?;
} else {
metrics::inc_counter(&metrics::HTTP_API_BEACON_PROPOSER_CACHE_HITS_TOTAL);
}
Ok(self.proposers.clone())
}
}

View File

@@ -0,0 +1,87 @@
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::types::BlockId as CoreBlockId;
use std::str::FromStr;
use types::{Hash256, SignedBeaconBlock, Slot};
/// Wraps `eth2::types::BlockId` and provides a simple way to obtain a block or root for a given
/// `BlockId`.
#[derive(Debug)]
pub struct BlockId(pub CoreBlockId);
impl BlockId {
pub fn from_slot(slot: Slot) -> Self {
Self(CoreBlockId::Slot(slot))
}
pub fn from_root(root: Hash256) -> Self {
Self(CoreBlockId::Root(root))
}
/// Return the block root identified by `self`.
pub fn root<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<Hash256, warp::Rejection> {
match &self.0 {
CoreBlockId::Head => chain
.head_info()
.map(|head| head.block_root)
.map_err(warp_utils::reject::beacon_chain_error),
CoreBlockId::Genesis => Ok(chain.genesis_block_root),
CoreBlockId::Finalized => chain
.head_info()
.map(|head| head.finalized_checkpoint.root)
.map_err(warp_utils::reject::beacon_chain_error),
CoreBlockId::Justified => chain
.head_info()
.map(|head| head.current_justified_checkpoint.root)
.map_err(warp_utils::reject::beacon_chain_error),
CoreBlockId::Slot(slot) => chain
.block_root_at_slot(*slot)
.map_err(warp_utils::reject::beacon_chain_error)
.and_then(|root_opt| {
root_opt.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!(
"beacon block at slot {}",
slot
))
})
}),
CoreBlockId::Root(root) => Ok(*root),
}
}
/// Return the `SignedBeaconBlock` identified by `self`.
pub fn block<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<SignedBeaconBlock<T::EthSpec>, warp::Rejection> {
match &self.0 {
CoreBlockId::Head => chain
.head_beacon_block()
.map_err(warp_utils::reject::beacon_chain_error),
_ => {
let root = self.root(chain)?;
chain
.get_block(&root)
.map_err(warp_utils::reject::beacon_chain_error)
.and_then(|root_opt| {
root_opt.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!(
"beacon block with root {}",
root
))
})
})
}
}
}
}
impl FromStr for BlockId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
CoreBlockId::from_str(s).map(Self)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
pub use lighthouse_metrics::*;
lazy_static::lazy_static! {
pub static ref HTTP_API_PATHS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"http_api_paths_total",
"Count of HTTP requests received",
&["path"]
);
pub static ref HTTP_API_STATUS_CODES_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"http_api_status_codes_total",
"Count of HTTP status codes returned",
&["status"]
);
pub static ref HTTP_API_PATHS_TIMES: Result<HistogramVec> = try_create_histogram_vec(
"http_api_paths_times",
"Duration to process HTTP requests per path",
&["path"]
);
pub static ref HTTP_API_BEACON_PROPOSER_CACHE_TIMES: Result<Histogram> = try_create_histogram(
"http_api_beacon_proposer_cache_build_times",
"Duration to process HTTP requests per path",
);
pub static ref HTTP_API_BEACON_PROPOSER_CACHE_HITS_TOTAL: Result<IntCounter> = try_create_int_counter(
"http_api_beacon_proposer_cache_hits_total",
"Count of times the proposer cache has been hit",
);
pub static ref HTTP_API_BEACON_PROPOSER_CACHE_MISSES_TOTAL: Result<IntCounter> = try_create_int_counter(
"http_api_beacon_proposer_cache_misses_total",
"Count of times the proposer cache has been missed",
);
}

View File

@@ -0,0 +1,118 @@
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::types::StateId as CoreStateId;
use std::str::FromStr;
use types::{BeaconState, EthSpec, Fork, Hash256, Slot};
/// Wraps `eth2::types::StateId` and provides common state-access functionality. E.g., reading
/// states or parts of states from the database.
pub struct StateId(CoreStateId);
impl StateId {
pub fn head() -> Self {
Self(CoreStateId::Head)
}
pub fn slot(slot: Slot) -> Self {
Self(CoreStateId::Slot(slot))
}
/// Return the state root identified by `self`.
pub fn root<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<Hash256, warp::Rejection> {
let slot = match &self.0 {
CoreStateId::Head => {
return chain
.head_info()
.map(|head| head.state_root)
.map_err(warp_utils::reject::beacon_chain_error)
}
CoreStateId::Genesis => return Ok(chain.genesis_state_root),
CoreStateId::Finalized => chain.head_info().map(|head| {
head.finalized_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch())
}),
CoreStateId::Justified => chain.head_info().map(|head| {
head.current_justified_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch())
}),
CoreStateId::Slot(slot) => Ok(*slot),
CoreStateId::Root(root) => return Ok(*root),
}
.map_err(warp_utils::reject::beacon_chain_error)?;
chain
.state_root_at_slot(slot)
.map_err(warp_utils::reject::beacon_chain_error)?
.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!("beacon state at slot {}", slot))
})
}
/// Return the `fork` field of the state identified by `self`.
pub fn fork<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<Fork, warp::Rejection> {
self.map_state(chain, |state| Ok(state.fork))
}
/// Return the `BeaconState` identified by `self`.
pub fn state<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<BeaconState<T::EthSpec>, warp::Rejection> {
let (state_root, slot_opt) = match &self.0 {
CoreStateId::Head => {
return chain
.head_beacon_state()
.map_err(warp_utils::reject::beacon_chain_error)
}
CoreStateId::Slot(slot) => (self.root(chain)?, Some(*slot)),
_ => (self.root(chain)?, None),
};
chain
.get_state(&state_root, slot_opt)
.map_err(warp_utils::reject::beacon_chain_error)
.and_then(|opt| {
opt.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!(
"beacon state at root {}",
state_root
))
})
})
}
/// Map a function across the `BeaconState` identified by `self`.
///
/// This function will avoid instantiating/copying a new state when `self` points to the head
/// of the chain.
pub fn map_state<T: BeaconChainTypes, F, U>(
&self,
chain: &BeaconChain<T>,
func: F,
) -> Result<U, warp::Rejection>
where
F: Fn(&BeaconState<T::EthSpec>) -> Result<U, warp::Rejection>,
{
match &self.0 {
CoreStateId::Head => chain
.with_head(|snapshot| Ok(func(&snapshot.beacon_state)))
.map_err(warp_utils::reject::beacon_chain_error)?,
_ => func(&self.state(chain)?),
}
}
}
impl FromStr for StateId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
CoreStateId::from_str(s).map(Self)
}
}

View File

@@ -0,0 +1,88 @@
use crate::state_id::StateId;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::{
lighthouse::{GlobalValidatorInclusionData, ValidatorInclusionData},
types::ValidatorId,
};
use state_processing::per_epoch_processing::ValidatorStatuses;
use types::{Epoch, EthSpec};
/// Returns information about *all validators* (i.e., global) and how they performed during a given
/// epoch.
pub fn global_validator_inclusion_data<T: BeaconChainTypes>(
epoch: Epoch,
chain: &BeaconChain<T>,
) -> Result<GlobalValidatorInclusionData, warp::Rejection> {
let target_slot = epoch.end_slot(T::EthSpec::slots_per_epoch());
let state = StateId::slot(target_slot).state(chain)?;
let mut validator_statuses = ValidatorStatuses::new(&state, &chain.spec)
.map_err(warp_utils::reject::beacon_state_error)?;
validator_statuses
.process_attestations(&state, &chain.spec)
.map_err(warp_utils::reject::beacon_state_error)?;
let totals = validator_statuses.total_balances;
Ok(GlobalValidatorInclusionData {
current_epoch_active_gwei: totals.current_epoch(),
previous_epoch_active_gwei: totals.previous_epoch(),
current_epoch_attesting_gwei: totals.current_epoch_attesters(),
current_epoch_target_attesting_gwei: totals.current_epoch_target_attesters(),
previous_epoch_attesting_gwei: totals.previous_epoch_attesters(),
previous_epoch_target_attesting_gwei: totals.previous_epoch_target_attesters(),
previous_epoch_head_attesting_gwei: totals.previous_epoch_head_attesters(),
})
}
/// Returns information about a single validator and how it performed during a given epoch.
pub fn validator_inclusion_data<T: BeaconChainTypes>(
epoch: Epoch,
validator_id: &ValidatorId,
chain: &BeaconChain<T>,
) -> Result<Option<ValidatorInclusionData>, warp::Rejection> {
let target_slot = epoch.end_slot(T::EthSpec::slots_per_epoch());
let mut state = StateId::slot(target_slot).state(chain)?;
let mut validator_statuses = ValidatorStatuses::new(&state, &chain.spec)
.map_err(warp_utils::reject::beacon_state_error)?;
validator_statuses
.process_attestations(&state, &chain.spec)
.map_err(warp_utils::reject::beacon_state_error)?;
state
.update_pubkey_cache()
.map_err(warp_utils::reject::beacon_state_error)?;
let validator_index = match validator_id {
ValidatorId::Index(index) => *index as usize,
ValidatorId::PublicKey(pubkey) => {
if let Some(index) = state
.get_validator_index(pubkey)
.map_err(warp_utils::reject::beacon_state_error)?
{
index
} else {
return Ok(None);
}
}
};
Ok(validator_statuses
.statuses
.get(validator_index)
.map(|vote| ValidatorInclusionData {
is_slashed: vote.is_slashed,
is_withdrawable_in_current_epoch: vote.is_withdrawable_in_current_epoch,
is_active_in_current_epoch: vote.is_active_in_current_epoch,
is_active_in_previous_epoch: vote.is_active_in_previous_epoch,
current_epoch_effective_balance_gwei: vote.current_epoch_effective_balance,
is_current_epoch_attester: vote.is_current_epoch_attester,
is_current_epoch_target_attester: vote.is_current_epoch_target_attester,
is_previous_epoch_attester: vote.is_previous_epoch_attester,
is_previous_epoch_target_attester: vote.is_previous_epoch_target_attester,
is_previous_epoch_head_attester: vote.is_previous_epoch_head_attester,
}))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
[package]
name = "http_metrics"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
prometheus = "0.9.0"
warp = "0.2.5"
serde = { version = "1.0.110", features = ["derive"] }
slog = "2.5.2"
beacon_chain = { path = "../beacon_chain" }
store = { path = "../store" }
eth2_libp2p = { path = "../eth2_libp2p" }
slot_clock = { path = "../../common/slot_clock" }
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
lazy_static = "1.4.0"
eth2 = { path = "../../common/eth2" }
lighthouse_version = { path = "../../common/lighthouse_version" }
warp_utils = { path = "../../common/warp_utils" }
[dev-dependencies]
tokio = { version = "0.2.21", features = ["sync"] }
reqwest = { version = "0.10.8", features = ["json"] }
environment = { path = "../../lighthouse/environment" }
types = { path = "../../consensus/types" }

View File

@@ -0,0 +1,135 @@
//! This crate provides a HTTP server that is solely dedicated to serving the `/metrics` endpoint.
//!
//! For other endpoints, see the `http_api` crate.
#[macro_use]
extern crate lazy_static;
mod metrics;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use lighthouse_version::version_with_platform;
use serde::{Deserialize, Serialize};
use slog::{crit, info, Logger};
use std::future::Future;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::path::PathBuf;
use std::sync::Arc;
use warp::{http::Response, Filter};
#[derive(Debug)]
pub enum Error {
Warp(warp::Error),
Other(String),
}
impl From<warp::Error> for Error {
fn from(e: warp::Error) -> Self {
Error::Warp(e)
}
}
impl From<String> for Error {
fn from(e: String) -> Self {
Error::Other(e)
}
}
/// A wrapper around all the items required to spawn the HTTP server.
///
/// The server will gracefully handle the case where any fields are `None`.
pub struct Context<T: BeaconChainTypes> {
pub config: Config,
pub chain: Option<Arc<BeaconChain<T>>>,
pub db_path: Option<PathBuf>,
pub freezer_db_path: Option<PathBuf>,
pub log: Logger,
}
/// Configuration for the HTTP server.
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub enabled: bool,
pub listen_addr: Ipv4Addr,
pub listen_port: u16,
pub allow_origin: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
enabled: false,
listen_addr: Ipv4Addr::new(127, 0, 0, 1),
listen_port: 5054,
allow_origin: None,
}
}
}
/// Creates a server that will serve requests using information from `ctx`.
///
/// The server will shut down gracefully when the `shutdown` future resolves.
///
/// ## Returns
///
/// This function will bind the server to the provided address and then return a tuple of:
///
/// - `SocketAddr`: the address that the HTTP server will listen on.
/// - `Future`: the actual server future that will need to be awaited.
///
/// ## Errors
///
/// Returns an error if the server is unable to bind or there is another error during
/// configuration.
pub fn serve<T: BeaconChainTypes>(
ctx: Arc<Context<T>>,
shutdown: impl Future<Output = ()> + Send + Sync + 'static,
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
let config = &ctx.config;
let log = ctx.log.clone();
let allow_origin = config.allow_origin.clone();
// Sanity check.
if !config.enabled {
crit!(log, "Cannot start disabled metrics HTTP server");
return Err(Error::Other(
"A disabled metrics server should not be started".to_string(),
));
}
let inner_ctx = ctx.clone();
let routes = warp::get()
.and(warp::path("metrics"))
.map(move || inner_ctx.clone())
.and_then(|ctx: Arc<Context<T>>| async move {
Ok::<_, warp::Rejection>(
metrics::gather_prometheus_metrics(&ctx)
.map(|body| Response::builder().status(200).body(body).unwrap())
.unwrap_or_else(|e| {
Response::builder()
.status(500)
.body(format!("Unable to gather metrics: {:?}", e))
.unwrap()
}),
)
})
// Add a `Server` header.
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
// Maybe add some CORS headers.
.map(move |reply| warp_utils::reply::maybe_cors(reply, allow_origin.as_ref()));
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
SocketAddrV4::new(config.listen_addr, config.listen_port),
async {
shutdown.await;
},
)?;
info!(
log,
"Metrics HTTP server started";
"listen_address" => listening_socket.to_string(),
);
Ok((listening_socket, server))
}

View File

@@ -1,38 +1,11 @@
use crate::{ApiError, Context};
use crate::Context;
use beacon_chain::BeaconChainTypes;
use eth2::lighthouse::Health;
use lighthouse_metrics::{Encoder, TextEncoder};
use rest_types::Health;
use std::sync::Arc;
pub use lighthouse_metrics::*;
lazy_static! {
pub static ref BEACON_HTTP_API_REQUESTS_TOTAL: Result<IntCounterVec> =
try_create_int_counter_vec(
"beacon_http_api_requests_total",
"Count of HTTP requests received",
&["endpoint"]
);
pub static ref BEACON_HTTP_API_SUCCESS_TOTAL: Result<IntCounterVec> =
try_create_int_counter_vec(
"beacon_http_api_success_total",
"Count of HTTP requests that returned 200 OK",
&["endpoint"]
);
pub static ref BEACON_HTTP_API_ERROR_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"beacon_http_api_error_total",
"Count of HTTP that did not return 200 OK",
&["endpoint"]
);
pub static ref BEACON_HTTP_API_TIMES_TOTAL: Result<HistogramVec> = try_create_histogram_vec(
"beacon_http_api_times_total",
"Duration to process HTTP requests",
&["endpoint"]
);
pub static ref REQUEST_RESPONSE_TIME: Result<Histogram> = try_create_histogram(
"http_server_request_duration_seconds",
"Time taken to build a response to a HTTP request"
);
pub static ref PROCESS_NUM_THREADS: Result<IntGauge> = try_create_int_gauge(
"process_num_threads",
"Number of threads used by the current process"
@@ -67,14 +40,9 @@ lazy_static! {
try_create_float_gauge("system_loadavg_15", "Loadavg over 15 minutes");
}
/// Returns the full set of Prometheus metrics for the Beacon Node application.
///
/// # Note
///
/// This is a HTTP handler method.
pub fn get_prometheus<T: BeaconChainTypes>(
ctx: Arc<Context<T>>,
) -> std::result::Result<String, ApiError> {
pub fn gather_prometheus_metrics<T: BeaconChainTypes>(
ctx: &Context<T>,
) -> std::result::Result<String, String> {
let mut buffer = vec![];
let encoder = TextEncoder::new();
@@ -94,9 +62,17 @@ pub fn get_prometheus<T: BeaconChainTypes>(
// using `lighthouse_metrics::gather(..)` to collect the global `DEFAULT_REGISTRY` metrics into
// a string that can be returned via HTTP.
slot_clock::scrape_for_metrics::<T::EthSpec, T::SlotClock>(&ctx.beacon_chain.slot_clock);
store::scrape_for_metrics(&ctx.db_path, &ctx.freezer_db_path);
beacon_chain::scrape_for_metrics(&ctx.beacon_chain);
if let Some(beacon_chain) = ctx.chain.as_ref() {
slot_clock::scrape_for_metrics::<T::EthSpec, T::SlotClock>(&beacon_chain.slot_clock);
beacon_chain::scrape_for_metrics(beacon_chain);
}
if let (Some(db_path), Some(freezer_db_path)) =
(ctx.db_path.as_ref(), ctx.freezer_db_path.as_ref())
{
store::scrape_for_metrics(db_path, freezer_db_path);
}
eth2_libp2p::scrape_discovery_metrics();
// This will silently fail if we are unable to observe the health. This is desired behaviour
@@ -125,6 +101,5 @@ pub fn get_prometheus<T: BeaconChainTypes>(
.encode(&lighthouse_metrics::gather(), &mut buffer)
.unwrap();
String::from_utf8(buffer)
.map_err(|e| ApiError::ServerError(format!("Failed to encode prometheus info: {:?}", e)))
String::from_utf8(buffer).map_err(|e| format!("Failed to encode prometheus info: {:?}", e))
}

View File

@@ -0,0 +1,46 @@
use beacon_chain::test_utils::BlockingMigratorEphemeralHarnessType;
use environment::null_logger;
use http_metrics::Config;
use reqwest::StatusCode;
use std::net::Ipv4Addr;
use std::sync::Arc;
use tokio::sync::oneshot;
use types::MainnetEthSpec;
type Context = http_metrics::Context<BlockingMigratorEphemeralHarnessType<MainnetEthSpec>>;
#[tokio::test(core_threads = 2)]
async fn returns_200_ok() {
let log = null_logger().unwrap();
let context = Arc::new(Context {
config: Config {
enabled: true,
listen_addr: Ipv4Addr::new(127, 0, 0, 1),
listen_port: 0,
allow_origin: None,
},
chain: None,
db_path: None,
freezer_db_path: None,
log,
});
let ctx = context.clone();
let (_shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let server_shutdown = async {
// It's not really interesting why this triggered, just that it happened.
let _ = shutdown_rx.await;
};
let (listening_socket, server) = http_metrics::serve(ctx, server_shutdown).unwrap();
tokio::spawn(async { server.await });
let url = format!(
"http://{}:{}/metrics",
listening_socket.ip(),
listening_socket.port()
);
assert_eq!(reqwest::get(&url).await.unwrap().status(), StatusCode::OK);
}

View File

@@ -17,7 +17,6 @@ beacon_chain = { path = "../beacon_chain" }
store = { path = "../store" }
eth2_libp2p = { path = "../eth2_libp2p" }
hashset_delay = { path = "../../common/hashset_delay" }
rest_types = { path = "../../common/rest_types" }
types = { path = "../../consensus/types" }
state_processing = { path = "../../consensus/state_processing" }
slot_clock = { path = "../../common/slot_clock" }

View File

@@ -15,9 +15,8 @@ use slog::{debug, error, o, trace, warn};
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2_libp2p::SubnetDiscovery;
use hashset_delay::HashSetDelay;
use rest_types::ValidatorSubscription;
use slot_clock::SlotClock;
use types::{Attestation, EthSpec, Slot, SubnetId};
use types::{Attestation, EthSpec, Slot, SubnetId, ValidatorSubscription};
use crate::metrics;

View File

@@ -45,7 +45,7 @@ impl<T: BeaconChainTypes> Worker<T> {
let attestation = match self
.chain
.verify_unaggregated_attestation_for_gossip(attestation, subnet_id)
.verify_unaggregated_attestation_for_gossip(attestation, Some(subnet_id))
{
Ok(attestation) => attestation,
Err(e) => {

View File

@@ -15,13 +15,12 @@ use eth2_libp2p::{
};
use eth2_libp2p::{MessageAcceptance, Service as LibP2PService};
use futures::prelude::*;
use rest_types::ValidatorSubscription;
use slog::{debug, error, info, o, trace, warn};
use std::{collections::HashMap, sync::Arc, time::Duration};
use store::HotColdDB;
use tokio::sync::mpsc;
use tokio::time::Delay;
use types::EthSpec;
use types::{EthSpec, ValidatorSubscription};
mod tests;

View File

@@ -332,6 +332,51 @@ impl<T: EthSpec> OperationPool<T> {
pub fn num_voluntary_exits(&self) -> usize {
self.voluntary_exits.read().len()
}
/// Returns all known `Attestation` objects.
///
/// This method may return objects that are invalid for block inclusion.
pub fn get_all_attestations(&self) -> Vec<Attestation<T>> {
self.attestations
.read()
.iter()
.map(|(_, attns)| attns.iter().cloned())
.flatten()
.collect()
}
/// Returns all known `AttesterSlashing` objects.
///
/// This method may return objects that are invalid for block inclusion.
pub fn get_all_attester_slashings(&self) -> Vec<AttesterSlashing<T>> {
self.attester_slashings
.read()
.iter()
.map(|(slashing, _)| slashing.clone())
.collect()
}
/// Returns all known `ProposerSlashing` objects.
///
/// This method may return objects that are invalid for block inclusion.
pub fn get_all_proposer_slashings(&self) -> Vec<ProposerSlashing> {
self.proposer_slashings
.read()
.iter()
.map(|(_, slashing)| slashing.clone())
.collect()
}
/// Returns all known `SignedVoluntaryExit` objects.
///
/// This method may return objects that are invalid for block inclusion.
pub fn get_all_voluntary_exits(&self) -> Vec<SignedVoluntaryExit> {
self.voluntary_exits
.read()
.iter()
.map(|(_, exit)| exit.clone())
.collect()
}
}
/// Filter up to a maximum number of operations out of an iterator.

View File

@@ -1,499 +0,0 @@
use crate::helpers::*;
use crate::validator::get_state_for_epoch;
use crate::Context;
use crate::{ApiError, UrlQuery};
use beacon_chain::{
observed_operations::ObservationOutcome, BeaconChain, BeaconChainTypes, StateSkipConfig,
};
use futures::executor::block_on;
use hyper::body::Bytes;
use hyper::{Body, Request};
use rest_types::{
BlockResponse, CanonicalHeadResponse, Committee, HeadBeaconBlock, StateResponse,
ValidatorRequest, ValidatorResponse,
};
use std::io::Write;
use std::sync::Arc;
use slog::error;
use types::{
AttesterSlashing, BeaconState, EthSpec, Hash256, ProposerSlashing, PublicKeyBytes,
RelativeEpoch, SignedBeaconBlockHash, Slot,
};
/// Returns a summary of the head of the beacon chain.
pub fn get_head<T: BeaconChainTypes>(
ctx: Arc<Context<T>>,
) -> Result<CanonicalHeadResponse, ApiError> {
let beacon_chain = &ctx.beacon_chain;
let chain_head = beacon_chain.head()?;
Ok(CanonicalHeadResponse {
slot: chain_head.beacon_state.slot,
block_root: chain_head.beacon_block_root,
state_root: chain_head.beacon_state_root,
finalized_slot: chain_head
.beacon_state
.finalized_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
finalized_block_root: chain_head.beacon_state.finalized_checkpoint.root,
justified_slot: chain_head
.beacon_state
.current_justified_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
justified_block_root: chain_head.beacon_state.current_justified_checkpoint.root,
previous_justified_slot: chain_head
.beacon_state
.previous_justified_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
previous_justified_block_root: chain_head.beacon_state.previous_justified_checkpoint.root,
})
}
/// Return the list of heads of the beacon chain.
pub fn get_heads<T: BeaconChainTypes>(ctx: Arc<Context<T>>) -> Vec<HeadBeaconBlock> {
ctx.beacon_chain
.heads()
.into_iter()
.map(|(beacon_block_root, beacon_block_slot)| HeadBeaconBlock {
beacon_block_root,
beacon_block_slot,
})
.collect()
}
/// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`.
pub fn get_block<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<BlockResponse<T::EthSpec>, ApiError> {
let beacon_chain = &ctx.beacon_chain;
let query_params = ["root", "slot"];
let (key, value) = UrlQuery::from_request(&req)?.first_of(&query_params)?;
let block_root = match (key.as_ref(), value) {
("slot", value) => {
let target = parse_slot(&value)?;
block_root_at_slot(beacon_chain, target)?.ok_or_else(|| {
ApiError::NotFound(format!(
"Unable to find SignedBeaconBlock for slot {:?}",
target
))
})?
}
("root", value) => parse_root(&value)?,
_ => return Err(ApiError::ServerError("Unexpected query parameter".into())),
};
let block = beacon_chain.store.get_block(&block_root)?.ok_or_else(|| {
ApiError::NotFound(format!(
"Unable to find SignedBeaconBlock for root {:?}",
block_root
))
})?;
Ok(BlockResponse {
root: block_root,
beacon_block: block,
})
}
/// HTTP handler to return a `SignedBeaconBlock` root at a given `slot`.
pub fn get_block_root<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Hash256, ApiError> {
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
let target = parse_slot(&slot_string)?;
block_root_at_slot(&ctx.beacon_chain, target)?.ok_or_else(|| {
ApiError::NotFound(format!(
"Unable to find SignedBeaconBlock for slot {:?}",
target
))
})
}
fn make_sse_response_chunk(new_head_hash: SignedBeaconBlockHash) -> std::io::Result<Bytes> {
let mut buffer = Vec::new();
{
let mut sse_message = uhttp_sse::SseMessage::new(&mut buffer);
let untyped_hash: Hash256 = new_head_hash.into();
write!(sse_message.data()?, "{:?}", untyped_hash)?;
}
let bytes: Bytes = buffer.into();
Ok(bytes)
}
pub fn stream_forks<T: BeaconChainTypes>(ctx: Arc<Context<T>>) -> Result<Body, ApiError> {
let mut events = ctx.events.lock().add_rx();
let (mut sender, body) = Body::channel();
std::thread::spawn(move || {
while let Ok(new_head_hash) = events.recv() {
let chunk = match make_sse_response_chunk(new_head_hash) {
Ok(chunk) => chunk,
Err(e) => {
error!(ctx.log, "Failed to make SSE chunk"; "error" => e.to_string());
sender.abort();
break;
}
};
match block_on(sender.send_data(chunk)) {
Err(e) if e.is_closed() => break,
Err(e) => error!(ctx.log, "Couldn't stream piece {:?}", e),
Ok(_) => (),
}
}
});
Ok(body)
}
/// HTTP handler to which accepts a query string of a list of validator pubkeys and maps it to a
/// `ValidatorResponse`.
///
/// This method is limited to as many `pubkeys` that can fit in a URL. See `post_validators` for
/// doing bulk requests.
pub fn get_validators<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorResponse>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let validator_pubkeys = query
.all_of("validator_pubkeys")?
.iter()
.map(|validator_pubkey_str| parse_pubkey_bytes(validator_pubkey_str))
.collect::<Result<Vec<_>, _>>()?;
let state_root_opt = if let Some((_key, value)) = query.first_of_opt(&["state_root"]) {
Some(parse_root(&value)?)
} else {
None
};
validator_responses_by_pubkey(&ctx.beacon_chain, state_root_opt, validator_pubkeys)
}
/// HTTP handler to return all validators, each as a `ValidatorResponse`.
pub fn get_all_validators<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorResponse>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let state_root_opt = if let Some((_key, value)) = query.first_of_opt(&["state_root"]) {
Some(parse_root(&value)?)
} else {
None
};
let mut state = get_state_from_root_opt(&ctx.beacon_chain, state_root_opt)?;
let validators = state.validators.clone();
validators
.iter()
.map(|validator| validator_response_by_pubkey(&mut state, validator.pubkey.clone()))
.collect::<Result<Vec<_>, _>>()
}
/// HTTP handler to return all active validators, each as a `ValidatorResponse`.
pub fn get_active_validators<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorResponse>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let state_root_opt = if let Some((_key, value)) = query.first_of_opt(&["state_root"]) {
Some(parse_root(&value)?)
} else {
None
};
let mut state = get_state_from_root_opt(&ctx.beacon_chain, state_root_opt)?;
let validators = state.validators.clone();
let current_epoch = state.current_epoch();
validators
.iter()
.filter(|validator| validator.is_active_at(current_epoch))
.map(|validator| validator_response_by_pubkey(&mut state, validator.pubkey.clone()))
.collect::<Result<Vec<_>, _>>()
}
/// HTTP handler to which accepts a `ValidatorRequest` and returns a `ValidatorResponse` for
/// each of the given `pubkeys`. When `state_root` is `None`, the canonical head is used.
///
/// This method allows for a basically unbounded list of `pubkeys`, where as the `get_validators`
/// request is limited by the max number of pubkeys you can fit in a URL.
pub fn post_validators<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorResponse>, ApiError> {
serde_json::from_slice::<ValidatorRequest>(&req.into_body())
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to parse JSON into ValidatorRequest: {:?}",
e
))
})
.and_then(|bulk_request| {
validator_responses_by_pubkey(
&ctx.beacon_chain,
bulk_request.state_root,
bulk_request.pubkeys,
)
})
}
/// Returns either the state given by `state_root_opt`, or the canonical head state if it is
/// `None`.
fn get_state_from_root_opt<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
state_root_opt: Option<Hash256>,
) -> Result<BeaconState<T::EthSpec>, ApiError> {
if let Some(state_root) = state_root_opt {
beacon_chain
.get_state(&state_root, None)
.map_err(|e| {
ApiError::ServerError(format!(
"Database error when reading state root {}: {:?}",
state_root, e
))
})?
.ok_or_else(|| ApiError::NotFound(format!("No state exists with root: {}", state_root)))
} else {
Ok(beacon_chain.head()?.beacon_state)
}
}
/// Maps a vec of `validator_pubkey` to a vec of `ValidatorResponse`, using the state at the given
/// `state_root`. If `state_root.is_none()`, uses the canonial head state.
fn validator_responses_by_pubkey<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
state_root_opt: Option<Hash256>,
validator_pubkeys: Vec<PublicKeyBytes>,
) -> Result<Vec<ValidatorResponse>, ApiError> {
let mut state = get_state_from_root_opt(beacon_chain, state_root_opt)?;
validator_pubkeys
.into_iter()
.map(|validator_pubkey| validator_response_by_pubkey(&mut state, validator_pubkey))
.collect::<Result<Vec<_>, ApiError>>()
}
/// Maps a `validator_pubkey` to a `ValidatorResponse`, using the given state.
///
/// The provided `state` must have a fully up-to-date pubkey cache.
fn validator_response_by_pubkey<E: EthSpec>(
state: &mut BeaconState<E>,
validator_pubkey: PublicKeyBytes,
) -> Result<ValidatorResponse, ApiError> {
let validator_index_opt = state
.get_validator_index(&validator_pubkey)
.map_err(|e| ApiError::ServerError(format!("Unable to read pubkey cache: {:?}", e)))?;
if let Some(validator_index) = validator_index_opt {
let balance = state.balances.get(validator_index).ok_or_else(|| {
ApiError::ServerError(format!("Invalid balances index: {:?}", validator_index))
})?;
let validator = state
.validators
.get(validator_index)
.ok_or_else(|| {
ApiError::ServerError(format!("Invalid validator index: {:?}", validator_index))
})?
.clone();
Ok(ValidatorResponse {
pubkey: validator_pubkey,
validator_index: Some(validator_index),
balance: Some(*balance),
validator: Some(validator),
})
} else {
Ok(ValidatorResponse {
pubkey: validator_pubkey,
validator_index: None,
balance: None,
validator: None,
})
}
}
/// HTTP handler
pub fn get_committees<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<Committee>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let epoch = query.epoch()?;
let mut state =
get_state_for_epoch(&ctx.beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch).map_err(|e| {
ApiError::ServerError(format!("Failed to get state suitable for epoch: {:?}", e))
})?;
state
.build_committee_cache(relative_epoch, &ctx.beacon_chain.spec)
.map_err(|e| ApiError::ServerError(format!("Unable to build committee cache: {:?}", e)))?;
Ok(state
.get_beacon_committees_at_epoch(relative_epoch)
.map_err(|e| ApiError::ServerError(format!("Unable to get all committees: {:?}", e)))?
.into_iter()
.map(|c| Committee {
slot: c.slot,
index: c.index,
committee: c.committee.to_vec(),
})
.collect::<Vec<_>>())
}
/// HTTP handler to return a `BeaconState` at a given `root` or `slot`.
///
/// Will not return a state if the request slot is in the future. Will return states higher than
/// the current head by skipping slots.
pub fn get_state<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<StateResponse<T::EthSpec>, ApiError> {
let head_state = ctx.beacon_chain.head()?.beacon_state;
let (key, value) = match UrlQuery::from_request(&req) {
Ok(query) => {
// We have *some* parameters, just check them.
let query_params = ["root", "slot"];
query.first_of(&query_params)?
}
Err(ApiError::BadRequest(_)) => {
// No parameters provided at all, use current slot.
(String::from("slot"), head_state.slot.to_string())
}
Err(e) => {
return Err(e);
}
};
let (root, state): (Hash256, BeaconState<T::EthSpec>) = match (key.as_ref(), value) {
("slot", value) => state_at_slot(&ctx.beacon_chain, parse_slot(&value)?)?,
("root", value) => {
let root = &parse_root(&value)?;
let state = ctx
.beacon_chain
.store
.get_state(root, None)?
.ok_or_else(|| ApiError::NotFound(format!("No state for root: {:?}", root)))?;
(*root, state)
}
_ => return Err(ApiError::ServerError("Unexpected query parameter".into())),
};
Ok(StateResponse {
root,
beacon_state: state,
})
}
/// HTTP handler to return a `BeaconState` root at a given `slot`.
///
/// Will not return a state if the request slot is in the future. Will return states higher than
/// the current head by skipping slots.
pub fn get_state_root<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Hash256, ApiError> {
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
let slot = parse_slot(&slot_string)?;
state_root_at_slot(&ctx.beacon_chain, slot, StateSkipConfig::WithStateRoots)
}
/// HTTP handler to return a `BeaconState` at the genesis block.
///
/// This is an undocumented convenience method used during testing. For production, simply do a
/// state request at slot 0.
pub fn get_genesis_state<T: BeaconChainTypes>(
ctx: Arc<Context<T>>,
) -> Result<BeaconState<T::EthSpec>, ApiError> {
state_at_slot(&ctx.beacon_chain, Slot::new(0)).map(|(_root, state)| state)
}
pub fn proposer_slashing<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<bool, ApiError> {
let body = req.into_body();
serde_json::from_slice::<ProposerSlashing>(&body)
.map_err(|e| format!("Unable to parse JSON into ProposerSlashing: {:?}", e))
.and_then(move |proposer_slashing| {
if ctx.beacon_chain.eth1_chain.is_some() {
let obs_outcome = ctx
.beacon_chain
.verify_proposer_slashing_for_gossip(proposer_slashing)
.map_err(|e| format!("Error while verifying proposer slashing: {:?}", e))?;
if let ObservationOutcome::New(verified_proposer_slashing) = obs_outcome {
ctx.beacon_chain
.import_proposer_slashing(verified_proposer_slashing);
Ok(())
} else {
Err("Proposer slashing for that validator index already known".into())
}
} else {
Err("Cannot insert proposer slashing on node without Eth1 connection.".to_string())
}
})
.map_err(ApiError::BadRequest)?;
Ok(true)
}
pub fn attester_slashing<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<bool, ApiError> {
let body = req.into_body();
serde_json::from_slice::<AttesterSlashing<T::EthSpec>>(&body)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to parse JSON into AttesterSlashing: {:?}",
e
))
})
.and_then(move |attester_slashing| {
if ctx.beacon_chain.eth1_chain.is_some() {
ctx.beacon_chain
.verify_attester_slashing_for_gossip(attester_slashing)
.map_err(|e| format!("Error while verifying attester slashing: {:?}", e))
.and_then(|outcome| {
if let ObservationOutcome::New(verified_attester_slashing) = outcome {
ctx.beacon_chain
.import_attester_slashing(verified_attester_slashing)
.map_err(|e| {
format!("Error while importing attester slashing: {:?}", e)
})
} else {
Err("Attester slashing only covers already slashed indices".to_string())
}
})
.map_err(ApiError::BadRequest)
} else {
Err(ApiError::BadRequest(
"Cannot insert attester slashing on node without Eth1 connection.".to_string(),
))
}
})?;
Ok(true)
}

View File

@@ -1,55 +0,0 @@
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
/// Defines the encoding for the API.
#[derive(Clone, Serialize, Deserialize, Copy)]
pub enum ApiEncodingFormat {
JSON,
YAML,
SSZ,
}
impl ApiEncodingFormat {
pub fn get_content_type(&self) -> &str {
match self {
ApiEncodingFormat::JSON => "application/json",
ApiEncodingFormat::YAML => "application/yaml",
ApiEncodingFormat::SSZ => "application/ssz",
}
}
}
impl From<&str> for ApiEncodingFormat {
fn from(f: &str) -> ApiEncodingFormat {
match f {
"application/yaml" => ApiEncodingFormat::YAML,
"application/ssz" => ApiEncodingFormat::SSZ,
_ => ApiEncodingFormat::JSON,
}
}
}
/// HTTP REST API Configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Enable the REST API server.
pub enabled: bool,
/// The IPv4 address the REST API HTTP server will listen on.
pub listen_address: Ipv4Addr,
/// The port the REST API HTTP server will listen on.
pub port: u16,
/// If something else than "", a 'Access-Control-Allow-Origin' header will be present in
/// responses. Put *, to allow any origin.
pub allow_origin: String,
}
impl Default for Config {
fn default() -> Self {
Config {
enabled: false,
listen_address: Ipv4Addr::new(127, 0, 0, 1),
port: 5052,
allow_origin: "".to_string(),
}
}
}

View File

@@ -1,126 +0,0 @@
use crate::helpers::*;
use crate::{ApiError, Context, UrlQuery};
use beacon_chain::BeaconChainTypes;
use hyper::Request;
use rest_types::{IndividualVotesRequest, IndividualVotesResponse};
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use state_processing::per_epoch_processing::{TotalBalances, ValidatorStatuses};
use std::sync::Arc;
use types::EthSpec;
/// The results of validators voting during an epoch.
///
/// Provides information about the current and previous epochs.
#[derive(Serialize, Deserialize, Encode, Decode)]
pub struct VoteCount {
/// The total effective balance of all active validators during the _current_ epoch.
pub current_epoch_active_gwei: u64,
/// The total effective balance of all active validators during the _previous_ epoch.
pub previous_epoch_active_gwei: u64,
/// The total effective balance of all validators who attested during the _current_ epoch.
pub current_epoch_attesting_gwei: u64,
/// The total effective balance of all validators who attested during the _current_ epoch and
/// agreed with the state about the beacon block at the first slot of the _current_ epoch.
pub current_epoch_target_attesting_gwei: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch.
pub previous_epoch_attesting_gwei: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the first slot of the _previous_ epoch.
pub previous_epoch_target_attesting_gwei: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the time of attestation.
pub previous_epoch_head_attesting_gwei: u64,
}
impl Into<VoteCount> for TotalBalances {
fn into(self) -> VoteCount {
VoteCount {
current_epoch_active_gwei: self.current_epoch(),
previous_epoch_active_gwei: self.previous_epoch(),
current_epoch_attesting_gwei: self.current_epoch_attesters(),
current_epoch_target_attesting_gwei: self.current_epoch_target_attesters(),
previous_epoch_attesting_gwei: self.previous_epoch_attesters(),
previous_epoch_target_attesting_gwei: self.previous_epoch_target_attesters(),
previous_epoch_head_attesting_gwei: self.previous_epoch_head_attesters(),
}
}
}
/// HTTP handler return a `VoteCount` for some given `Epoch`.
pub fn get_vote_count<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<VoteCount, ApiError> {
let query = UrlQuery::from_request(&req)?;
let epoch = query.epoch()?;
// This is the last slot of the given epoch (one prior to the first slot of the next epoch).
let target_slot = (epoch + 1).start_slot(T::EthSpec::slots_per_epoch()) - 1;
let (_root, state) = state_at_slot(&ctx.beacon_chain, target_slot)?;
let spec = &ctx.beacon_chain.spec;
let mut validator_statuses = ValidatorStatuses::new(&state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
Ok(validator_statuses.total_balances.into())
}
pub fn post_individual_votes<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<IndividualVotesResponse>, ApiError> {
let body = req.into_body();
serde_json::from_slice::<IndividualVotesRequest>(&body)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to parse JSON into ValidatorDutiesRequest: {:?}",
e
))
})
.and_then(move |body| {
let epoch = body.epoch;
// This is the last slot of the given epoch (one prior to the first slot of the next epoch).
let target_slot = (epoch + 1).start_slot(T::EthSpec::slots_per_epoch()) - 1;
let (_root, mut state) = state_at_slot(&ctx.beacon_chain, target_slot)?;
let spec = &ctx.beacon_chain.spec;
let mut validator_statuses = ValidatorStatuses::new(&state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
body.pubkeys
.into_iter()
.map(|pubkey| {
let validator_index_opt = state.get_validator_index(&pubkey).map_err(|e| {
ApiError::ServerError(format!("Unable to read pubkey cache: {:?}", e))
})?;
if let Some(validator_index) = validator_index_opt {
let vote = validator_statuses
.statuses
.get(validator_index)
.cloned()
.map(Into::into);
Ok(IndividualVotesResponse {
epoch,
pubkey,
validator_index: Some(validator_index),
vote,
})
} else {
Ok(IndividualVotesResponse {
epoch,
pubkey,
validator_index: None,
vote: None,
})
}
})
.collect::<Result<Vec<_>, _>>()
})
}

View File

@@ -1,260 +0,0 @@
use crate::{ApiError, NetworkChannel};
use beacon_chain::{BeaconChain, BeaconChainTypes, StateSkipConfig};
use bls::PublicKeyBytes;
use eth2_libp2p::PubsubMessage;
use itertools::process_results;
use network::NetworkMessage;
use ssz::Decode;
use store::iter::AncestorIter;
use types::{
BeaconState, CommitteeIndex, Epoch, EthSpec, Hash256, RelativeEpoch, SignedBeaconBlock, Slot,
};
/// Parse a slot.
///
/// E.g., `"1234"`
pub fn parse_slot(string: &str) -> Result<Slot, ApiError> {
string
.parse::<u64>()
.map(Slot::from)
.map_err(|e| ApiError::BadRequest(format!("Unable to parse slot: {:?}", e)))
}
/// Parse an epoch.
///
/// E.g., `"13"`
pub fn parse_epoch(string: &str) -> Result<Epoch, ApiError> {
string
.parse::<u64>()
.map(Epoch::from)
.map_err(|e| ApiError::BadRequest(format!("Unable to parse epoch: {:?}", e)))
}
/// Parse a CommitteeIndex.
///
/// E.g., `"18"`
pub fn parse_committee_index(string: &str) -> Result<CommitteeIndex, ApiError> {
string
.parse::<CommitteeIndex>()
.map_err(|e| ApiError::BadRequest(format!("Unable to parse committee index: {:?}", e)))
}
/// Parse an SSZ object from some hex-encoded bytes.
///
/// E.g., A signature is `"0x0000000000000000000000000000000000000000000000000000000000000000"`
pub fn parse_hex_ssz_bytes<T: Decode>(string: &str) -> Result<T, ApiError> {
const PREFIX: &str = "0x";
if string.starts_with(PREFIX) {
let trimmed = string.trim_start_matches(PREFIX);
let bytes = hex::decode(trimmed)
.map_err(|e| ApiError::BadRequest(format!("Unable to parse SSZ hex: {:?}", e)))?;
T::from_ssz_bytes(&bytes)
.map_err(|e| ApiError::BadRequest(format!("Unable to parse SSZ bytes: {:?}", e)))
} else {
Err(ApiError::BadRequest(
"Hex bytes must have a 0x prefix".to_string(),
))
}
}
/// Parse a root from a `0x` prefixed string.
///
/// E.g., `"0x0000000000000000000000000000000000000000000000000000000000000000"`
pub fn parse_root(string: &str) -> Result<Hash256, ApiError> {
const PREFIX: &str = "0x";
if string.starts_with(PREFIX) {
let trimmed = string.trim_start_matches(PREFIX);
trimmed
.parse()
.map_err(|e| ApiError::BadRequest(format!("Unable to parse root: {:?}", e)))
} else {
Err(ApiError::BadRequest(
"Root must have a 0x prefix".to_string(),
))
}
}
/// Parse a PublicKey from a `0x` prefixed hex string
pub fn parse_pubkey_bytes(string: &str) -> Result<PublicKeyBytes, ApiError> {
const PREFIX: &str = "0x";
if string.starts_with(PREFIX) {
let pubkey_bytes = hex::decode(string.trim_start_matches(PREFIX))
.map_err(|e| ApiError::BadRequest(format!("Invalid hex string: {:?}", e)))?;
let pubkey = PublicKeyBytes::deserialize(pubkey_bytes.as_slice()).map_err(|e| {
ApiError::BadRequest(format!("Unable to deserialize public key: {:?}.", e))
})?;
Ok(pubkey)
} else {
Err(ApiError::BadRequest(
"Public key must have a 0x prefix".to_string(),
))
}
}
/// Returns the root of the `SignedBeaconBlock` in the canonical chain of `beacon_chain` at the given
/// `slot`, if possible.
///
/// May return a root for a previous slot, in the case of skip slots.
pub fn block_root_at_slot<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
target: Slot,
) -> Result<Option<Hash256>, ApiError> {
Ok(process_results(
beacon_chain.rev_iter_block_roots()?,
|iter| {
iter.take_while(|(_, slot)| *slot >= target)
.find(|(_, slot)| *slot == target)
.map(|(root, _)| root)
},
)?)
}
/// Returns a `BeaconState` and it's root in the canonical chain of `beacon_chain` at the given
/// `slot`, if possible.
///
/// Will not return a state if the request slot is in the future. Will return states higher than
/// the current head by skipping slots.
pub fn state_at_slot<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
slot: Slot,
) -> Result<(Hash256, BeaconState<T::EthSpec>), ApiError> {
let head = beacon_chain.head()?;
if head.beacon_state.slot == slot {
Ok((head.beacon_state_root, head.beacon_state))
} else {
let root = state_root_at_slot(beacon_chain, slot, StateSkipConfig::WithStateRoots)?;
let state: BeaconState<T::EthSpec> = beacon_chain
.store
.get_state(&root, Some(slot))?
.ok_or_else(|| ApiError::NotFound(format!("Unable to find state at root {}", root)))?;
Ok((root, state))
}
}
/// Returns the root of the `BeaconState` in the canonical chain of `beacon_chain` at the given
/// `slot`, if possible.
///
/// Will not return a state root if the request slot is in the future. Will return state roots
/// higher than the current head by skipping slots.
pub fn state_root_at_slot<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
slot: Slot,
config: StateSkipConfig,
) -> Result<Hash256, ApiError> {
let head_state = &beacon_chain.head()?.beacon_state;
let current_slot = beacon_chain
.slot()
.map_err(|_| ApiError::ServerError("Unable to read slot clock".to_string()))?;
// There are four scenarios when obtaining a state for a given slot:
//
// 1. The request slot is in the future.
// 2. The request slot is the same as the best block (head) slot.
// 3. The request slot is prior to the head slot.
// 4. The request slot is later than the head slot.
if current_slot < slot {
// 1. The request slot is in the future. Reject the request.
//
// We could actually speculate about future state roots by skipping slots, however that's
// likely to cause confusion for API users.
Err(ApiError::BadRequest(format!(
"Requested slot {} is past the current slot {}",
slot, current_slot
)))
} else if head_state.slot == slot {
// 2. The request slot is the same as the best block (head) slot.
//
// The head state root is stored in memory, return a reference.
Ok(beacon_chain.head()?.beacon_state_root)
} else if head_state.slot > slot {
// 3. The request slot is prior to the head slot.
//
// Iterate through the state roots on the head state to find the root for that
// slot. Once the root is found, load it from the database.
process_results(
head_state
.try_iter_ancestor_roots(beacon_chain.store.clone())
.ok_or_else(|| {
ApiError::ServerError("Failed to create roots iterator".to_string())
})?,
|mut iter| iter.find(|(_, s)| *s == slot).map(|(root, _)| root),
)?
.ok_or_else(|| ApiError::NotFound(format!("Unable to find state at slot {}", slot)))
} else {
// 4. The request slot is later than the head slot.
//
// Use `per_slot_processing` to advance the head state to the present slot,
// assuming that all slots do not contain a block (i.e., they are skipped slots).
let mut state = beacon_chain.head()?.beacon_state;
let spec = &T::EthSpec::default_spec();
let skip_state_root = match config {
StateSkipConfig::WithStateRoots => None,
StateSkipConfig::WithoutStateRoots => Some(Hash256::zero()),
};
for _ in state.slot.as_u64()..slot.as_u64() {
// Ensure the next epoch state caches are built in case of an epoch transition.
state.build_committee_cache(RelativeEpoch::Next, spec)?;
state_processing::per_slot_processing(&mut state, skip_state_root, spec)?;
}
// Note: this is an expensive operation. Once the tree hash cache is implement it may be
// used here.
Ok(state.canonical_root())
}
}
pub fn publish_beacon_block_to_network<T: BeaconChainTypes + 'static>(
chan: &NetworkChannel<T::EthSpec>,
block: SignedBeaconBlock<T::EthSpec>,
) -> Result<(), ApiError> {
// send the block via SSZ encoding
let messages = vec![PubsubMessage::BeaconBlock(Box::new(block))];
// Publish the block to the p2p network via gossipsub.
if let Err(e) = chan.send(NetworkMessage::Publish { messages }) {
return Err(ApiError::ServerError(format!(
"Unable to send new block to network: {:?}",
e
)));
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_root_works() {
assert_eq!(
parse_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
Ok(Hash256::zero())
);
assert_eq!(
parse_root("0x000000000000000000000000000000000000000000000000000000000000002a"),
Ok(Hash256::from_low_u64_be(42))
);
assert!(
parse_root("0000000000000000000000000000000000000000000000000000000000000042").is_err()
);
assert!(parse_root("0x").is_err());
assert!(parse_root("0x00").is_err());
}
#[test]
fn parse_slot_works() {
assert_eq!(parse_slot("0"), Ok(Slot::new(0)));
assert_eq!(parse_slot("42"), Ok(Slot::new(42)));
assert_eq!(parse_slot("10000000"), Ok(Slot::new(10_000_000)));
assert!(parse_slot("cats").is_err());
}
}

View File

@@ -1,127 +0,0 @@
#[macro_use]
extern crate lazy_static;
mod router;
extern crate network as client_network;
mod beacon;
pub mod config;
mod consensus;
mod helpers;
mod lighthouse;
mod metrics;
mod node;
mod url_query;
mod validator;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use bus::Bus;
use client_network::NetworkMessage;
pub use config::ApiEncodingFormat;
use eth2_config::Eth2Config;
use eth2_libp2p::NetworkGlobals;
use futures::future::TryFutureExt;
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Server};
use parking_lot::Mutex;
use rest_types::ApiError;
use slog::{info, warn};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc;
use types::SignedBeaconBlockHash;
use url_query::UrlQuery;
pub use crate::helpers::parse_pubkey_bytes;
pub use config::Config;
pub use router::Context;
pub type NetworkChannel<T> = mpsc::UnboundedSender<NetworkMessage<T>>;
pub struct NetworkInfo<T: BeaconChainTypes> {
pub network_globals: Arc<NetworkGlobals<T::EthSpec>>,
pub network_chan: NetworkChannel<T::EthSpec>,
}
// Allowing more than 7 arguments.
#[allow(clippy::too_many_arguments)]
pub fn start_server<T: BeaconChainTypes>(
executor: environment::TaskExecutor,
config: &Config,
beacon_chain: Arc<BeaconChain<T>>,
network_info: NetworkInfo<T>,
db_path: PathBuf,
freezer_db_path: PathBuf,
eth2_config: Eth2Config,
events: Arc<Mutex<Bus<SignedBeaconBlockHash>>>,
) -> Result<SocketAddr, hyper::Error> {
let log = executor.log();
let eth2_config = Arc::new(eth2_config);
let context = Arc::new(Context {
executor: executor.clone(),
config: config.clone(),
beacon_chain,
network_globals: network_info.network_globals.clone(),
network_chan: network_info.network_chan,
eth2_config,
log: log.clone(),
db_path,
freezer_db_path,
events,
});
// Define the function that will build the request handler.
let make_service = make_service_fn(move |_socket: &AddrStream| {
let ctx = context.clone();
async move {
Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
router::on_http_request(req, ctx.clone())
}))
}
});
let bind_addr = (config.listen_address, config.port).into();
let server = Server::bind(&bind_addr).serve(make_service);
// Determine the address the server is actually listening on.
//
// This may be different to `bind_addr` if bind port was 0 (this allows the OS to choose a free
// port).
let actual_listen_addr = server.local_addr();
// Build a channel to kill the HTTP server.
let exit = executor.exit();
let inner_log = log.clone();
let server_exit = async move {
let _ = exit.await;
info!(inner_log, "HTTP service shutdown");
};
// Configure the `hyper` server to gracefully shutdown when the shutdown channel is triggered.
let inner_log = log.clone();
let server_future = server
.with_graceful_shutdown(async {
server_exit.await;
})
.map_err(move |e| {
warn!(
inner_log,
"HTTP server failed to start, Unable to bind"; "address" => format!("{:?}", e)
)
})
.unwrap_or_else(|_| ());
info!(
log,
"HTTP API started";
"address" => format!("{}", actual_listen_addr.ip()),
"port" => actual_listen_addr.port(),
);
executor.spawn_without_exit(server_future, "http");
Ok(actual_listen_addr)
}

View File

@@ -1,48 +0,0 @@
//! This contains a collection of lighthouse specific HTTP endpoints.
use crate::{ApiError, Context};
use beacon_chain::BeaconChainTypes;
use eth2_libp2p::PeerInfo;
use serde::Serialize;
use std::sync::Arc;
use types::EthSpec;
/// Returns all known peers and corresponding information
pub fn peers<T: BeaconChainTypes>(ctx: Arc<Context<T>>) -> Result<Vec<Peer<T::EthSpec>>, ApiError> {
Ok(ctx
.network_globals
.peers
.read()
.peers()
.map(|(peer_id, peer_info)| Peer {
peer_id: peer_id.to_string(),
peer_info: peer_info.clone(),
})
.collect())
}
/// Returns all known connected peers and their corresponding information
pub fn connected_peers<T: BeaconChainTypes>(
ctx: Arc<Context<T>>,
) -> Result<Vec<Peer<T::EthSpec>>, ApiError> {
Ok(ctx
.network_globals
.peers
.read()
.connected_peers()
.map(|(peer_id, peer_info)| Peer {
peer_id: peer_id.to_string(),
peer_info: peer_info.clone(),
})
.collect())
}
/// Information returned by `peers` and `connected_peers`.
#[derive(Clone, Debug, Serialize)]
#[serde(bound = "T: EthSpec")]
pub struct Peer<T: EthSpec> {
/// The Peer's ID
peer_id: String,
/// The PeerInfo associated with the peer.
peer_info: PeerInfo<T>,
}

View File

@@ -1,39 +0,0 @@
use crate::{ApiError, Context};
use beacon_chain::BeaconChainTypes;
use eth2_libp2p::types::SyncState;
use rest_types::{SyncingResponse, SyncingStatus};
use std::sync::Arc;
use types::Slot;
/// Returns a syncing status.
pub fn syncing<T: BeaconChainTypes>(ctx: Arc<Context<T>>) -> Result<SyncingResponse, ApiError> {
let current_slot = ctx
.beacon_chain
.head_info()
.map_err(|e| ApiError::ServerError(format!("Unable to read head slot: {:?}", e)))?
.slot;
let (starting_slot, highest_slot) = match ctx.network_globals.sync_state() {
SyncState::SyncingFinalized {
start_slot,
head_slot,
..
}
| SyncState::SyncingHead {
start_slot,
head_slot,
} => (start_slot, head_slot),
SyncState::Synced | SyncState::Stalled => (Slot::from(0u64), current_slot),
};
let sync_status = SyncingStatus {
starting_slot,
current_slot,
highest_slot,
};
Ok(SyncingResponse {
is_syncing: ctx.network_globals.is_syncing(),
sync_status,
})
}

View File

@@ -1,322 +0,0 @@
use crate::{
beacon, config::Config, consensus, lighthouse, metrics, node, validator, NetworkChannel,
};
use beacon_chain::{BeaconChain, BeaconChainTypes};
use bus::Bus;
use environment::TaskExecutor;
use eth2_config::Eth2Config;
use eth2_libp2p::{NetworkGlobals, PeerId};
use hyper::header::HeaderValue;
use hyper::{Body, Method, Request, Response};
use lighthouse_version::version_with_platform;
use operation_pool::PersistedOperationPool;
use parking_lot::Mutex;
use rest_types::{ApiError, Handler, Health};
use slog::debug;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use types::{EthSpec, SignedBeaconBlockHash};
pub struct Context<T: BeaconChainTypes> {
pub executor: TaskExecutor,
pub config: Config,
pub beacon_chain: Arc<BeaconChain<T>>,
pub network_globals: Arc<NetworkGlobals<T::EthSpec>>,
pub network_chan: NetworkChannel<T::EthSpec>,
pub eth2_config: Arc<Eth2Config>,
pub log: slog::Logger,
pub db_path: PathBuf,
pub freezer_db_path: PathBuf,
pub events: Arc<Mutex<Bus<SignedBeaconBlockHash>>>,
}
pub async fn on_http_request<T: BeaconChainTypes>(
req: Request<Body>,
ctx: Arc<Context<T>>,
) -> Result<Response<Body>, ApiError> {
let path = req.uri().path().to_string();
let _timer = metrics::start_timer_vec(&metrics::BEACON_HTTP_API_TIMES_TOTAL, &[&path]);
metrics::inc_counter_vec(&metrics::BEACON_HTTP_API_REQUESTS_TOTAL, &[&path]);
let received_instant = Instant::now();
let log = ctx.log.clone();
let allow_origin = ctx.config.allow_origin.clone();
match route(req, ctx).await {
Ok(mut response) => {
metrics::inc_counter_vec(&metrics::BEACON_HTTP_API_SUCCESS_TOTAL, &[&path]);
if allow_origin != "" {
let headers = response.headers_mut();
headers.insert(
hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN,
HeaderValue::from_str(&allow_origin)?,
);
headers.insert(hyper::header::VARY, HeaderValue::from_static("Origin"));
}
debug!(
log,
"HTTP API request successful";
"path" => path,
"duration_ms" => Instant::now().duration_since(received_instant).as_millis()
);
Ok(response)
}
Err(error) => {
metrics::inc_counter_vec(&metrics::BEACON_HTTP_API_ERROR_TOTAL, &[&path]);
debug!(
log,
"HTTP API request failure";
"path" => path,
"duration_ms" => Instant::now().duration_since(received_instant).as_millis()
);
Ok(error.into())
}
}
}
async fn route<T: BeaconChainTypes>(
req: Request<Body>,
ctx: Arc<Context<T>>,
) -> Result<Response<Body>, ApiError> {
let path = req.uri().path().to_string();
let ctx = ctx.clone();
let method = req.method().clone();
let executor = ctx.executor.clone();
let handler = Handler::new(req, ctx, executor)?;
match (method, path.as_ref()) {
(Method::GET, "/node/version") => handler
.static_value(version_with_platform())
.await?
.serde_encodings(),
(Method::GET, "/node/health") => handler
.static_value(Health::observe().map_err(ApiError::ServerError)?)
.await?
.serde_encodings(),
(Method::GET, "/node/syncing") => handler
.allow_body()
.in_blocking_task(|_, ctx| node::syncing(ctx))
.await?
.serde_encodings(),
(Method::GET, "/network/enr") => handler
.in_core_task(|_, ctx| Ok(ctx.network_globals.local_enr().to_base64()))
.await?
.serde_encodings(),
(Method::GET, "/network/peer_count") => handler
.in_core_task(|_, ctx| Ok(ctx.network_globals.connected_peers()))
.await?
.serde_encodings(),
(Method::GET, "/network/peer_id") => handler
.in_core_task(|_, ctx| Ok(ctx.network_globals.local_peer_id().to_base58()))
.await?
.serde_encodings(),
(Method::GET, "/network/peers") => handler
.in_blocking_task(|_, ctx| {
Ok(ctx
.network_globals
.peers
.read()
.connected_peer_ids()
.map(PeerId::to_string)
.collect::<Vec<_>>())
})
.await?
.serde_encodings(),
(Method::GET, "/network/listen_port") => handler
.in_core_task(|_, ctx| Ok(ctx.network_globals.listen_port_tcp()))
.await?
.serde_encodings(),
(Method::GET, "/network/listen_addresses") => handler
.in_blocking_task(|_, ctx| Ok(ctx.network_globals.listen_multiaddrs()))
.await?
.serde_encodings(),
(Method::GET, "/beacon/head") => handler
.in_blocking_task(|_, ctx| beacon::get_head(ctx))
.await?
.all_encodings(),
(Method::GET, "/beacon/heads") => handler
.in_blocking_task(|_, ctx| Ok(beacon::get_heads(ctx)))
.await?
.all_encodings(),
(Method::GET, "/beacon/block") => handler
.in_blocking_task(beacon::get_block)
.await?
.all_encodings(),
(Method::GET, "/beacon/block_root") => handler
.in_blocking_task(beacon::get_block_root)
.await?
.all_encodings(),
(Method::GET, "/beacon/fork") => handler
.in_blocking_task(|_, ctx| Ok(ctx.beacon_chain.head_info()?.fork))
.await?
.all_encodings(),
(Method::GET, "/beacon/fork/stream") => {
handler.sse_stream(|_, ctx| beacon::stream_forks(ctx)).await
}
(Method::GET, "/beacon/genesis_time") => handler
.in_blocking_task(|_, ctx| Ok(ctx.beacon_chain.head_info()?.genesis_time))
.await?
.all_encodings(),
(Method::GET, "/beacon/genesis_validators_root") => handler
.in_blocking_task(|_, ctx| Ok(ctx.beacon_chain.head_info()?.genesis_validators_root))
.await?
.all_encodings(),
(Method::GET, "/beacon/validators") => handler
.in_blocking_task(beacon::get_validators)
.await?
.all_encodings(),
(Method::POST, "/beacon/validators") => handler
.allow_body()
.in_blocking_task(beacon::post_validators)
.await?
.all_encodings(),
(Method::GET, "/beacon/validators/all") => handler
.in_blocking_task(beacon::get_all_validators)
.await?
.all_encodings(),
(Method::GET, "/beacon/validators/active") => handler
.in_blocking_task(beacon::get_active_validators)
.await?
.all_encodings(),
(Method::GET, "/beacon/state") => handler
.in_blocking_task(beacon::get_state)
.await?
.all_encodings(),
(Method::GET, "/beacon/state_root") => handler
.in_blocking_task(beacon::get_state_root)
.await?
.all_encodings(),
(Method::GET, "/beacon/state/genesis") => handler
.in_blocking_task(|_, ctx| beacon::get_genesis_state(ctx))
.await?
.all_encodings(),
(Method::GET, "/beacon/committees") => handler
.in_blocking_task(beacon::get_committees)
.await?
.all_encodings(),
(Method::POST, "/beacon/proposer_slashing") => handler
.allow_body()
.in_blocking_task(beacon::proposer_slashing)
.await?
.serde_encodings(),
(Method::POST, "/beacon/attester_slashing") => handler
.allow_body()
.in_blocking_task(beacon::attester_slashing)
.await?
.serde_encodings(),
(Method::POST, "/validator/duties") => handler
.allow_body()
.in_blocking_task(validator::post_validator_duties)
.await?
.serde_encodings(),
(Method::POST, "/validator/subscribe") => handler
.allow_body()
.in_blocking_task(validator::post_validator_subscriptions)
.await?
.serde_encodings(),
(Method::GET, "/validator/duties/all") => handler
.in_blocking_task(validator::get_all_validator_duties)
.await?
.serde_encodings(),
(Method::GET, "/validator/duties/active") => handler
.in_blocking_task(validator::get_active_validator_duties)
.await?
.serde_encodings(),
(Method::GET, "/validator/block") => handler
.in_blocking_task(validator::get_new_beacon_block)
.await?
.serde_encodings(),
(Method::POST, "/validator/block") => handler
.allow_body()
.in_blocking_task(validator::publish_beacon_block)
.await?
.serde_encodings(),
(Method::GET, "/validator/attestation") => handler
.in_blocking_task(validator::get_new_attestation)
.await?
.serde_encodings(),
(Method::GET, "/validator/aggregate_attestation") => handler
.in_blocking_task(validator::get_aggregate_attestation)
.await?
.serde_encodings(),
(Method::POST, "/validator/attestations") => handler
.allow_body()
.in_blocking_task(validator::publish_attestations)
.await?
.serde_encodings(),
(Method::POST, "/validator/aggregate_and_proofs") => handler
.allow_body()
.in_blocking_task(validator::publish_aggregate_and_proofs)
.await?
.serde_encodings(),
(Method::GET, "/consensus/global_votes") => handler
.allow_body()
.in_blocking_task(consensus::get_vote_count)
.await?
.serde_encodings(),
(Method::POST, "/consensus/individual_votes") => handler
.allow_body()
.in_blocking_task(consensus::post_individual_votes)
.await?
.serde_encodings(),
(Method::GET, "/spec") => handler
// TODO: this clone is not ideal.
.in_blocking_task(|_, ctx| Ok(ctx.beacon_chain.spec.clone()))
.await?
.serde_encodings(),
(Method::GET, "/spec/slots_per_epoch") => handler
.static_value(T::EthSpec::slots_per_epoch())
.await?
.serde_encodings(),
(Method::GET, "/spec/eth2_config") => handler
// TODO: this clone is not ideal.
.in_blocking_task(|_, ctx| Ok(ctx.eth2_config.as_ref().clone()))
.await?
.serde_encodings(),
(Method::GET, "/advanced/fork_choice") => handler
.in_blocking_task(|_, ctx| {
Ok(ctx
.beacon_chain
.fork_choice
.read()
.proto_array()
.core_proto_array()
.clone())
})
.await?
.serde_encodings(),
(Method::GET, "/advanced/operation_pool") => handler
.in_blocking_task(|_, ctx| {
Ok(PersistedOperationPool::from_operation_pool(
&ctx.beacon_chain.op_pool,
))
})
.await?
.serde_encodings(),
(Method::GET, "/metrics") => handler
.in_blocking_task(|_, ctx| metrics::get_prometheus(ctx))
.await?
.text_encoding(),
(Method::GET, "/lighthouse/syncing") => handler
.in_blocking_task(|_, ctx| Ok(ctx.network_globals.sync_state()))
.await?
.serde_encodings(),
(Method::GET, "/lighthouse/peers") => handler
.in_blocking_task(|_, ctx| lighthouse::peers(ctx))
.await?
.serde_encodings(),
(Method::GET, "/lighthouse/connected_peers") => handler
.in_blocking_task(|_, ctx| lighthouse::connected_peers(ctx))
.await?
.serde_encodings(),
_ => Err(ApiError::NotFound(
"Request path and/or method not found.".to_owned(),
)),
}
}

View File

@@ -1,166 +0,0 @@
use crate::helpers::{parse_committee_index, parse_epoch, parse_hex_ssz_bytes, parse_slot};
use crate::ApiError;
use hyper::Request;
use types::{AttestationData, CommitteeIndex, Epoch, Signature, Slot};
/// Provides handy functions for parsing the query parameters of a URL.
#[derive(Clone, Copy)]
pub struct UrlQuery<'a>(url::form_urlencoded::Parse<'a>);
impl<'a> UrlQuery<'a> {
/// Instantiate from an existing `Request`.
///
/// Returns `Err` if `req` does not contain any query parameters.
pub fn from_request<T>(req: &'a Request<T>) -> Result<Self, ApiError> {
let query_str = req.uri().query().unwrap_or_else(|| "");
Ok(UrlQuery(url::form_urlencoded::parse(query_str.as_bytes())))
}
/// Returns the first `(key, value)` pair found where the `key` is in `keys`.
///
/// If no match is found, an `InvalidQueryParams` error is returned.
pub fn first_of(mut self, keys: &[&str]) -> Result<(String, String), ApiError> {
self.0
.find(|(key, _value)| keys.contains(&&**key))
.map(|(key, value)| (key.into_owned(), value.into_owned()))
.ok_or_else(|| {
ApiError::BadRequest(format!(
"URL query must be valid and contain at least one of the following keys: {:?}",
keys
))
})
}
/// Returns the first `(key, value)` pair found where the `key` is in `keys`, if any.
///
/// Returns `None` if no match is found.
pub fn first_of_opt(mut self, keys: &[&str]) -> Option<(String, String)> {
self.0
.find(|(key, _value)| keys.contains(&&**key))
.map(|(key, value)| (key.into_owned(), value.into_owned()))
}
/// Returns the value for `key`, if and only if `key` is the only key present in the query
/// parameters.
pub fn only_one(self, key: &str) -> Result<String, ApiError> {
let queries: Vec<_> = self
.0
.map(|(k, v)| (k.into_owned(), v.into_owned()))
.collect();
if queries.len() == 1 {
let (first_key, first_value) = &queries[0]; // Must have 0 index if len is 1.
if first_key == key {
Ok(first_value.to_string())
} else {
Err(ApiError::BadRequest(format!(
"Only the {} query parameter is supported",
key
)))
}
} else {
Err(ApiError::BadRequest(format!(
"Only one query parameter is allowed, {} supplied",
queries.len()
)))
}
}
/// Returns a vector of all values present where `key` is in `keys
///
/// If no match is found, an `InvalidQueryParams` error is returned.
pub fn all_of(self, key: &str) -> Result<Vec<String>, ApiError> {
let queries: Vec<_> = self
.0
.filter_map(|(k, v)| {
if k.eq(key) {
Some(v.into_owned())
} else {
None
}
})
.collect();
Ok(queries)
}
/// Returns the value of the first occurrence of the `epoch` key.
pub fn epoch(self) -> Result<Epoch, ApiError> {
self.first_of(&["epoch"])
.and_then(|(_key, value)| parse_epoch(&value))
}
/// Returns the value of the first occurrence of the `slot` key.
pub fn slot(self) -> Result<Slot, ApiError> {
self.first_of(&["slot"])
.and_then(|(_key, value)| parse_slot(&value))
}
/// Returns the value of the first occurrence of the `committee_index` key.
pub fn committee_index(self) -> Result<CommitteeIndex, ApiError> {
self.first_of(&["committee_index"])
.and_then(|(_key, value)| parse_committee_index(&value))
}
/// Returns the value of the first occurrence of the `randao_reveal` key.
pub fn randao_reveal(self) -> Result<Signature, ApiError> {
self.first_of(&["randao_reveal"])
.and_then(|(_key, value)| parse_hex_ssz_bytes(&value))
}
/// Returns the value of the first occurrence of the `attestation_data` key.
pub fn attestation_data(self) -> Result<AttestationData, ApiError> {
self.first_of(&["attestation_data"])
.and_then(|(_key, value)| parse_hex_ssz_bytes(&value))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn only_one() {
let get_result = |addr: &str, key: &str| -> Result<String, ApiError> {
UrlQuery(url::Url::parse(addr).unwrap().query_pairs()).only_one(key)
};
assert_eq!(get_result("http://cat.io/?a=42", "a"), Ok("42".to_string()));
assert!(get_result("http://cat.io/?a=42", "b").is_err());
assert!(get_result("http://cat.io/?a=42&b=12", "a").is_err());
assert!(get_result("http://cat.io/", "").is_err());
}
#[test]
fn first_of() {
let url = url::Url::parse("http://lighthouse.io/cats?a=42&b=12&c=100").unwrap();
let get_query = || UrlQuery(url.query_pairs());
assert_eq!(
get_query().first_of(&["a"]),
Ok(("a".to_string(), "42".to_string()))
);
assert_eq!(
get_query().first_of(&["a", "b", "c"]),
Ok(("a".to_string(), "42".to_string()))
);
assert_eq!(
get_query().first_of(&["a", "a", "a"]),
Ok(("a".to_string(), "42".to_string()))
);
assert_eq!(
get_query().first_of(&["a", "b", "c"]),
Ok(("a".to_string(), "42".to_string()))
);
assert_eq!(
get_query().first_of(&["b", "c"]),
Ok(("b".to_string(), "12".to_string()))
);
assert_eq!(
get_query().first_of(&["c"]),
Ok(("c".to_string(), "100".to_string()))
);
assert!(get_query().first_of(&["nothing"]).is_err());
}
}

View File

@@ -1,747 +0,0 @@
use crate::helpers::{parse_hex_ssz_bytes, publish_beacon_block_to_network};
use crate::{ApiError, Context, NetworkChannel, UrlQuery};
use beacon_chain::{
attestation_verification::Error as AttnError, BeaconChain, BeaconChainError, BeaconChainTypes,
BlockError, ForkChoiceError, StateSkipConfig,
};
use bls::PublicKeyBytes;
use eth2_libp2p::PubsubMessage;
use hyper::Request;
use network::NetworkMessage;
use rest_types::{ValidatorDutiesRequest, ValidatorDutyBytes, ValidatorSubscription};
use slog::{error, info, trace, warn, Logger};
use std::sync::Arc;
use types::beacon_state::EthSpec;
use types::{
Attestation, AttestationData, BeaconBlock, BeaconState, Epoch, RelativeEpoch, SelectionProof,
SignedAggregateAndProof, SignedBeaconBlock, SubnetId,
};
/// HTTP Handler to retrieve the duties for a set of validators during a particular epoch. This
/// method allows for collecting bulk sets of validator duties without risking exceeding the max
/// URL length with query pairs.
pub fn post_validator_duties<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorDutyBytes>, ApiError> {
let body = req.into_body();
serde_json::from_slice::<ValidatorDutiesRequest>(&body)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to parse JSON into ValidatorDutiesRequest: {:?}",
e
))
})
.and_then(|bulk_request| {
return_validator_duties(
&ctx.beacon_chain.clone(),
bulk_request.epoch,
bulk_request.pubkeys.into_iter().map(Into::into).collect(),
)
})
}
/// HTTP Handler to retrieve subscriptions for a set of validators. This allows the node to
/// organise peer discovery and topic subscription for known validators.
pub fn post_validator_subscriptions<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<(), ApiError> {
let body = req.into_body();
serde_json::from_slice(&body)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to parse JSON into ValidatorSubscriptions: {:?}",
e
))
})
.and_then(move |subscriptions: Vec<ValidatorSubscription>| {
ctx.network_chan
.send(NetworkMessage::Subscribe { subscriptions })
.map_err(|e| {
ApiError::ServerError(format!(
"Unable to subscriptions to the network: {:?}",
e
))
})?;
Ok(())
})
}
/// HTTP Handler to retrieve all validator duties for the given epoch.
pub fn get_all_validator_duties<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorDutyBytes>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let epoch = query.epoch()?;
let state = get_state_for_epoch(&ctx.beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let validator_pubkeys = state
.validators
.iter()
.map(|validator| validator.pubkey.clone())
.collect();
return_validator_duties(&ctx.beacon_chain, epoch, validator_pubkeys)
}
/// HTTP Handler to retrieve all active validator duties for the given epoch.
pub fn get_active_validator_duties<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Vec<ValidatorDutyBytes>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let epoch = query.epoch()?;
let state = get_state_for_epoch(&ctx.beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let validator_pubkeys = state
.validators
.iter()
.filter(|validator| validator.is_active_at(state.current_epoch()))
.map(|validator| validator.pubkey.clone())
.collect();
return_validator_duties(&ctx.beacon_chain, epoch, validator_pubkeys)
}
/// Helper function to return the state that can be used to determine the duties for some `epoch`.
pub fn get_state_for_epoch<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
epoch: Epoch,
config: StateSkipConfig,
) -> Result<BeaconState<T::EthSpec>, ApiError> {
let slots_per_epoch = T::EthSpec::slots_per_epoch();
let head = beacon_chain.head()?;
let current_epoch = beacon_chain.epoch()?;
let head_epoch = head.beacon_state.current_epoch();
if head_epoch == current_epoch && RelativeEpoch::from_epoch(current_epoch, epoch).is_ok() {
Ok(head.beacon_state)
} else {
// If epoch is ahead of current epoch, then it should be a "next epoch" request for
// attestation duties. So, go to the start slot of the epoch prior to that,
// which should be just the next wall-clock epoch.
let slot = if epoch > current_epoch {
(epoch - 1).start_slot(slots_per_epoch)
}
// Otherwise, go to the start of the request epoch.
else {
epoch.start_slot(slots_per_epoch)
};
beacon_chain.state_at_slot(slot, config).map_err(|e| {
ApiError::ServerError(format!("Unable to load state for epoch {}: {:?}", epoch, e))
})
}
}
/// Helper function to get the duties for some `validator_pubkeys` in some `epoch`.
fn return_validator_duties<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
epoch: Epoch,
validator_pubkeys: Vec<PublicKeyBytes>,
) -> Result<Vec<ValidatorDutyBytes>, ApiError> {
let mut state = get_state_for_epoch(&beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch)
.map_err(|_| ApiError::ServerError(String::from("Loaded state is in the wrong epoch")))?;
state
.build_committee_cache(relative_epoch, &beacon_chain.spec)
.map_err(|e| ApiError::ServerError(format!("Unable to build committee cache: {:?}", e)))?;
// Get a list of all validators for this epoch.
//
// Used for quickly determining the slot for a proposer.
let validator_proposers = if epoch == state.current_epoch() {
Some(
epoch
.slot_iter(T::EthSpec::slots_per_epoch())
.map(|slot| {
state
.get_beacon_proposer_index(slot, &beacon_chain.spec)
.map(|i| (i, slot))
.map_err(|e| {
ApiError::ServerError(format!(
"Unable to get proposer index for validator: {:?}",
e
))
})
})
.collect::<Result<Vec<_>, _>>()?,
)
} else {
None
};
validator_pubkeys
.into_iter()
.map(|validator_pubkey| {
// The `beacon_chain` can return a validator index that does not exist in all states.
// Therefore, we must check to ensure that the validator index is valid for our
// `state`.
let validator_index = beacon_chain
.validator_index(&validator_pubkey)
.map_err(|e| {
ApiError::ServerError(format!("Unable to get validator index: {:?}", e))
})?
.filter(|i| *i < state.validators.len());
if let Some(validator_index) = validator_index {
let duties = state
.get_attestation_duties(validator_index, relative_epoch)
.map_err(|e| {
ApiError::ServerError(format!(
"Unable to obtain attestation duties: {:?}",
e
))
})?;
let committee_count_at_slot = duties
.map(|d| state.get_committee_count_at_slot(d.slot))
.transpose()
.map_err(|e| {
ApiError::ServerError(format!(
"Unable to find committee count at slot: {:?}",
e
))
})?;
let aggregator_modulo = duties
.map(|duties| SelectionProof::modulo(duties.committee_len, &beacon_chain.spec))
.transpose()
.map_err(|e| {
ApiError::ServerError(format!("Unable to find modulo: {:?}", e))
})?;
let block_proposal_slots = validator_proposers.as_ref().map(|proposers| {
proposers
.iter()
.filter(|(i, _slot)| validator_index == *i)
.map(|(_i, slot)| *slot)
.collect()
});
Ok(ValidatorDutyBytes {
validator_pubkey,
validator_index: Some(validator_index as u64),
attestation_slot: duties.map(|d| d.slot),
attestation_committee_index: duties.map(|d| d.index),
committee_count_at_slot,
attestation_committee_position: duties.map(|d| d.committee_position),
block_proposal_slots,
aggregator_modulo,
})
} else {
Ok(ValidatorDutyBytes {
validator_pubkey,
validator_index: None,
attestation_slot: None,
attestation_committee_index: None,
attestation_committee_position: None,
block_proposal_slots: None,
committee_count_at_slot: None,
aggregator_modulo: None,
})
}
})
.collect::<Result<Vec<_>, ApiError>>()
}
/// HTTP Handler to produce a new BeaconBlock from the current state, ready to be signed by a validator.
pub fn get_new_beacon_block<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<BeaconBlock<T::EthSpec>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let slot = query.slot()?;
let randao_reveal = query.randao_reveal()?;
let validator_graffiti = if let Some((_key, value)) = query.first_of_opt(&["graffiti"]) {
Some(parse_hex_ssz_bytes(&value)?)
} else {
None
};
let (new_block, _state) = ctx
.beacon_chain
.produce_block(randao_reveal, slot, validator_graffiti)
.map_err(|e| {
error!(
ctx.log,
"Error whilst producing block";
"error" => format!("{:?}", e)
);
ApiError::ServerError(format!(
"Beacon node is not able to produce a block: {:?}",
e
))
})?;
Ok(new_block)
}
/// HTTP Handler to publish a SignedBeaconBlock, which has been signed by a validator.
pub fn publish_beacon_block<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<(), ApiError> {
let body = req.into_body();
serde_json::from_slice(&body).map_err(|e| {
ApiError::BadRequest(format!("Unable to parse JSON into SignedBeaconBlock: {:?}", e))
})
.and_then(move |block: SignedBeaconBlock<T::EthSpec>| {
let slot = block.slot();
match ctx.beacon_chain.process_block(block.clone()) {
Ok(block_root) => {
// Block was processed, publish via gossipsub
info!(
ctx.log,
"Block from local validator";
"block_root" => format!("{}", block_root),
"block_slot" => slot,
);
publish_beacon_block_to_network::<T>(&ctx.network_chan, block)?;
// Run the fork choice algorithm and enshrine a new canonical head, if
// found.
//
// The new head may or may not be the block we just received.
if let Err(e) = ctx.beacon_chain.fork_choice() {
error!(
ctx.log,
"Failed to find beacon chain head";
"error" => format!("{:?}", e)
);
} else {
// In the best case, validators should produce blocks that become the
// head.
//
// Potential reasons this may not be the case:
//
// - A quick re-org between block produce and publish.
// - Excessive time between block produce and publish.
// - A validator is using another beacon node to produce blocks and
// submitting them here.
if ctx.beacon_chain.head()?.beacon_block_root != block_root {
warn!(
ctx.log,
"Block from validator is not head";
"desc" => "potential re-org",
);
}
}
Ok(())
}
Err(BlockError::BeaconChainError(e)) => {
error!(
ctx.log,
"Error whilst processing block";
"error" => format!("{:?}", e)
);
Err(ApiError::ServerError(format!(
"Error while processing block: {:?}",
e
)))
}
Err(other) => {
warn!(
ctx.log,
"Invalid block from local validator";
"outcome" => format!("{:?}", other)
);
Err(ApiError::ProcessingError(format!(
"The SignedBeaconBlock could not be processed and has not been published: {:?}",
other
)))
}
}
})
}
/// HTTP Handler to produce a new Attestation from the current state, ready to be signed by a validator.
pub fn get_new_attestation<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Attestation<T::EthSpec>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let slot = query.slot()?;
let index = query.committee_index()?;
ctx.beacon_chain
.produce_unaggregated_attestation(slot, index)
.map_err(|e| ApiError::BadRequest(format!("Unable to produce attestation: {:?}", e)))
}
/// HTTP Handler to retrieve the aggregate attestation for a slot
pub fn get_aggregate_attestation<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<Attestation<T::EthSpec>, ApiError> {
let query = UrlQuery::from_request(&req)?;
let attestation_data = query.attestation_data()?;
match ctx
.beacon_chain
.get_aggregated_attestation(&attestation_data)
{
Ok(Some(attestation)) => Ok(attestation),
Ok(None) => Err(ApiError::NotFound(format!(
"No matching aggregate attestation for slot {:?} is known in slot {:?}",
attestation_data.slot,
ctx.beacon_chain.slot()
))),
Err(e) => Err(ApiError::ServerError(format!(
"Unable to obtain attestation: {:?}",
e
))),
}
}
/// HTTP Handler to publish a list of Attestations, which have been signed by a number of validators.
pub fn publish_attestations<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<(), ApiError> {
let bytes = req.into_body();
serde_json::from_slice(&bytes)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to deserialize JSON into a list of attestations: {:?}",
e
))
})
// Process all of the aggregates _without_ exiting early if one fails.
.map(
move |attestations: Vec<(Attestation<T::EthSpec>, SubnetId)>| {
attestations
.into_iter()
.enumerate()
.map(|(i, (attestation, subnet_id))| {
process_unaggregated_attestation(
&ctx.beacon_chain,
ctx.network_chan.clone(),
attestation,
subnet_id,
i,
&ctx.log,
)
})
.collect::<Vec<Result<_, _>>>()
},
)
// Iterate through all the results and return on the first `Err`.
//
// Note: this will only provide info about the _first_ failure, not all failures.
.and_then(|processing_results| processing_results.into_iter().try_for_each(|result| result))
.map(|_| ())
}
/// Processes an unaggregrated attestation that was included in a list of attestations with the
/// index `i`.
#[allow(clippy::redundant_clone)] // false positives in this function.
fn process_unaggregated_attestation<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
network_chan: NetworkChannel<T::EthSpec>,
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
i: usize,
log: &Logger,
) -> Result<(), ApiError> {
let data = &attestation.data.clone();
// Verify that the attestation is valid to included on the gossip network.
let verified_attestation = beacon_chain
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id)
.map_err(|e| {
handle_attestation_error(
e,
&format!("unaggregated attestation {} failed gossip verification", i),
data,
log,
)
})?;
// Publish the attestation to the network
if let Err(e) = network_chan.send(NetworkMessage::Publish {
messages: vec![PubsubMessage::Attestation(Box::new((
subnet_id,
attestation,
)))],
}) {
return Err(ApiError::ServerError(format!(
"Unable to send unaggregated attestation {} to network: {:?}",
i, e
)));
}
beacon_chain
.apply_attestation_to_fork_choice(&verified_attestation)
.map_err(|e| {
handle_fork_choice_error(
e,
&format!(
"unaggregated attestation {} was unable to be added to fork choice",
i
),
data,
log,
)
})?;
beacon_chain
.add_to_naive_aggregation_pool(verified_attestation)
.map_err(|e| {
handle_attestation_error(
e,
&format!(
"unaggregated attestation {} was unable to be added to aggregation pool",
i
),
data,
log,
)
})?;
Ok(())
}
/// HTTP Handler to publish an Attestation, which has been signed by a validator.
pub fn publish_aggregate_and_proofs<T: BeaconChainTypes>(
req: Request<Vec<u8>>,
ctx: Arc<Context<T>>,
) -> Result<(), ApiError> {
let body = req.into_body();
serde_json::from_slice(&body)
.map_err(|e| {
ApiError::BadRequest(format!(
"Unable to deserialize JSON into a list of SignedAggregateAndProof: {:?}",
e
))
})
// Process all of the aggregates _without_ exiting early if one fails.
.map(
move |signed_aggregates: Vec<SignedAggregateAndProof<T::EthSpec>>| {
signed_aggregates
.into_iter()
.enumerate()
.map(|(i, signed_aggregate)| {
process_aggregated_attestation(
&ctx.beacon_chain,
ctx.network_chan.clone(),
signed_aggregate,
i,
&ctx.log,
)
})
.collect::<Vec<Result<_, _>>>()
},
)
// Iterate through all the results and return on the first `Err`.
//
// Note: this will only provide info about the _first_ failure, not all failures.
.and_then(|processing_results| processing_results.into_iter().try_for_each(|result| result))
}
/// Processes an aggregrated attestation that was included in a list of attestations with the index
/// `i`.
#[allow(clippy::redundant_clone)] // false positives in this function.
fn process_aggregated_attestation<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
network_chan: NetworkChannel<T::EthSpec>,
signed_aggregate: SignedAggregateAndProof<T::EthSpec>,
i: usize,
log: &Logger,
) -> Result<(), ApiError> {
let data = &signed_aggregate.message.aggregate.data.clone();
// Verify that the attestation is valid to be included on the gossip network.
//
// Using this gossip check for local validators is not necessarily ideal, there will be some
// attestations that we reject that could possibly be included in a block (e.g., attestations
// that late by more than 1 epoch but less than 2). We can come pick this back up if we notice
// that it's materially affecting validator profits. Until then, I'm hesitant to introduce yet
// _another_ attestation verification path.
let verified_attestation =
match beacon_chain.verify_aggregated_attestation_for_gossip(signed_aggregate.clone()) {
Ok(verified_attestation) => verified_attestation,
Err(AttnError::AttestationAlreadyKnown(attestation_root)) => {
trace!(
log,
"Ignored known attn from local validator";
"attn_root" => format!("{}", attestation_root)
);
// Exit early with success for a known attestation, there's no need to re-process
// an aggregate we already know.
return Ok(());
}
/*
* It's worth noting that we don't check for `Error::AggregatorAlreadyKnown` since (at
* the time of writing) we check for `AttestationAlreadyKnown` first.
*
* Given this, it's impossible to hit `Error::AggregatorAlreadyKnown` without that
* aggregator having already produced a conflicting aggregation. This is not slashable
* but I think it's still the sort of condition we should error on, at least for now.
*/
Err(e) => {
return Err(handle_attestation_error(
e,
&format!("aggregated attestation {} failed gossip verification", i),
data,
log,
))
}
};
// Publish the attestation to the network
if let Err(e) = network_chan.send(NetworkMessage::Publish {
messages: vec![PubsubMessage::AggregateAndProofAttestation(Box::new(
signed_aggregate,
))],
}) {
return Err(ApiError::ServerError(format!(
"Unable to send aggregated attestation {} to network: {:?}",
i, e
)));
}
beacon_chain
.apply_attestation_to_fork_choice(&verified_attestation)
.map_err(|e| {
handle_fork_choice_error(
e,
&format!(
"aggregated attestation {} was unable to be added to fork choice",
i
),
data,
log,
)
})?;
beacon_chain
.add_to_block_inclusion_pool(verified_attestation)
.map_err(|e| {
handle_attestation_error(
e,
&format!(
"aggregated attestation {} was unable to be added to op pool",
i
),
data,
log,
)
})?;
Ok(())
}
/// Common handler for `AttnError` during attestation verification.
fn handle_attestation_error(
e: AttnError,
detail: &str,
data: &AttestationData,
log: &Logger,
) -> ApiError {
match e {
AttnError::BeaconChainError(e) => {
error!(
log,
"Internal error verifying local attestation";
"detail" => detail,
"error" => format!("{:?}", e),
"target" => data.target.epoch,
"source" => data.source.epoch,
"index" => data.index,
"slot" => data.slot,
);
ApiError::ServerError(format!(
"Internal error verifying local attestation. Error: {:?}. Detail: {}",
e, detail
))
}
e => {
error!(
log,
"Invalid local attestation";
"detail" => detail,
"reason" => format!("{:?}", e),
"target" => data.target.epoch,
"source" => data.source.epoch,
"index" => data.index,
"slot" => data.slot,
);
ApiError::ProcessingError(format!(
"Invalid local attestation. Error: {:?} Detail: {}",
e, detail
))
}
}
}
/// Common handler for `ForkChoiceError` during attestation verification.
fn handle_fork_choice_error(
e: BeaconChainError,
detail: &str,
data: &AttestationData,
log: &Logger,
) -> ApiError {
match e {
BeaconChainError::ForkChoiceError(ForkChoiceError::InvalidAttestation(e)) => {
error!(
log,
"Local attestation invalid for fork choice";
"detail" => detail,
"reason" => format!("{:?}", e),
"target" => data.target.epoch,
"source" => data.source.epoch,
"index" => data.index,
"slot" => data.slot,
);
ApiError::ProcessingError(format!(
"Invalid local attestation. Error: {:?} Detail: {}",
e, detail
))
}
e => {
error!(
log,
"Internal error applying attn to fork choice";
"detail" => detail,
"error" => format!("{:?}", e),
"target" => data.target.epoch,
"source" => data.source.epoch,
"index" => data.index,
"slot" => data.slot,
);
ApiError::ServerError(format!(
"Internal error verifying local attestation. Error: {:?}. Detail: {}",
e, detail
))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -142,7 +142,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.arg(
Arg::with_name("http")
.long("http")
.help("Enable RESTful HTTP API server. Disabled by default.")
.help("Enable the RESTful HTTP API server. Disabled by default.")
.takes_value(false),
)
.arg(
@@ -169,6 +169,38 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.default_value("")
.takes_value(true),
)
/* Prometheus metrics HTTP server related arguments */
.arg(
Arg::with_name("metrics")
.long("metrics")
.help("Enable the Prometheus metrics HTTP server. Disabled by default.")
.takes_value(false),
)
.arg(
Arg::with_name("metrics-address")
.long("metrics-address")
.value_name("ADDRESS")
.help("Set the listen address for the Prometheus metrics HTTP server.")
.default_value("127.0.0.1")
.takes_value(true),
)
.arg(
Arg::with_name("metrics-port")
.long("metrics-port")
.value_name("PORT")
.help("Set the listen TCP port for the Prometheus metrics HTTP server.")
.default_value("5054")
.takes_value(true),
)
.arg(
Arg::with_name("metrics-allow-origin")
.long("metrics-allow-origin")
.value_name("ORIGIN")
.help("Set the value of the Access-Control-Allow-Origin response HTTP header for the Prometheus metrics HTTP server. \
Use * to allow any origin (not recommended in production)")
.default_value("")
.takes_value(true),
)
/* Websocket related arguments */
.arg(
Arg::with_name("ws")

View File

@@ -87,26 +87,26 @@ pub fn get_config<E: EthSpec>(
*/
if cli_args.is_present("staking") {
client_config.rest_api.enabled = true;
client_config.http_api.enabled = true;
client_config.sync_eth1_chain = true;
}
/*
* Http server
* Http API server
*/
if cli_args.is_present("http") {
client_config.rest_api.enabled = true;
client_config.http_api.enabled = true;
}
if let Some(address) = cli_args.value_of("http-address") {
client_config.rest_api.listen_address = address
client_config.http_api.listen_addr = address
.parse::<Ipv4Addr>()
.map_err(|_| "http-address is not a valid IPv4 address.")?;
}
if let Some(port) = cli_args.value_of("http-port") {
client_config.rest_api.port = port
client_config.http_api.listen_port = port
.parse::<u16>()
.map_err(|_| "http-port is not a valid u16.")?;
}
@@ -117,7 +117,36 @@ pub fn get_config<E: EthSpec>(
hyper::header::HeaderValue::from_str(allow_origin)
.map_err(|_| "Invalid allow-origin value")?;
client_config.rest_api.allow_origin = allow_origin.to_string();
client_config.http_api.allow_origin = Some(allow_origin.to_string());
}
/*
* Prometheus metrics HTTP server
*/
if cli_args.is_present("metrics") {
client_config.http_metrics.enabled = true;
}
if let Some(address) = cli_args.value_of("metrics-address") {
client_config.http_metrics.listen_addr = address
.parse::<Ipv4Addr>()
.map_err(|_| "metrics-address is not a valid IPv4 address.")?;
}
if let Some(port) = cli_args.value_of("metrics-port") {
client_config.http_metrics.listen_port = port
.parse::<u16>()
.map_err(|_| "metrics-port is not a valid u16.")?;
}
if let Some(allow_origin) = cli_args.value_of("metrics-allow-origin") {
// Pre-validate the config value to give feedback to the user on node startup, instead of
// as late as when the first API response is produced.
hyper::header::HeaderValue::from_str(allow_origin)
.map_err(|_| "Invalid allow-origin value")?;
client_config.http_metrics.allow_origin = Some(allow_origin.to_string());
}
// Log a warning indicating an open HTTP server if it wasn't specified explicitly
@@ -125,7 +154,7 @@ pub fn get_config<E: EthSpec>(
if cli_args.is_present("staking") {
warn!(
log,
"Running HTTP server on port {}", client_config.rest_api.port
"Running HTTP server on port {}", client_config.http_api.listen_port
);
}
@@ -219,7 +248,8 @@ pub fn get_config<E: EthSpec>(
unused_port("tcp").map_err(|e| format!("Failed to get port for libp2p: {}", e))?;
client_config.network.discovery_port =
unused_port("udp").map_err(|e| format!("Failed to get port for discovery: {}", e))?;
client_config.rest_api.port = 0;
client_config.http_api.listen_port = 0;
client_config.http_metrics.listen_port = 0;
client_config.websocket_server.port = 0;
}
@@ -230,6 +260,11 @@ pub fn get_config<E: EthSpec>(
client_config.eth1.deposit_contract_address =
format!("{:?}", eth2_testnet_config.deposit_contract_address()?);
let spec_contract_address = format!("{:?}", spec.deposit_contract_address);
if client_config.eth1.deposit_contract_address != spec_contract_address {
return Err("Testnet contract address does not match spec".into());
}
client_config.eth1.deposit_contract_deploy_block =
eth2_testnet_config.deposit_contract_deploy_block;
client_config.eth1.lowest_cached_block_number =
@@ -265,7 +300,7 @@ pub fn get_config<E: EthSpec>(
};
let trimmed_graffiti_len = cmp::min(raw_graffiti.len(), GRAFFITI_BYTES_LEN);
client_config.graffiti[..trimmed_graffiti_len]
client_config.graffiti.0[..trimmed_graffiti_len]
.copy_from_slice(&raw_graffiti[..trimmed_graffiti_len]);
if let Some(max_skip_slots) = cli_args.value_of("max-skip-slots") {

View File

@@ -71,7 +71,6 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
context: RuntimeContext<E>,
mut client_config: ClientConfig,
) -> Result<Self, String> {
let http_eth2_config = context.eth2_config().clone();
let spec = context.eth2_config().spec.clone();
let client_config_1 = client_config.clone();
let client_genesis = client_config.genesis.clone();
@@ -118,26 +117,22 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
builder.no_eth1_backend()?
};
let (builder, events) = builder
let (builder, _events) = builder
.system_time_slot_clock()?
.tee_event_handler(client_config.websocket_server.clone())?;
// Inject the executor into the discv5 network config.
client_config.network.discv5_config.executor = Some(Box::new(executor));
let builder = builder
builder
.build_beacon_chain()?
.network(&client_config.network)
.await?
.notifier()?;
let builder = if client_config.rest_api.enabled {
builder.http_server(&client_config, &http_eth2_config, events)?
} else {
builder
};
Ok(Self(builder.build()))
.notifier()?
.http_api_config(client_config.http_api.clone())
.http_metrics_config(client_config.http_metrics.clone())
.build()
.map(Self)
}
pub fn into_inner(self) -> ProductionClient<E> {

View File

@@ -3,6 +3,7 @@
use beacon_chain::StateSkipConfig;
use node_test_rig::{
environment::{Environment, EnvironmentBuilder},
eth2::types::StateId,
testing_client_config, LocalBeaconNode,
};
use types::{EthSpec, MinimalEthSpec, Slot};
@@ -34,10 +35,12 @@ fn http_server_genesis_state() {
let node = build_node(&mut env);
let remote_node = node.remote_node().expect("should produce remote node");
let (api_state, _root) = env
let api_state = env
.runtime()
.block_on(remote_node.http.beacon().get_state_by_slot(Slot::new(0)))
.expect("should fetch state from http api");
.block_on(remote_node.get_debug_beacon_states(StateId::Slot(Slot::new(0))))
.expect("should fetch state from http api")
.unwrap()
.data;
let mut db_state = node
.client