mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-18 21:38:31 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user