mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -83,6 +83,10 @@ pub fn get_attestation_performance<T: BeaconChainTypes>(
|
||||
}
|
||||
|
||||
// Either use the global validator set, or the specified index.
|
||||
//
|
||||
// Does no further validation of the indices, so in the event an index has not yet been
|
||||
// activated or does not yet exist (according to the head state), it will return all fields as
|
||||
// `false`.
|
||||
let index_range = if target.to_lowercase() == "global" {
|
||||
chain
|
||||
.with_head(|head| Ok((0..head.beacon_state.validators().len() as u64).collect()))
|
||||
|
||||
@@ -56,15 +56,19 @@ fn cached_attestation_duties<T: BeaconChainTypes>(
|
||||
request_indices: &[u64],
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
let head = chain
|
||||
.head_info()
|
||||
let head_block_root = chain.canonical_head.cached_head().head_block_root();
|
||||
|
||||
let (duties, dependent_root, execution_status) = chain
|
||||
.validator_attestation_duties(request_indices, request_epoch, head_block_root)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
let (duties, dependent_root) = chain
|
||||
.validator_attestation_duties(request_indices, request_epoch, head.block_root)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
convert_to_api_response(duties, request_indices, dependent_root, chain)
|
||||
convert_to_api_response(
|
||||
duties,
|
||||
request_indices,
|
||||
dependent_root,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
chain,
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute some attester duties by reading a `BeaconState` from disk, completely ignoring the
|
||||
@@ -76,31 +80,41 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
// If the head is quite old then it might still be relevant for a historical request.
|
||||
//
|
||||
// Use the `with_head` function to read & clone in a single call to avoid race conditions.
|
||||
let state_opt = chain
|
||||
.with_head(|head| {
|
||||
if head.beacon_state.current_epoch() <= request_epoch {
|
||||
Ok(Some((head.beacon_state_root(), head.beacon_state.clone())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
// Avoid holding the `cached_head` longer than necessary.
|
||||
let state_opt = {
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let head = &cached_head.snapshot;
|
||||
|
||||
let mut state = if let Some((state_root, mut state)) = state_opt {
|
||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||
// suitable epoch.
|
||||
ensure_state_knows_attester_duties_for_epoch(
|
||||
&mut state,
|
||||
state_root,
|
||||
request_epoch,
|
||||
&chain.spec,
|
||||
)?;
|
||||
state
|
||||
} else {
|
||||
StateId::slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
||||
if head.beacon_state.current_epoch() <= request_epoch {
|
||||
Some((
|
||||
head.beacon_state_root(),
|
||||
head.beacon_state.clone(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let (mut state, execution_optimistic) =
|
||||
if let Some((state_root, mut state, execution_optimistic)) = state_opt {
|
||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||
// suitable epoch.
|
||||
ensure_state_knows_attester_duties_for_epoch(
|
||||
&mut state,
|
||||
state_root,
|
||||
request_epoch,
|
||||
&chain.spec,
|
||||
)?;
|
||||
(state, execution_optimistic)
|
||||
} else {
|
||||
StateId::from_slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch()))
|
||||
.state(chain)?
|
||||
};
|
||||
|
||||
// Sanity-check the state lookup.
|
||||
if !(state.current_epoch() == request_epoch || state.current_epoch() + 1 == request_epoch) {
|
||||
return Err(warp_utils::reject::custom_server_error(format!(
|
||||
@@ -136,7 +150,13 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
convert_to_api_response(duties, request_indices, dependent_root, chain)
|
||||
convert_to_api_response(
|
||||
duties,
|
||||
request_indices,
|
||||
dependent_root,
|
||||
execution_optimistic,
|
||||
chain,
|
||||
)
|
||||
}
|
||||
|
||||
fn ensure_state_knows_attester_duties_for_epoch<E: EthSpec>(
|
||||
@@ -174,6 +194,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
|
||||
duties: Vec<Option<AttestationDuty>>,
|
||||
indices: &[u64],
|
||||
dependent_root: Hash256,
|
||||
execution_optimistic: bool,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
// Protect against an inconsistent slot clock.
|
||||
@@ -209,6 +230,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
|
||||
|
||||
Ok(api_types::DutiesResponse {
|
||||
dependent_root,
|
||||
execution_optimistic: Some(execution_optimistic),
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes, WhenSlotSkipped};
|
||||
use crate::{state_id::checkpoint_slot_and_execution_optimistic, ExecutionOptimistic};
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
|
||||
use eth2::types::BlockId as CoreBlockId;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use types::{BlindedPayload, Hash256, SignedBeaconBlock, Slot};
|
||||
use std::sync::Arc;
|
||||
use types::{Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot};
|
||||
|
||||
/// Wraps `eth2::types::BlockId` and provides a simple way to obtain a block or root for a given
|
||||
/// `BlockId`.
|
||||
@@ -21,33 +24,78 @@ impl BlockId {
|
||||
pub fn root<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Hash256, warp::Rejection> {
|
||||
) -> Result<(Hash256, ExecutionOptimistic), 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, WhenSlotSkipped::None)
|
||||
.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),
|
||||
CoreBlockId::Head => {
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
Ok((
|
||||
cached_head.head_block_root(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
))
|
||||
}
|
||||
CoreBlockId::Genesis => Ok((chain.genesis_block_root, false)),
|
||||
CoreBlockId::Finalized => {
|
||||
let finalized_checkpoint =
|
||||
chain.canonical_head.cached_head().finalized_checkpoint();
|
||||
let (_slot, execution_optimistic) =
|
||||
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?;
|
||||
Ok((finalized_checkpoint.root, execution_optimistic))
|
||||
}
|
||||
CoreBlockId::Justified => {
|
||||
let justified_checkpoint =
|
||||
chain.canonical_head.cached_head().justified_checkpoint();
|
||||
let (_slot, execution_optimistic) =
|
||||
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?;
|
||||
Ok((justified_checkpoint.root, execution_optimistic))
|
||||
}
|
||||
CoreBlockId::Slot(slot) => {
|
||||
let execution_optimistic = chain
|
||||
.is_optimistic_or_invalid_head()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let root = chain
|
||||
.block_root_at_slot(*slot, WhenSlotSkipped::None)
|
||||
.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
|
||||
))
|
||||
})
|
||||
})?;
|
||||
Ok((root, execution_optimistic))
|
||||
}
|
||||
CoreBlockId::Root(root) => {
|
||||
// This matches the behaviour of other consensus clients (e.g. Teku).
|
||||
if root == &Hash256::zero() {
|
||||
return Err(warp_utils::reject::custom_not_found(format!(
|
||||
"beacon block with root {}",
|
||||
root
|
||||
)));
|
||||
};
|
||||
if chain
|
||||
.store
|
||||
.block_exists(root)
|
||||
.map_err(BeaconChainError::DBError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||
{
|
||||
let execution_optimistic = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_or_invalid_block(root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
Ok((*root, execution_optimistic))
|
||||
} else {
|
||||
Err(warp_utils::reject::custom_not_found(format!(
|
||||
"beacon block with root {}",
|
||||
root
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +103,20 @@ impl BlockId {
|
||||
pub fn blinded_block<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>, warp::Rejection> {
|
||||
) -> Result<(SignedBlindedBeaconBlock<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
|
||||
match &self.0 {
|
||||
CoreBlockId::Head => chain
|
||||
.head_beacon_block()
|
||||
.map(Into::into)
|
||||
.map_err(warp_utils::reject::beacon_chain_error),
|
||||
CoreBlockId::Head => {
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
Ok((
|
||||
cached_head.snapshot.beacon_block.clone_as_blinded(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
))
|
||||
}
|
||||
CoreBlockId::Slot(slot) => {
|
||||
let root = self.root(chain)?;
|
||||
let (root, execution_optimistic) = self.root(chain)?;
|
||||
chain
|
||||
.get_blinded_block(&root)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)
|
||||
@@ -74,7 +128,7 @@ impl BlockId {
|
||||
slot
|
||||
)));
|
||||
}
|
||||
Ok(block)
|
||||
Ok((block, execution_optimistic))
|
||||
}
|
||||
None => Err(warp_utils::reject::custom_not_found(format!(
|
||||
"beacon block with root {}",
|
||||
@@ -83,8 +137,8 @@ impl BlockId {
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let root = self.root(chain)?;
|
||||
chain
|
||||
let (root, execution_optimistic) = self.root(chain)?;
|
||||
let block = chain
|
||||
.get_blinded_block(&root)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)
|
||||
.and_then(|root_opt| {
|
||||
@@ -94,7 +148,8 @@ impl BlockId {
|
||||
root
|
||||
))
|
||||
})
|
||||
})
|
||||
})?;
|
||||
Ok((block, execution_optimistic))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,13 +158,20 @@ impl BlockId {
|
||||
pub async fn full_block<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<SignedBeaconBlock<T::EthSpec>, warp::Rejection> {
|
||||
) -> Result<(Arc<SignedBeaconBlock<T::EthSpec>>, ExecutionOptimistic), warp::Rejection> {
|
||||
match &self.0 {
|
||||
CoreBlockId::Head => chain
|
||||
.head_beacon_block()
|
||||
.map_err(warp_utils::reject::beacon_chain_error),
|
||||
CoreBlockId::Head => {
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
Ok((
|
||||
cached_head.snapshot.beacon_block.clone(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
))
|
||||
}
|
||||
CoreBlockId::Slot(slot) => {
|
||||
let root = self.root(chain)?;
|
||||
let (root, execution_optimistic) = self.root(chain)?;
|
||||
chain
|
||||
.get_block(&root)
|
||||
.await
|
||||
@@ -122,7 +184,7 @@ impl BlockId {
|
||||
slot
|
||||
)));
|
||||
}
|
||||
Ok(block)
|
||||
Ok((Arc::new(block), execution_optimistic))
|
||||
}
|
||||
None => Err(warp_utils::reject::custom_not_found(format!(
|
||||
"beacon block with root {}",
|
||||
@@ -131,18 +193,20 @@ impl BlockId {
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let root = self.root(chain)?;
|
||||
let (root, execution_optimistic) = self.root(chain)?;
|
||||
chain
|
||||
.get_block(&root)
|
||||
.await
|
||||
.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
|
||||
))
|
||||
})
|
||||
.and_then(|block_opt| {
|
||||
block_opt
|
||||
.map(|block| (Arc::new(block), execution_optimistic))
|
||||
.ok_or_else(|| {
|
||||
warp_utils::reject::custom_not_found(format!(
|
||||
"beacon block with root {}",
|
||||
root
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -156,3 +220,9 @@ impl FromStr for BlockId {
|
||||
CoreBlockId::from_str(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
|
||||
use eth2::lighthouse::{BlockReward, BlockRewardsQuery};
|
||||
use slog::{warn, Logger};
|
||||
use lru::LruCache;
|
||||
use slog::{debug, warn, Logger};
|
||||
use state_processing::BlockReplayer;
|
||||
use std::sync::Arc;
|
||||
use warp_utils::reject::{beacon_chain_error, beacon_state_error, custom_bad_request};
|
||||
use types::BeaconBlock;
|
||||
use warp_utils::reject::{
|
||||
beacon_chain_error, beacon_state_error, custom_bad_request, custom_server_error,
|
||||
};
|
||||
|
||||
const STATE_CACHE_SIZE: usize = 2;
|
||||
|
||||
/// Fetch block rewards for blocks from the canonical chain.
|
||||
pub fn get_block_rewards<T: BeaconChainTypes>(
|
||||
query: BlockRewardsQuery,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
@@ -45,13 +52,21 @@ pub fn get_block_rewards<T: BeaconChainTypes>(
|
||||
.build_all_caches(&chain.spec)
|
||||
.map_err(beacon_state_error)?;
|
||||
|
||||
let mut reward_cache = Default::default();
|
||||
let mut block_rewards = Vec::with_capacity(blocks.len());
|
||||
|
||||
let block_replayer = BlockReplayer::new(state, &chain.spec)
|
||||
.pre_block_hook(Box::new(|state, block| {
|
||||
state.build_all_committee_caches(&chain.spec)?;
|
||||
|
||||
// Compute block reward.
|
||||
let block_reward =
|
||||
chain.compute_block_reward(block.message(), block.canonical_root(), state)?;
|
||||
let block_reward = chain.compute_block_reward(
|
||||
block.message(),
|
||||
block.canonical_root(),
|
||||
state,
|
||||
&mut reward_cache,
|
||||
query.include_attestations,
|
||||
)?;
|
||||
block_rewards.push(block_reward);
|
||||
Ok(())
|
||||
}))
|
||||
@@ -78,3 +93,96 @@ pub fn get_block_rewards<T: BeaconChainTypes>(
|
||||
|
||||
Ok(block_rewards)
|
||||
}
|
||||
|
||||
/// Compute block rewards for blocks passed in as input.
|
||||
pub fn compute_block_rewards<T: BeaconChainTypes>(
|
||||
blocks: Vec<BeaconBlock<T::EthSpec>>,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
log: Logger,
|
||||
) -> Result<Vec<BlockReward>, warp::Rejection> {
|
||||
let mut block_rewards = Vec::with_capacity(blocks.len());
|
||||
let mut state_cache = LruCache::new(STATE_CACHE_SIZE);
|
||||
let mut reward_cache = Default::default();
|
||||
|
||||
for block in blocks {
|
||||
let parent_root = block.parent_root();
|
||||
|
||||
// Check LRU cache for a constructed state from a previous iteration.
|
||||
let state = if let Some(state) = state_cache.get(&(parent_root, block.slot())) {
|
||||
debug!(
|
||||
log,
|
||||
"Re-using cached state for block rewards";
|
||||
"parent_root" => ?parent_root,
|
||||
"slot" => block.slot(),
|
||||
);
|
||||
state
|
||||
} else {
|
||||
debug!(
|
||||
log,
|
||||
"Fetching state for block rewards";
|
||||
"parent_root" => ?parent_root,
|
||||
"slot" => block.slot()
|
||||
);
|
||||
let parent_block = chain
|
||||
.get_blinded_block(&parent_root)
|
||||
.map_err(beacon_chain_error)?
|
||||
.ok_or_else(|| {
|
||||
custom_bad_request(format!(
|
||||
"parent block not known or not canonical: {:?}",
|
||||
parent_root
|
||||
))
|
||||
})?;
|
||||
|
||||
let parent_state = chain
|
||||
.get_state(&parent_block.state_root(), Some(parent_block.slot()))
|
||||
.map_err(beacon_chain_error)?
|
||||
.ok_or_else(|| {
|
||||
custom_bad_request(format!(
|
||||
"no state known for parent block: {:?}",
|
||||
parent_root
|
||||
))
|
||||
})?;
|
||||
|
||||
let block_replayer = BlockReplayer::new(parent_state, &chain.spec)
|
||||
.no_signature_verification()
|
||||
.state_root_iter([Ok((parent_block.state_root(), parent_block.slot()))].into_iter())
|
||||
.minimal_block_root_verification()
|
||||
.apply_blocks(vec![], Some(block.slot()))
|
||||
.map_err(beacon_chain_error)?;
|
||||
|
||||
if block_replayer.state_root_miss() {
|
||||
warn!(
|
||||
log,
|
||||
"Block reward state root miss";
|
||||
"parent_slot" => parent_block.slot(),
|
||||
"slot" => block.slot(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut state = block_replayer.into_state();
|
||||
state
|
||||
.build_all_committee_caches(&chain.spec)
|
||||
.map_err(beacon_state_error)?;
|
||||
|
||||
state_cache
|
||||
.get_or_insert((parent_root, block.slot()), || state)
|
||||
.ok_or_else(|| {
|
||||
custom_server_error("LRU cache insert should always succeed".into())
|
||||
})?
|
||||
};
|
||||
|
||||
// Compute block reward.
|
||||
let block_reward = chain
|
||||
.compute_block_reward(
|
||||
block.to_ref(),
|
||||
block.canonical_root(),
|
||||
state,
|
||||
&mut reward_cache,
|
||||
true,
|
||||
)
|
||||
.map_err(beacon_chain_error)?;
|
||||
block_rewards.push(block_reward);
|
||||
}
|
||||
|
||||
Ok(block_rewards)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn info<T: BeaconChainTypes>(
|
||||
|
||||
pub fn historical_blocks<T: BeaconChainTypes>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
blocks: Vec<SignedBlindedBeaconBlock<T::EthSpec>>,
|
||||
blocks: Vec<Arc<SignedBlindedBeaconBlock<T::EthSpec>>>,
|
||||
) -> Result<AnchorInfo, warp::Rejection> {
|
||||
chain
|
||||
.import_historical_block_batch(blocks)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,10 +55,16 @@ pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
.safe_add(1)
|
||||
.map_err(warp_utils::reject::arith_error)?
|
||||
{
|
||||
let (proposers, dependent_root, _) =
|
||||
let (proposers, dependent_root, execution_status, _fork) =
|
||||
compute_proposer_duties_from_head(request_epoch, chain)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
convert_to_api_response(chain, request_epoch, dependent_root, proposers)
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
request_epoch,
|
||||
dependent_root,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
proposers,
|
||||
)
|
||||
} else if request_epoch
|
||||
> current_epoch
|
||||
.safe_add(1)
|
||||
@@ -88,16 +94,24 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
request_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Option<ApiDuties>, warp::reject::Rejection> {
|
||||
let head = chain
|
||||
.head_info()
|
||||
let head = chain.canonical_head.cached_head();
|
||||
let head_block = &head.snapshot.beacon_block;
|
||||
let head_block_root = head.head_block_root();
|
||||
let head_decision_root = head
|
||||
.snapshot
|
||||
.beacon_state
|
||||
.proposer_shuffling_decision_root(head_block_root)
|
||||
.map_err(warp_utils::reject::beacon_state_error)?;
|
||||
let head_epoch = head_block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
let execution_optimistic = chain
|
||||
.is_optimistic_or_invalid_head_block(head_block)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let head_epoch = head.slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
|
||||
let dependent_root = match head_epoch.cmp(&request_epoch) {
|
||||
// head_epoch == request_epoch
|
||||
Ordering::Equal => head.proposer_shuffling_decision_root,
|
||||
Ordering::Equal => head_decision_root,
|
||||
// head_epoch < request_epoch
|
||||
Ordering::Less => head.block_root,
|
||||
Ordering::Less => head_block_root,
|
||||
// head_epoch > request_epoch
|
||||
Ordering::Greater => {
|
||||
return Err(warp_utils::reject::custom_server_error(format!(
|
||||
@@ -113,7 +127,13 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
.get_epoch::<T::EthSpec>(dependent_root, request_epoch)
|
||||
.cloned()
|
||||
.map(|indices| {
|
||||
convert_to_api_response(chain, request_epoch, dependent_root, indices.to_vec())
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
request_epoch,
|
||||
dependent_root,
|
||||
execution_optimistic,
|
||||
indices.to_vec(),
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
@@ -132,8 +152,9 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
|
||||
current_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
let (indices, dependent_root, fork) = compute_proposer_duties_from_head(current_epoch, chain)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let (indices, dependent_root, execution_status, fork) =
|
||||
compute_proposer_duties_from_head(current_epoch, chain)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
// Prime the proposer shuffling cache with the newly-learned value.
|
||||
chain
|
||||
@@ -143,7 +164,13 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
convert_to_api_response(chain, current_epoch, dependent_root, indices)
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
current_epoch,
|
||||
dependent_root,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
indices,
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute some proposer duties by reading a `BeaconState` from disk, completely ignoring the
|
||||
@@ -154,27 +181,36 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
// If the head is quite old then it might still be relevant for a historical request.
|
||||
//
|
||||
// Use the `with_head` function to read & clone in a single call to avoid race conditions.
|
||||
let state_opt = chain
|
||||
.with_head(|head| {
|
||||
if head.beacon_state.current_epoch() <= epoch {
|
||||
Ok(Some((head.beacon_state_root(), head.beacon_state.clone())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
let state = if let Some((state_root, mut state)) = state_opt {
|
||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||
// suitable epoch.
|
||||
ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec)
|
||||
// Avoid holding the `cached_head` longer than necessary.
|
||||
let state_opt = {
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
state
|
||||
} else {
|
||||
StateId::slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
||||
let head = &cached_head.snapshot;
|
||||
|
||||
if head.beacon_state.current_epoch() <= epoch {
|
||||
Some((
|
||||
head.beacon_state_root(),
|
||||
head.beacon_state.clone(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let (state, execution_optimistic) =
|
||||
if let Some((state_root, mut state, execution_optimistic)) = state_opt {
|
||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||
// suitable epoch.
|
||||
ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
(state, execution_optimistic)
|
||||
} else {
|
||||
StateId::from_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
||||
};
|
||||
|
||||
// Ensure the state lookup was correct.
|
||||
if state.current_epoch() != epoch {
|
||||
return Err(warp_utils::reject::custom_server_error(format!(
|
||||
@@ -196,7 +232,7 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
convert_to_api_response(chain, epoch, dependent_root, indices)
|
||||
convert_to_api_response(chain, epoch, dependent_root, execution_optimistic, indices)
|
||||
}
|
||||
|
||||
/// Converts the internal representation of proposer duties into one that is compatible with the
|
||||
@@ -205,6 +241,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
epoch: Epoch,
|
||||
dependent_root: Hash256,
|
||||
execution_optimistic: bool,
|
||||
indices: Vec<usize>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
let index_to_pubkey_map = chain
|
||||
@@ -239,6 +276,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
|
||||
} else {
|
||||
Ok(api_types::DutiesResponse {
|
||||
dependent_root,
|
||||
execution_optimistic: Some(execution_optimistic),
|
||||
data: proposer_data,
|
||||
})
|
||||
}
|
||||
|
||||
176
beacon_node/http_api/src/publish_blocks.rs
Normal file
176
beacon_node/http_api/src/publish_blocks.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use crate::metrics;
|
||||
use beacon_chain::validator_monitor::{get_block_delay_ms, timestamp_now};
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockError, CountUnrealized};
|
||||
use lighthouse_network::PubsubMessage;
|
||||
use network::NetworkMessage;
|
||||
use slog::{crit, error, info, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
BlindedPayload, ExecPayload, ExecutionBlockHash, ExecutionPayload, FullPayload,
|
||||
SignedBeaconBlock,
|
||||
};
|
||||
use warp::Rejection;
|
||||
|
||||
/// Handles a request from the HTTP API for full blocks.
|
||||
pub async fn publish_block<T: BeaconChainTypes>(
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: &UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger,
|
||||
) -> Result<(), Rejection> {
|
||||
let seen_timestamp = timestamp_now();
|
||||
|
||||
// Send the block, regardless of whether or not it is valid. The API
|
||||
// specification is very clear that this is the desired behaviour.
|
||||
crate::publish_pubsub_message(network_tx, PubsubMessage::BeaconBlock(block.clone()))?;
|
||||
|
||||
// Determine the delay after the start of the slot, register it with metrics.
|
||||
let delay = get_block_delay_ms(seen_timestamp, block.message(), &chain.slot_clock);
|
||||
metrics::observe_duration(&metrics::HTTP_API_BLOCK_BROADCAST_DELAY_TIMES, delay);
|
||||
|
||||
match chain
|
||||
.process_block(block.clone(), CountUnrealized::True)
|
||||
.await
|
||||
{
|
||||
Ok(root) => {
|
||||
info!(
|
||||
log,
|
||||
"Valid block from HTTP API";
|
||||
"block_delay" => ?delay,
|
||||
"root" => format!("{}", root),
|
||||
"proposer_index" => block.message().proposer_index(),
|
||||
"slot" => block.slot(),
|
||||
);
|
||||
|
||||
// Notify the validator monitor.
|
||||
chain.validator_monitor.read().register_api_block(
|
||||
seen_timestamp,
|
||||
block.message(),
|
||||
root,
|
||||
&chain.slot_clock,
|
||||
);
|
||||
|
||||
// Update the head since it's likely this block will become the new
|
||||
// head.
|
||||
chain.recompute_head_at_current_slot().await;
|
||||
|
||||
// Perform some logging to inform users if their blocks are being produced
|
||||
// late.
|
||||
//
|
||||
// Check to see the thresholds are non-zero to avoid logging errors with small
|
||||
// slot times (e.g., during testing)
|
||||
let crit_threshold = chain.slot_clock.unagg_attestation_production_delay();
|
||||
let error_threshold = crit_threshold / 2;
|
||||
if delay >= crit_threshold {
|
||||
crit!(
|
||||
log,
|
||||
"Block was broadcast too late";
|
||||
"msg" => "system may be overloaded, block likely to be orphaned",
|
||||
"delay_ms" => delay.as_millis(),
|
||||
"slot" => block.slot(),
|
||||
"root" => ?root,
|
||||
)
|
||||
} else if delay >= error_threshold {
|
||||
error!(
|
||||
log,
|
||||
"Block broadcast was delayed";
|
||||
"msg" => "system may be overloaded, block may be orphaned",
|
||||
"delay_ms" => delay.as_millis(),
|
||||
"slot" => block.slot(),
|
||||
"root" => ?root,
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(BlockError::BlockIsAlreadyKnown) => {
|
||||
info!(
|
||||
log,
|
||||
"Block from HTTP API already known";
|
||||
"block" => ?block.canonical_root(),
|
||||
"slot" => block.slot(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(BlockError::RepeatProposal { proposer, slot }) => {
|
||||
warn!(
|
||||
log,
|
||||
"Block ignored due to repeat proposal";
|
||||
"msg" => "this can happen when a VC uses fallback BNs. \
|
||||
whilst this is not necessarily an error, it can indicate issues with a BN \
|
||||
or between the VC and BN.",
|
||||
"slot" => slot,
|
||||
"proposer" => proposer,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("{:?}", e);
|
||||
error!(
|
||||
log,
|
||||
"Invalid block provided to HTTP API";
|
||||
"reason" => &msg
|
||||
);
|
||||
Err(warp_utils::reject::broadcast_without_import(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a request from the HTTP API for blinded blocks. This converts blinded blocks into full
|
||||
/// blocks before publishing.
|
||||
pub async fn publish_blinded_block<T: BeaconChainTypes>(
|
||||
block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: &UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger,
|
||||
) -> Result<(), Rejection> {
|
||||
let full_block = reconstruct_block(chain.clone(), block, log.clone()).await?;
|
||||
publish_block::<T>(Arc::new(full_block), chain, network_tx, log).await
|
||||
}
|
||||
|
||||
/// Deconstruct the given blinded block, and construct a full block. This attempts to use the
|
||||
/// execution layer's payload cache, and if that misses, attempts a blind block proposal to retrieve
|
||||
/// the full payload.
|
||||
async fn reconstruct_block<T: BeaconChainTypes>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
|
||||
log: Logger,
|
||||
) -> Result<SignedBeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>, Rejection> {
|
||||
let full_payload = if let Ok(payload_header) = block.message().body().execution_payload() {
|
||||
let el = chain.execution_layer.as_ref().ok_or_else(|| {
|
||||
warp_utils::reject::custom_server_error("Missing execution layer".to_string())
|
||||
})?;
|
||||
|
||||
// If the execution block hash is zero, use an empty payload.
|
||||
let full_payload = if payload_header.block_hash() == ExecutionBlockHash::zero() {
|
||||
ExecutionPayload::default()
|
||||
// If we already have an execution payload with this transactions root cached, use it.
|
||||
} else if let Some(cached_payload) =
|
||||
el.get_payload_by_root(&payload_header.tree_hash_root())
|
||||
{
|
||||
info!(log, "Reconstructing a full block using a local payload"; "block_hash" => ?cached_payload.block_hash);
|
||||
cached_payload
|
||||
// Otherwise, this means we are attempting a blind block proposal.
|
||||
} else {
|
||||
let full_payload = el.propose_blinded_beacon_block(&block).await.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"Blind block proposal failed: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
info!(log, "Successfully published a block to the builder network"; "block_hash" => ?full_payload.block_hash);
|
||||
full_payload
|
||||
};
|
||||
|
||||
Some(full_payload)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
block.try_into_full_block(full_payload).ok_or_else(|| {
|
||||
warp_utils::reject::custom_server_error("Unable to add payload to block".to_string())
|
||||
})
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use crate::ExecutionOptimistic;
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use eth2::types::StateId as CoreStateId;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use types::{BeaconState, EthSpec, Fork, Hash256, Slot};
|
||||
use types::{BeaconState, Checkpoint, 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);
|
||||
#[derive(Debug)]
|
||||
pub struct StateId(pub CoreStateId);
|
||||
|
||||
impl StateId {
|
||||
pub fn slot(slot: Slot) -> Self {
|
||||
pub fn from_slot(slot: Slot) -> Self {
|
||||
Self(CoreStateId::Slot(slot))
|
||||
}
|
||||
|
||||
@@ -16,62 +19,125 @@ impl StateId {
|
||||
pub fn root<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Hash256, warp::Rejection> {
|
||||
let slot = match &self.0 {
|
||||
) -> Result<(Hash256, ExecutionOptimistic), warp::Rejection> {
|
||||
let (slot, execution_optimistic) = match &self.0 {
|
||||
CoreStateId::Head => {
|
||||
return chain
|
||||
.head_info()
|
||||
.map(|head| head.state_root)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
return Ok((
|
||||
cached_head.head_state_root(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
));
|
||||
}
|
||||
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)?;
|
||||
CoreStateId::Genesis => return Ok((chain.genesis_state_root, false)),
|
||||
CoreStateId::Finalized => {
|
||||
let finalized_checkpoint =
|
||||
chain.canonical_head.cached_head().finalized_checkpoint();
|
||||
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?
|
||||
}
|
||||
CoreStateId::Justified => {
|
||||
let justified_checkpoint =
|
||||
chain.canonical_head.cached_head().justified_checkpoint();
|
||||
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?
|
||||
}
|
||||
CoreStateId::Slot(slot) => (
|
||||
*slot,
|
||||
chain
|
||||
.is_optimistic_or_invalid_head()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?,
|
||||
),
|
||||
CoreStateId::Root(root) => {
|
||||
if let Some(hot_summary) = chain
|
||||
.store
|
||||
.load_hot_state_summary(root)
|
||||
.map_err(BeaconChainError::DBError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||
{
|
||||
let execution_optimistic = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_or_invalid_block_no_fallback(&hot_summary.latest_block_root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
return Ok((*root, execution_optimistic));
|
||||
} else if let Some(_cold_state_slot) = chain
|
||||
.store
|
||||
.load_cold_state_slot(root)
|
||||
.map_err(BeaconChainError::DBError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||
{
|
||||
let fork_choice = chain.canonical_head.fork_choice_read_lock();
|
||||
let finalized_root = fork_choice
|
||||
.cached_fork_choice_view()
|
||||
.finalized_checkpoint
|
||||
.root;
|
||||
let execution_optimistic = fork_choice
|
||||
.is_optimistic_or_invalid_block_no_fallback(&finalized_root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
return Ok((*root, execution_optimistic));
|
||||
} else {
|
||||
return Err(warp_utils::reject::custom_not_found(format!(
|
||||
"beacon state for state root {}",
|
||||
root
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
chain
|
||||
let root = 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))
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok((root, execution_optimistic))
|
||||
}
|
||||
|
||||
/// Return the `fork` field of the state identified by `self`.
|
||||
/// Also returns the `execution_optimistic` value of the state.
|
||||
pub fn fork_and_execution_optimistic<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<(Fork, bool), warp::Rejection> {
|
||||
self.map_state_and_execution_optimistic(chain, |state, execution_optimistic| {
|
||||
Ok((state.fork(), execution_optimistic))
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience function to compute `fork` when `execution_optimistic` isn't desired.
|
||||
pub fn fork<T: BeaconChainTypes>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Fork, warp::Rejection> {
|
||||
self.map_state(chain, |state| Ok(state.fork()))
|
||||
self.fork_and_execution_optimistic(chain)
|
||||
.map(|(fork, _)| 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 {
|
||||
) -> Result<(BeaconState<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
|
||||
let ((state_root, execution_optimistic), slot_opt) = match &self.0 {
|
||||
CoreStateId::Head => {
|
||||
return chain
|
||||
.head_beacon_state()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)
|
||||
let (cached_head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
return Ok((
|
||||
cached_head.snapshot.beacon_state.clone(),
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
));
|
||||
}
|
||||
CoreStateId::Slot(slot) => (self.root(chain)?, Some(*slot)),
|
||||
_ => (self.root(chain)?, None),
|
||||
};
|
||||
|
||||
chain
|
||||
let state = chain
|
||||
.get_state(&state_root, slot_opt)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)
|
||||
.and_then(|opt| {
|
||||
@@ -81,13 +147,17 @@ impl StateId {
|
||||
state_root
|
||||
))
|
||||
})
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok((state, execution_optimistic))
|
||||
}
|
||||
|
||||
/*
|
||||
/// 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.
|
||||
#[allow(dead_code)]
|
||||
pub fn map_state<T: BeaconChainTypes, F, U>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
@@ -103,6 +173,36 @@ impl StateId {
|
||||
_ => func(&self.state(chain)?),
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// Functions the same as `map_state` but additionally computes the value of
|
||||
/// `execution_optimistic` of the state identified by `self`.
|
||||
///
|
||||
/// This is to avoid re-instantiating `state` unnecessarily.
|
||||
pub fn map_state_and_execution_optimistic<T: BeaconChainTypes, F, U>(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
func: F,
|
||||
) -> Result<U, warp::Rejection>
|
||||
where
|
||||
F: Fn(&BeaconState<T::EthSpec>, bool) -> Result<U, warp::Rejection>,
|
||||
{
|
||||
let (state, execution_optimistic) = match &self.0 {
|
||||
CoreStateId::Head => {
|
||||
let (head, execution_status) = chain
|
||||
.canonical_head
|
||||
.head_and_execution_status()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
return func(
|
||||
&head.snapshot.beacon_state,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
);
|
||||
}
|
||||
_ => self.state(chain)?,
|
||||
};
|
||||
|
||||
func(&state, execution_optimistic)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StateId {
|
||||
@@ -112,3 +212,35 @@ impl FromStr for StateId {
|
||||
CoreStateId::from_str(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StateId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first slot of the checkpoint's `epoch` and the execution status of the checkpoint's
|
||||
/// `root`.
|
||||
pub fn checkpoint_slot_and_execution_optimistic<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
checkpoint: Checkpoint,
|
||||
) -> Result<(Slot, ExecutionOptimistic), warp::reject::Rejection> {
|
||||
let slot = checkpoint.epoch.start_slot(T::EthSpec::slots_per_epoch());
|
||||
let fork_choice = chain.canonical_head.fork_choice_read_lock();
|
||||
let finalized_checkpoint = fork_choice.cached_fork_choice_view().finalized_checkpoint;
|
||||
|
||||
// If the checkpoint is pre-finalization, just use the optimistic status of the finalized
|
||||
// block.
|
||||
let root = if checkpoint.epoch < finalized_checkpoint.epoch {
|
||||
&finalized_checkpoint.root
|
||||
} else {
|
||||
&checkpoint.root
|
||||
};
|
||||
|
||||
let execution_optimistic = fork_choice
|
||||
.is_optimistic_or_invalid_block_no_fallback(root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
Ok((slot, execution_optimistic))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use beacon_chain::{
|
||||
use eth2::types::{self as api_types};
|
||||
use lighthouse_network::PubsubMessage;
|
||||
use network::NetworkMessage;
|
||||
use slog::{error, warn, Logger};
|
||||
use slog::{debug, error, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
@@ -22,7 +22,7 @@ use types::{
|
||||
};
|
||||
|
||||
/// The struct that is returned to the requesting HTTP client.
|
||||
type SyncDuties = api_types::GenericResponse<Vec<SyncDuty>>;
|
||||
type SyncDuties = api_types::ExecutionOptimisticResponse<Vec<SyncDuty>>;
|
||||
|
||||
/// Handles a request from the HTTP API for sync committee duties.
|
||||
pub fn sync_committee_duties<T: BeaconChainTypes>(
|
||||
@@ -34,14 +34,20 @@ pub fn sync_committee_duties<T: BeaconChainTypes>(
|
||||
altair_fork_epoch
|
||||
} else {
|
||||
// Empty response for networks with Altair disabled.
|
||||
return Ok(convert_to_response(vec![]));
|
||||
return Ok(convert_to_response(vec![], false));
|
||||
};
|
||||
|
||||
// Even when computing duties from state, any block roots pulled using the request epoch are
|
||||
// still dependent on the head. So using `is_optimistic_head` is fine for both cases.
|
||||
let execution_optimistic = chain
|
||||
.is_optimistic_or_invalid_head()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
|
||||
// Try using the head's sync committees to satisfy the request. This should be sufficient for
|
||||
// the vast majority of requests. Rather than checking if we think the request will succeed in a
|
||||
// way prone to data races, we attempt the request immediately and check the error code.
|
||||
match chain.sync_committee_duties_from_head(request_epoch, request_indices) {
|
||||
Ok(duties) => return Ok(convert_to_response(duties)),
|
||||
Ok(duties) => return Ok(convert_to_response(duties, execution_optimistic)),
|
||||
Err(BeaconChainError::SyncDutiesError(BeaconStateError::SyncCommitteeNotKnown {
|
||||
..
|
||||
}))
|
||||
@@ -60,7 +66,7 @@ pub fn sync_committee_duties<T: BeaconChainTypes>(
|
||||
)),
|
||||
e => warp_utils::reject::beacon_chain_error(e),
|
||||
})?;
|
||||
Ok(convert_to_response(duties))
|
||||
Ok(convert_to_response(duties, execution_optimistic))
|
||||
}
|
||||
|
||||
/// Slow path for duties: load a state and use it to compute the duties.
|
||||
@@ -117,8 +123,9 @@ fn duties_from_state_load<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_response(duties: Vec<Option<SyncDuty>>) -> SyncDuties {
|
||||
fn convert_to_response(duties: Vec<Option<SyncDuty>>, execution_optimistic: bool) -> SyncDuties {
|
||||
api_types::GenericResponse::from(duties.into_iter().flatten().collect::<Vec<_>>())
|
||||
.add_execution_optimistic(execution_optimistic)
|
||||
}
|
||||
|
||||
/// Receive sync committee duties, storing them in the pools & broadcasting them.
|
||||
@@ -182,6 +189,24 @@ pub fn process_sync_committee_signatures<T: BeaconChainTypes>(
|
||||
|
||||
verified_for_pool = Some(verified);
|
||||
}
|
||||
// If this validator has already published a sync message, just ignore this message
|
||||
// without returning an error.
|
||||
//
|
||||
// This is likely to happen when a VC uses fallback BNs. If the first BN publishes
|
||||
// the message and then fails to respond in a timely fashion then the VC will move
|
||||
// to the second BN. The BN will then report that this message has already been
|
||||
// seen, which is not actually an error as far as the network or user are concerned.
|
||||
Err(SyncVerificationError::PriorSyncCommitteeMessageKnown {
|
||||
validator_index,
|
||||
slot,
|
||||
}) => {
|
||||
debug!(
|
||||
log,
|
||||
"Ignoring already-known sync message";
|
||||
"slot" => slot,
|
||||
"validator_index" => validator_index,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
log,
|
||||
@@ -276,6 +301,16 @@ pub fn process_signed_contribution_and_proofs<T: BeaconChainTypes>(
|
||||
// If we already know the contribution, don't broadcast it or attempt to
|
||||
// further verify it. Return success.
|
||||
Err(SyncVerificationError::SyncContributionAlreadyKnown(_)) => continue,
|
||||
// If we've already seen this aggregator produce an aggregate, just
|
||||
// skip this one.
|
||||
//
|
||||
// We're likely to see this with VCs that use fallback BNs. The first
|
||||
// BN might time-out *after* publishing the aggregate and then the
|
||||
// second BN will indicate it's already seen the aggregate.
|
||||
//
|
||||
// There's no actual error for the user or the network since the
|
||||
// aggregate has been successfully published by some other node.
|
||||
Err(SyncVerificationError::AggregatorAlreadyKnown(_)) => continue,
|
||||
Err(e) => {
|
||||
error!(
|
||||
log,
|
||||
|
||||
@@ -16,7 +16,10 @@ fn end_of_epoch_state<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<BeaconState<T::EthSpec>, warp::reject::Rejection> {
|
||||
let target_slot = epoch.end_slot(T::EthSpec::slots_per_epoch());
|
||||
StateId::slot(target_slot).state(chain)
|
||||
// The execution status is not returned, any functions which rely upon this method might return
|
||||
// optimistic information without explicitly declaring so.
|
||||
let (state, _execution_status) = StateId::from_slot(target_slot).state(chain)?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Generate an `EpochProcessingSummary` for `state`.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::api_types::{EndpointVersion, ForkVersionedResponse};
|
||||
use crate::api_types::{
|
||||
EndpointVersion, ExecutionOptimisticForkVersionedResponse, ForkVersionedResponse,
|
||||
};
|
||||
use eth2::CONSENSUS_VERSION_HEADER;
|
||||
use serde::Serialize;
|
||||
use types::{ForkName, InconsistentFork};
|
||||
@@ -25,6 +27,26 @@ pub fn fork_versioned_response<T: Serialize>(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn execution_optimistic_fork_versioned_response<T: Serialize>(
|
||||
endpoint_version: EndpointVersion,
|
||||
fork_name: ForkName,
|
||||
execution_optimistic: bool,
|
||||
data: T,
|
||||
) -> Result<ExecutionOptimisticForkVersionedResponse<T>, warp::reject::Rejection> {
|
||||
let fork_name = if endpoint_version == V1 {
|
||||
None
|
||||
} else if endpoint_version == V2 {
|
||||
Some(fork_name)
|
||||
} else {
|
||||
return Err(unsupported_version_rejection(endpoint_version));
|
||||
};
|
||||
Ok(ExecutionOptimisticForkVersionedResponse {
|
||||
version: fork_name,
|
||||
execution_optimistic: Some(execution_optimistic),
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add the `Eth-Consensus-Version` header to a response.
|
||||
pub fn add_consensus_version_header<T: Reply>(reply: T, fork_name: ForkName) -> WithHeader<T> {
|
||||
reply::with_header(reply, CONSENSUS_VERSION_HEADER, fork_name.to_string())
|
||||
|
||||
Reference in New Issue
Block a user