Add execution_optimistic flag to HTTP responses (#3070)

## Issue Addressed

#3031 

## Proposed Changes

Updates the following API endpoints to conform with https://github.com/ethereum/beacon-APIs/pull/190 and https://github.com/ethereum/beacon-APIs/pull/196
- [x] `beacon/states/{state_id}/root` 
- [x] `beacon/states/{state_id}/fork`
- [x] `beacon/states/{state_id}/finality_checkpoints`
- [x] `beacon/states/{state_id}/validators`
- [x] `beacon/states/{state_id}/validators/{validator_id}`
- [x] `beacon/states/{state_id}/validator_balances`
- [x] `beacon/states/{state_id}/committees`
- [x] `beacon/states/{state_id}/sync_committees`
- [x] `beacon/headers`
- [x] `beacon/headers/{block_id}`
- [x] `beacon/blocks/{block_id}`
- [x] `beacon/blocks/{block_id}/root`
- [x] `beacon/blocks/{block_id}/attestations`
- [x] `debug/beacon/states/{state_id}`
- [x] `debug/beacon/heads`
- [x] `validator/duties/attester/{epoch}`
- [x] `validator/duties/proposer/{epoch}`
- [x] `validator/duties/sync/{epoch}`

Updates the following Server-Sent Events:
- [x]  `events?topics=head`
- [x]  `events?topics=block`
- [x]  `events?topics=finalized_checkpoint`
- [x]  `events?topics=chain_reorg`

## Backwards Incompatible
There is a very minor breaking change with the way the API now handles requests to `beacon/blocks/{block_id}/root` and `beacon/states/{state_id}/root` when `block_id` or `state_id` is the `Root` variant of `BlockId` and `StateId` respectively.

Previously a request to a non-existent root would simply echo the root back to the requester:
```
curl "http://localhost:5052/eth/v1/beacon/states/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/root"
{"data":{"root":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
```
Now it will return a `404`:
```
curl "http://localhost:5052/eth/v1/beacon/blocks/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/root"
{"code":404,"message":"NOT_FOUND: beacon block with root 0xaaaa…aaaa","stacktraces":[]}
```

In addition to this is the block root `0x0000000000000000000000000000000000000000000000000000000000000000` previously would return the genesis block. It will now return a `404`:
```
curl "http://localhost:5052/eth/v1/beacon/blocks/0x0000000000000000000000000000000000000000000000000000000000000000"
{"code":404,"message":"NOT_FOUND: beacon block with root 0x0000…0000","stacktraces":[]}
```

## Additional Info
- `execution_optimistic` is always set, and will return `false` pre-Bellatrix. I am also open to the idea of doing something like `#[serde(skip_serializing_if = "Option::is_none")]`.
- The value of `execution_optimistic` is set to `false` where possible. Any computation that is reliant on the `head` will simply use the `ExecutionStatus` of the head (unless the head block is pre-Bellatrix).

Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
Mac L
2022-07-25 08:23:00 +00:00
parent 21dec6f603
commit bb5a6d2cca
18 changed files with 1227 additions and 693 deletions

View File

@@ -60,11 +60,17 @@ fn cached_attestation_duties<T: BeaconChainTypes>(
) -> Result<ApiDuties, warp::reject::Rejection> {
let head_block_root = chain.canonical_head.cached_head().head_block_root();
let (duties, dependent_root, _execution_status) = chain
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)?;
convert_to_api_response(duties, request_indices, dependent_root, chain)
convert_to_api_response(
duties,
request_indices,
dependent_root,
execution_status.is_optimistic(),
chain,
)
}
/// Compute some attester duties by reading a `BeaconState` from disk, completely ignoring the
@@ -76,35 +82,42 @@ 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_with(CloneConfig::committee_caches_only()),
)))
} 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_with(CloneConfig::committee_caches_only()),
execution_status.is_optimistic(),
))
} 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!(
@@ -140,7 +153,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>(
@@ -178,6 +197,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.
@@ -213,6 +233,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
Ok(api_types::DutiesResponse {
dependent_root,
execution_optimistic: Some(execution_optimistic),
data,
})
}

View File

@@ -1,8 +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 std::sync::Arc;
use types::{BlindedPayload, Hash256, SignedBeaconBlock, Slot};
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`.
@@ -22,32 +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 => Ok(chain.canonical_head.cached_head().head_block_root()),
CoreBlockId::Genesis => Ok(chain.genesis_block_root),
CoreBlockId::Finalized => Ok(chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.root),
CoreBlockId::Justified => Ok(chain
.canonical_head
.cached_head()
.justified_checkpoint()
.root),
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(),
))
}
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_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_block(root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?;
Ok((*root, execution_optimistic))
} else {
return Err(warp_utils::reject::custom_not_found(format!(
"beacon block with root {}",
root
)));
}
}
}
}
@@ -55,11 +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 => Ok(chain.head_beacon_block().clone_as_blinded()),
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(),
))
}
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)
@@ -71,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 {}",
@@ -80,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| {
@@ -91,7 +148,8 @@ impl BlockId {
root
))
})
})
})?;
Ok((block, execution_optimistic))
}
}
}
@@ -100,11 +158,20 @@ impl BlockId {
pub async fn full_block<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<Arc<SignedBeaconBlock<T::EthSpec>>, warp::Rejection> {
) -> Result<(Arc<SignedBeaconBlock<T::EthSpec>>, ExecutionOptimistic), warp::Rejection> {
match &self.0 {
CoreBlockId::Head => Ok(chain.head_beacon_block()),
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(),
))
}
CoreBlockId::Slot(slot) => {
let root = self.root(chain)?;
let (root, execution_optimistic) = self.root(chain)?;
chain
.get_block(&root)
.await
@@ -117,7 +184,7 @@ impl BlockId {
slot
)));
}
Ok(Arc::new(block))
Ok((Arc::new(block), execution_optimistic))
}
None => Err(warp_utils::reject::custom_not_found(format!(
"beacon block with root {}",
@@ -126,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(|block_opt| {
block_opt.map(Arc::new).ok_or_else(|| {
warp_utils::reject::custom_not_found(format!(
"beacon block with root {}",
root
))
})
block_opt
.map(|block| (Arc::new(block), execution_optimistic))
.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!(
"beacon block with root {}",
root
))
})
})
}
}
@@ -151,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)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -55,10 +55,16 @@ pub fn proposer_duties<T: BeaconChainTypes>(
.safe_add(1)
.map_err(warp_utils::reject::arith_error)?
{
let (proposers, dependent_root, _execution_status, _fork) =
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(),
proposers,
)
} else if request_epoch
> current_epoch
.safe_add(1)
@@ -88,17 +94,18 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
request_epoch: Epoch,
chain: &BeaconChain<T>,
) -> Result<Option<ApiDuties>, warp::reject::Rejection> {
let (head_slot, head_block_root, head_decision_root) = {
let head = chain.canonical_head.cached_head();
let head_block_root = head.head_block_root();
let decision_root = head
.snapshot
.beacon_state
.proposer_shuffling_decision_root(head_block_root)
.map_err(warp_utils::reject::beacon_state_error)?;
(head.head_slot(), head_block_root, decision_root)
};
let head_epoch = head_slot.epoch(T::EthSpec::slots_per_epoch());
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_head_block(head_block)
.map_err(warp_utils::reject::beacon_chain_error)?;
let dependent_root = match head_epoch.cmp(&request_epoch) {
// head_epoch == request_epoch
@@ -120,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()
}
@@ -139,7 +152,7 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
current_epoch: Epoch,
chain: &BeaconChain<T>,
) -> Result<ApiDuties, warp::reject::Rejection> {
let (indices, dependent_root, _execution_status, fork) =
let (indices, dependent_root, execution_status, fork) =
compute_proposer_duties_from_head(current_epoch, chain)
.map_err(warp_utils::reject::beacon_chain_error)?;
@@ -151,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(),
indices,
)
}
/// Compute some proposer duties by reading a `BeaconState` from disk, completely ignoring the
@@ -162,31 +181,37 @@ 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_with(CloneConfig::committee_caches_only()),
)))
} 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_with(CloneConfig::committee_caches_only()),
execution_status.is_optimistic(),
))
} 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!(
@@ -208,7 +233,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
@@ -217,6 +242,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
@@ -251,6 +277,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
} else {
Ok(api_types::DutiesResponse {
dependent_root,
execution_optimistic: Some(execution_optimistic),
data: proposer_data,
})
}

View File

@@ -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,54 +19,128 @@ impl StateId {
pub fn root<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<Hash256, warp::Rejection> {
let slot = match &self.0 {
CoreStateId::Head => return Ok(chain.canonical_head.cached_head().head_state_root()),
CoreStateId::Genesis => return Ok(chain.genesis_state_root),
CoreStateId::Finalized => chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
CoreStateId::Justified => chain
.canonical_head
.cached_head()
.justified_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
CoreStateId::Slot(slot) => *slot,
CoreStateId::Root(root) => return Ok(*root),
) -> Result<(Hash256, ExecutionOptimistic), warp::Rejection> {
let (slot, execution_optimistic) = match &self.0 {
CoreStateId::Head => {
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(),
));
}
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_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_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_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 {
CoreStateId::Head => return Ok(chain.head_beacon_state_cloned()),
) -> Result<(BeaconState<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
let ((state_root, execution_optimistic), slot_opt) = match &self.0 {
CoreStateId::Head => {
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_with_only_committee_caches(),
execution_status.is_optimistic(),
));
}
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| {
@@ -73,13 +150,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>,
@@ -95,6 +176,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(),
);
}
_ => self.state(chain)?,
};
func(&state, execution_optimistic)
}
}
impl FromStr for StateId {
@@ -104,3 +215,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_block_no_fallback(root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?;
Ok((slot, execution_optimistic))
}

View File

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

View File

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

View File

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