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

1
Cargo.lock generated
View File

@ -2604,6 +2604,7 @@ dependencies = [
"lru", "lru",
"network", "network",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"proto_array",
"safe_arith", "safe_arith",
"sensitive_url", "sensitive_url",
"serde", "serde",

View File

@ -1289,7 +1289,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
epoch: Epoch, epoch: Epoch,
head_block_root: Hash256, head_block_root: Hash256,
) -> Result<(Vec<Option<AttestationDuty>>, Hash256, ExecutionStatus), Error> { ) -> Result<(Vec<Option<AttestationDuty>>, Hash256, ExecutionStatus), Error> {
self.with_committee_cache(head_block_root, epoch, |committee_cache, dependent_root| { let execution_status = self
.canonical_head
.fork_choice_read_lock()
.get_block_execution_status(&head_block_root)
.ok_or(Error::AttestationHeadNotInForkChoice(head_block_root))?;
let (duties, dependent_root) = self.with_committee_cache(
head_block_root,
epoch,
|committee_cache, dependent_root| {
let duties = validator_indices let duties = validator_indices
.iter() .iter()
.map(|validator_index| { .map(|validator_index| {
@ -1298,14 +1307,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}) })
.collect(); .collect();
let execution_status = self Ok((duties, dependent_root))
.canonical_head },
.fork_choice_read_lock() )?;
.get_block_execution_status(&head_block_root)
.ok_or(Error::AttestationHeadNotInForkChoice(head_block_root))?;
Ok((duties, dependent_root, execution_status)) Ok((duties, dependent_root, execution_status))
})
} }
/// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`. /// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`.
@ -2908,6 +2913,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
event_handler.register(EventKind::Block(SseBlock { event_handler.register(EventKind::Block(SseBlock {
slot, slot,
block: block_root, block: block_root,
execution_optimistic: payload_verification_status.is_optimistic(),
})); }));
} }
} }
@ -4055,9 +4061,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ///
/// Returns `Ok(false)` if the block is pre-Bellatrix, or has `ExecutionStatus::Valid`. /// Returns `Ok(false)` if the block is pre-Bellatrix, or has `ExecutionStatus::Valid`.
/// Returns `Ok(true)` if the block has `ExecutionStatus::Optimistic`. /// Returns `Ok(true)` if the block has `ExecutionStatus::Optimistic`.
pub fn is_optimistic_block( pub fn is_optimistic_block<Payload: ExecPayload<T::EthSpec>>(
&self, &self,
block: &SignedBeaconBlock<T::EthSpec>, block: &SignedBeaconBlock<T::EthSpec, Payload>,
) -> Result<bool, BeaconChainError> { ) -> Result<bool, BeaconChainError> {
// Check if the block is pre-Bellatrix. // Check if the block is pre-Bellatrix.
if self.slot_is_prior_to_bellatrix(block.slot()) { if self.slot_is_prior_to_bellatrix(block.slot()) {
@ -4081,9 +4087,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ///
/// There is a potential race condition when syncing where the block_root of `head_block` could /// There is a potential race condition when syncing where the block_root of `head_block` could
/// be pruned from the fork choice store before being read. /// be pruned from the fork choice store before being read.
pub fn is_optimistic_head_block( pub fn is_optimistic_head_block<Payload: ExecPayload<T::EthSpec>>(
&self, &self,
head_block: &SignedBeaconBlock<T::EthSpec>, head_block: &SignedBeaconBlock<T::EthSpec, Payload>,
) -> Result<bool, BeaconChainError> { ) -> Result<bool, BeaconChainError> {
// Check if the block is pre-Bellatrix. // Check if the block is pre-Bellatrix.
if self.slot_is_prior_to_bellatrix(head_block.slot()) { if self.slot_is_prior_to_bellatrix(head_block.slot()) {

View File

@ -300,6 +300,23 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
.ok_or(Error::HeadMissingFromForkChoice(head_block_root)) .ok_or(Error::HeadMissingFromForkChoice(head_block_root))
} }
/// Returns a clone of the `CachedHead` and the execution status of the contained head block.
///
/// This will only return `Err` in the scenario where `self.fork_choice` has advanced
/// significantly past the cached `head_snapshot`. In such a scenario it is likely prudent to
/// run `BeaconChain::recompute_head` to update the cached values.
pub fn head_and_execution_status(
&self,
) -> Result<(CachedHead<T::EthSpec>, ExecutionStatus), Error> {
let head = self.cached_head();
let head_block_root = head.head_block_root();
let execution_status = self
.fork_choice_read_lock()
.get_block_execution_status(&head_block_root)
.ok_or(Error::HeadMissingFromForkChoice(head_block_root))?;
Ok((head, execution_status))
}
/// Returns a clone of `self.cached_head`. /// Returns a clone of `self.cached_head`.
/// ///
/// Takes a read-lock on `self.cached_head` for a short time (just long enough to clone it). /// Takes a read-lock on `self.cached_head` for a short time (just long enough to clone it).
@ -713,6 +730,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
) -> Result<(), Error> { ) -> Result<(), Error> {
let old_snapshot = &old_cached_head.snapshot; let old_snapshot = &old_cached_head.snapshot;
let new_snapshot = &new_cached_head.snapshot; let new_snapshot = &new_cached_head.snapshot;
let new_head_is_optimistic = new_head_proto_block.execution_status.is_optimistic();
// Detect and potentially report any re-orgs. // Detect and potentially report any re-orgs.
let reorg_distance = detect_reorg( let reorg_distance = detect_reorg(
@ -798,6 +816,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
current_duty_dependent_root, current_duty_dependent_root,
previous_duty_dependent_root, previous_duty_dependent_root,
epoch_transition: is_epoch_transition, epoch_transition: is_epoch_transition,
execution_optimistic: new_head_is_optimistic,
})); }));
} }
(Err(e), _) | (_, Err(e)) => { (Err(e), _) | (_, Err(e)) => {
@ -825,6 +844,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
new_head_block: new_snapshot.beacon_block_root, new_head_block: new_snapshot.beacon_block_root,
new_head_state: new_snapshot.beacon_state_root(), new_head_state: new_snapshot.beacon_state_root(),
epoch: head_slot.epoch(T::EthSpec::slots_per_epoch()), epoch: head_slot.epoch(T::EthSpec::slots_per_epoch()),
execution_optimistic: new_head_is_optimistic,
})); }));
} }
} }
@ -841,6 +861,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
finalized_proto_block: ProtoBlock, finalized_proto_block: ProtoBlock,
) -> Result<(), Error> { ) -> Result<(), Error> {
let new_snapshot = &new_cached_head.snapshot; let new_snapshot = &new_cached_head.snapshot;
let finalized_block_is_optimistic = finalized_proto_block.execution_status.is_optimistic();
self.op_pool self.op_pool
.prune_all(&new_snapshot.beacon_state, self.epoch()?); .prune_all(&new_snapshot.beacon_state, self.epoch()?);
@ -884,6 +905,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// specific state root at the first slot of the finalized epoch (which // specific state root at the first slot of the finalized epoch (which
// might be a skip slot). // might be a skip slot).
state: finalized_proto_block.state_root, state: finalized_proto_block.state_root,
execution_optimistic: finalized_block_is_optimistic,
})); }));
} }
} }
@ -1216,6 +1238,7 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
let block_time_set_as_head = timestamp_now(); let block_time_set_as_head = timestamp_now();
let head_block_root = head_block.root; let head_block_root = head_block.root;
let head_block_slot = head_block.slot; let head_block_slot = head_block.slot;
let head_block_is_optimistic = head_block.execution_status.is_optimistic();
// Calculate the total delay between the start of the slot and when it was set as head. // Calculate the total delay between the start of the slot and when it was set as head.
let block_delay_total = get_slot_delay_ms(block_time_set_as_head, head_block_slot, slot_clock); let block_delay_total = get_slot_delay_ms(block_time_set_as_head, head_block_slot, slot_clock);
@ -1308,6 +1331,7 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
observed_delay: block_delays.observed, observed_delay: block_delays.observed,
imported_delay: block_delays.imported, imported_delay: block_delays.imported,
set_as_head_delay: block_delays.set_as_head, set_as_head_delay: block_delays.set_as_head,
execution_optimistic: head_block_is_optimistic,
})); }));
} }
} }

View File

@ -37,6 +37,7 @@ use state_processing::{
}; };
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -1778,3 +1779,10 @@ where
(honest_head, faulty_head) (honest_head, faulty_head)
} }
} }
// Junk `Debug` impl to satistfy certain trait bounds during testing.
impl<T: BeaconChainTypes> fmt::Debug for BeaconChainHarness<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BeaconChainHarness")
}
}

View File

@ -40,6 +40,7 @@ tree_hash = "0.4.1"
sensitive_url = { path = "../../common/sensitive_url" } sensitive_url = { path = "../../common/sensitive_url" }
logging = { path = "../../common/logging" } logging = { path = "../../common/logging" }
serde_json = "1.0.58" serde_json = "1.0.58"
proto_array = { path = "../../consensus/proto_array" }
[[test]] [[test]]
name = "bn_http_api_tests" name = "bn_http_api_tests"

View File

@ -60,11 +60,17 @@ fn cached_attestation_duties<T: BeaconChainTypes>(
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> Result<ApiDuties, warp::reject::Rejection> {
let head_block_root = chain.canonical_head.cached_head().head_block_root(); 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) .validator_attestation_duties(request_indices, request_epoch, head_block_root)
.map_err(warp_utils::reject::beacon_chain_error)?; .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 /// Compute some attester duties by reading a `BeaconState` from disk, completely ignoring the
@ -76,22 +82,28 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> Result<ApiDuties, warp::reject::Rejection> {
// If the head is quite old then it might still be relevant for a historical request. // 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. // Avoid holding the `cached_head` longer than necessary.
let state_opt = chain let state_opt = {
.with_head(|head| { 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;
if head.beacon_state.current_epoch() <= request_epoch { if head.beacon_state.current_epoch() <= request_epoch {
Ok(Some(( Some((
head.beacon_state_root(), head.beacon_state_root(),
head.beacon_state head.beacon_state
.clone_with(CloneConfig::committee_caches_only()), .clone_with(CloneConfig::committee_caches_only()),
))) execution_status.is_optimistic(),
))
} else { } else {
Ok(None) None
} }
}) };
.map_err(warp_utils::reject::beacon_chain_error)?;
let mut state = if let Some((state_root, mut state)) = state_opt { 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 // If we've loaded the head state it might be from a previous epoch, ensure it's in a
// suitable epoch. // suitable epoch.
ensure_state_knows_attester_duties_for_epoch( ensure_state_knows_attester_duties_for_epoch(
@ -100,9 +112,10 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
request_epoch, request_epoch,
&chain.spec, &chain.spec,
)?; )?;
state (state, execution_optimistic)
} else { } else {
StateId::slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)? StateId::from_slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch()))
.state(chain)?
}; };
// Sanity-check the state lookup. // Sanity-check the state lookup.
@ -140,7 +153,13 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(warp_utils::reject::beacon_chain_error)?; .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>( 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>>, duties: Vec<Option<AttestationDuty>>,
indices: &[u64], indices: &[u64],
dependent_root: Hash256, dependent_root: Hash256,
execution_optimistic: bool,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> Result<ApiDuties, warp::reject::Rejection> {
// Protect against an inconsistent slot clock. // Protect against an inconsistent slot clock.
@ -213,6 +233,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
Ok(api_types::DutiesResponse { Ok(api_types::DutiesResponse {
dependent_root, dependent_root,
execution_optimistic: Some(execution_optimistic),
data, 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 eth2::types::BlockId as CoreBlockId;
use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; 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 /// Wraps `eth2::types::BlockId` and provides a simple way to obtain a block or root for a given
/// `BlockId`. /// `BlockId`.
@ -22,21 +24,38 @@ impl BlockId {
pub fn root<T: BeaconChainTypes>( pub fn root<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<Hash256, warp::Rejection> { ) -> Result<(Hash256, ExecutionOptimistic), warp::Rejection> {
match &self.0 { match &self.0 {
CoreBlockId::Head => Ok(chain.canonical_head.cached_head().head_block_root()), CoreBlockId::Head => {
CoreBlockId::Genesis => Ok(chain.genesis_block_root), let (cached_head, execution_status) = chain
CoreBlockId::Finalized => Ok(chain
.canonical_head .canonical_head
.cached_head() .head_and_execution_status()
.finalized_checkpoint() .map_err(warp_utils::reject::beacon_chain_error)?;
.root), Ok((
CoreBlockId::Justified => Ok(chain cached_head.head_block_root(),
.canonical_head execution_status.is_optimistic(),
.cached_head() ))
.justified_checkpoint() }
.root), CoreBlockId::Genesis => Ok((chain.genesis_block_root, false)),
CoreBlockId::Slot(slot) => chain 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) .block_root_at_slot(*slot, WhenSlotSkipped::None)
.map_err(warp_utils::reject::beacon_chain_error) .map_err(warp_utils::reject::beacon_chain_error)
.and_then(|root_opt| { .and_then(|root_opt| {
@ -46,8 +65,37 @@ impl BlockId {
slot slot
)) ))
}) })
}), })?;
CoreBlockId::Root(root) => Ok(*root), 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>( pub fn blinded_block<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>, warp::Rejection> { ) -> Result<(SignedBlindedBeaconBlock<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
match &self.0 { 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) => { CoreBlockId::Slot(slot) => {
let root = self.root(chain)?; let (root, execution_optimistic) = self.root(chain)?;
chain chain
.get_blinded_block(&root) .get_blinded_block(&root)
.map_err(warp_utils::reject::beacon_chain_error) .map_err(warp_utils::reject::beacon_chain_error)
@ -71,7 +128,7 @@ impl BlockId {
slot slot
))); )));
} }
Ok(block) Ok((block, execution_optimistic))
} }
None => Err(warp_utils::reject::custom_not_found(format!( None => Err(warp_utils::reject::custom_not_found(format!(
"beacon block with root {}", "beacon block with root {}",
@ -80,8 +137,8 @@ impl BlockId {
}) })
} }
_ => { _ => {
let root = self.root(chain)?; let (root, execution_optimistic) = self.root(chain)?;
chain let block = chain
.get_blinded_block(&root) .get_blinded_block(&root)
.map_err(warp_utils::reject::beacon_chain_error) .map_err(warp_utils::reject::beacon_chain_error)
.and_then(|root_opt| { .and_then(|root_opt| {
@ -91,7 +148,8 @@ impl BlockId {
root root
)) ))
}) })
}) })?;
Ok((block, execution_optimistic))
} }
} }
} }
@ -100,11 +158,20 @@ impl BlockId {
pub async fn full_block<T: BeaconChainTypes>( pub async fn full_block<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<Arc<SignedBeaconBlock<T::EthSpec>>, warp::Rejection> { ) -> Result<(Arc<SignedBeaconBlock<T::EthSpec>>, ExecutionOptimistic), warp::Rejection> {
match &self.0 { 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) => { CoreBlockId::Slot(slot) => {
let root = self.root(chain)?; let (root, execution_optimistic) = self.root(chain)?;
chain chain
.get_block(&root) .get_block(&root)
.await .await
@ -117,7 +184,7 @@ impl BlockId {
slot slot
))); )));
} }
Ok(Arc::new(block)) Ok((Arc::new(block), execution_optimistic))
} }
None => Err(warp_utils::reject::custom_not_found(format!( None => Err(warp_utils::reject::custom_not_found(format!(
"beacon block with root {}", "beacon block with root {}",
@ -126,13 +193,15 @@ impl BlockId {
}) })
} }
_ => { _ => {
let root = self.root(chain)?; let (root, execution_optimistic) = self.root(chain)?;
chain chain
.get_block(&root) .get_block(&root)
.await .await
.map_err(warp_utils::reject::beacon_chain_error) .map_err(warp_utils::reject::beacon_chain_error)
.and_then(|block_opt| { .and_then(|block_opt| {
block_opt.map(Arc::new).ok_or_else(|| { block_opt
.map(|block| (Arc::new(block), execution_optimistic))
.ok_or_else(|| {
warp_utils::reject::custom_not_found(format!( warp_utils::reject::custom_not_found(format!(
"beacon block with root {}", "beacon block with root {}",
root root
@ -151,3 +220,9 @@ impl FromStr for BlockId {
CoreBlockId::from_str(s).map(Self) CoreBlockId::from_str(s).map(Self)
} }
} }
impl fmt::Display for BlockId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@ -25,7 +25,7 @@ use beacon_chain::{
AttestationError as AttnError, BeaconChain, BeaconChainError, BeaconChainTypes, AttestationError as AttnError, BeaconChain, BeaconChainError, BeaconChainTypes,
ProduceBlockVerification, WhenSlotSkipped, ProduceBlockVerification, WhenSlotSkipped,
}; };
use block_id::BlockId; pub use block_id::BlockId;
use eth2::types::{self as api_types, EndpointVersion, ValidatorId}; use eth2::types::{self as api_types, EndpointVersion, ValidatorId};
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
use lighthouse_version::version_with_platform; use lighthouse_version::version_with_platform;
@ -34,7 +34,7 @@ use serde::{Deserialize, Serialize};
use slog::{crit, debug, error, info, warn, Logger}; use slog::{crit, debug, error, info, warn, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use ssz::Encode; use ssz::Encode;
use state_id::StateId; pub use state_id::StateId;
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::TryInto; use std::convert::TryInto;
use std::future::Future; use std::future::Future;
@ -53,8 +53,8 @@ use types::{
SyncCommitteeMessage, SyncContributionData, SyncCommitteeMessage, SyncContributionData,
}; };
use version::{ use version::{
add_consensus_version_header, fork_versioned_response, inconsistent_fork_rejection, add_consensus_version_header, execution_optimistic_fork_versioned_response,
unsupported_version_rejection, V1, fork_versioned_response, inconsistent_fork_rejection, unsupported_version_rejection, V1, V2,
}; };
use warp::http::StatusCode; use warp::http::StatusCode;
use warp::sse::Event; use warp::sse::Event;
@ -77,6 +77,9 @@ const SYNC_TOLERANCE_EPOCHS: u64 = 8;
/// A custom type which allows for both unsecured and TLS-enabled HTTP servers. /// A custom type which allows for both unsecured and TLS-enabled HTTP servers.
type HttpServer = (SocketAddr, Pin<Box<dyn Future<Output = ()> + Send>>); type HttpServer = (SocketAddr, Pin<Box<dyn Future<Output = ()> + Send>>);
/// Alias for readability.
pub type ExecutionOptimistic = bool;
/// Configuration used when serving the HTTP server over TLS. /// Configuration used when serving the HTTP server over TLS.
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig { pub struct TlsConfig {
@ -304,7 +307,7 @@ pub fn serve<T: BeaconChainTypes>(
.untuple_one() .untuple_one()
}; };
let eth1_v1 = single_version(V1); let eth_v1 = single_version(V1);
// Create a `warp` filter that provides access to the network globals. // Create a `warp` filter that provides access to the network globals.
let inner_network_globals = ctx.network_globals.clone(); let inner_network_globals = ctx.network_globals.clone();
@ -413,7 +416,7 @@ pub fn serve<T: BeaconChainTypes>(
*/ */
// GET beacon/genesis // GET beacon/genesis
let get_beacon_genesis = eth1_v1 let get_beacon_genesis = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("genesis")) .and(warp::path("genesis"))
.and(warp::path::end()) .and(warp::path::end())
@ -433,7 +436,7 @@ pub fn serve<T: BeaconChainTypes>(
* beacon/states/{state_id} * beacon/states/{state_id}
*/ */
let beacon_states_path = eth1_v1 let beacon_states_path = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("states")) .and(warp::path("states"))
.and(warp::path::param::<StateId>().or_else(|_| async { .and(warp::path::param::<StateId>().or_else(|_| async {
@ -450,10 +453,12 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end()) .and(warp::path::end())
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| { .and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
state_id let (root, execution_optimistic) = state_id.root(&chain)?;
.root(&chain)
Ok(root)
.map(api_types::RootData::from) .map(api_types::RootData::from)
.map(api_types::GenericResponse::from) .map(api_types::GenericResponse::from)
.map(|resp| resp.add_execution_optimistic(execution_optimistic))
}) })
}); });
@ -463,7 +468,14 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path("fork")) .and(warp::path("fork"))
.and(warp::path::end()) .and(warp::path::end())
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| { .and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || state_id.fork(&chain).map(api_types::GenericResponse::from)) blocking_json_task(move || {
let (fork, execution_optimistic) =
state_id.fork_and_execution_optimistic(&chain)?;
Ok(api_types::ExecutionOptimisticResponse {
data: fork,
execution_optimistic: Some(execution_optimistic),
})
})
}); });
// GET beacon/states/{state_id}/finality_checkpoints // GET beacon/states/{state_id}/finality_checkpoints
@ -473,16 +485,25 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end()) .and(warp::path::end())
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| { .and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
state_id let (data, execution_optimistic) = state_id.map_state_and_execution_optimistic(
.map_state(&chain, |state| { &chain,
Ok(api_types::FinalityCheckpointsData { |state, execution_optimistic| {
Ok((
api_types::FinalityCheckpointsData {
previous_justified: state.previous_justified_checkpoint(), previous_justified: state.previous_justified_checkpoint(),
current_justified: state.current_justified_checkpoint(), current_justified: state.current_justified_checkpoint(),
finalized: state.finalized_checkpoint(), finalized: state.finalized_checkpoint(),
},
execution_optimistic,
))
},
)?;
Ok(api_types::ExecutionOptimisticResponse {
data,
execution_optimistic: Some(execution_optimistic),
}) })
}) })
.map(api_types::GenericResponse::from)
})
}); });
// GET beacon/states/{state_id}/validator_balances?id // GET beacon/states/{state_id}/validator_balances?id
@ -497,9 +518,12 @@ pub fn serve<T: BeaconChainTypes>(
query_res: Result<api_types::ValidatorBalancesQuery, warp::Rejection>| { query_res: Result<api_types::ValidatorBalancesQuery, warp::Rejection>| {
blocking_json_task(move || { blocking_json_task(move || {
let query = query_res?; let query = query_res?;
state_id let (data, execution_optimistic) = state_id
.map_state(&chain, |state| { .map_state_and_execution_optimistic(
Ok(state &chain,
|state, execution_optimistic| {
Ok((
state
.validators() .validators()
.iter() .iter()
.zip(state.balances().iter()) .zip(state.balances().iter())
@ -523,9 +547,16 @@ pub fn serve<T: BeaconChainTypes>(
balance: *balance, balance: *balance,
}) })
}) })
.collect::<Vec<_>>()) .collect::<Vec<_>>(),
execution_optimistic,
))
},
)?;
Ok(api_types::ExecutionOptimisticResponse {
data,
execution_optimistic: Some(execution_optimistic),
}) })
.map(api_types::GenericResponse::from)
}) })
}, },
); );
@ -542,12 +573,15 @@ pub fn serve<T: BeaconChainTypes>(
query_res: Result<api_types::ValidatorsQuery, warp::Rejection>| { query_res: Result<api_types::ValidatorsQuery, warp::Rejection>| {
blocking_json_task(move || { blocking_json_task(move || {
let query = query_res?; let query = query_res?;
state_id let (data, execution_optimistic) = state_id
.map_state(&chain, |state| { .map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let epoch = state.current_epoch(); let epoch = state.current_epoch();
let far_future_epoch = chain.spec.far_future_epoch; let far_future_epoch = chain.spec.far_future_epoch;
Ok(state Ok((
state
.validators() .validators()
.iter() .iter()
.zip(state.balances().iter()) .zip(state.balances().iter())
@ -590,9 +624,16 @@ pub fn serve<T: BeaconChainTypes>(
None None
} }
}) })
.collect::<Vec<_>>()) .collect::<Vec<_>>(),
execution_optimistic,
))
},
)?;
Ok(api_types::ExecutionOptimisticResponse {
data,
execution_optimistic: Some(execution_optimistic),
}) })
.map(api_types::GenericResponse::from)
}) })
}, },
); );
@ -610,8 +651,10 @@ pub fn serve<T: BeaconChainTypes>(
.and_then( .and_then(
|state_id: StateId, chain: Arc<BeaconChain<T>>, validator_id: ValidatorId| { |state_id: StateId, chain: Arc<BeaconChain<T>>, validator_id: ValidatorId| {
blocking_json_task(move || { blocking_json_task(move || {
state_id let (data, execution_optimistic) = state_id
.map_state(&chain, |state| { .map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let index_opt = match &validator_id { let index_opt = match &validator_id {
ValidatorId::PublicKey(pubkey) => { ValidatorId::PublicKey(pubkey) => {
state.validators().iter().position(|v| v.pubkey == *pubkey) state.validators().iter().position(|v| v.pubkey == *pubkey)
@ -619,6 +662,7 @@ pub fn serve<T: BeaconChainTypes>(
ValidatorId::Index(index) => Some(*index as usize), ValidatorId::Index(index) => Some(*index as usize),
}; };
Ok((
index_opt index_opt
.and_then(|index| { .and_then(|index| {
let validator = state.validators().get(index)?; let validator = state.validators().get(index)?;
@ -642,10 +686,17 @@ pub fn serve<T: BeaconChainTypes>(
"unknown validator: {}", "unknown validator: {}",
validator_id validator_id
)) ))
})?,
execution_optimistic,
))
},
)?;
Ok(api_types::ExecutionOptimisticResponse {
data,
execution_optimistic: Some(execution_optimistic),
}) })
}) })
.map(api_types::GenericResponse::from)
})
}, },
); );
@ -658,14 +709,18 @@ pub fn serve<T: BeaconChainTypes>(
.and_then( .and_then(
|state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::CommitteesQuery| { |state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::CommitteesQuery| {
blocking_json_task(move || { blocking_json_task(move || {
state_id.map_state(&chain, |state| { let (data, execution_optimistic) = state_id
.map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let current_epoch = state.current_epoch(); let current_epoch = state.current_epoch();
let epoch = query.epoch.unwrap_or(current_epoch); let epoch = query.epoch.unwrap_or(current_epoch);
let committee_cache = match RelativeEpoch::from_epoch(current_epoch, epoch) let committee_cache =
{ match RelativeEpoch::from_epoch(current_epoch, epoch) {
Ok(relative_epoch) Ok(relative_epoch)
if state.committee_cache_is_initialized(relative_epoch) => if state
.committee_cache_is_initialized(relative_epoch) =>
{ {
state.committee_cache(relative_epoch).map(Cow::Borrowed) state.committee_cache(relative_epoch).map(Cow::Borrowed)
} }
@ -674,9 +729,11 @@ pub fn serve<T: BeaconChainTypes>(
} }
.map_err(|e| match e { .map_err(|e| match e {
BeaconStateError::EpochOutOfBounds => { BeaconStateError::EpochOutOfBounds => {
let max_sprp = T::EthSpec::slots_per_historical_root() as u64; let max_sprp =
let first_subsequent_restore_point_slot = T::EthSpec::slots_per_historical_root() as u64;
((epoch.start_slot(T::EthSpec::slots_per_epoch()) / max_sprp) let first_subsequent_restore_point_slot = ((epoch
.start_slot(T::EthSpec::slots_per_epoch())
/ max_sprp)
+ 1) + 1)
* max_sprp; * max_sprp;
if epoch < current_epoch { if epoch < current_epoch {
@ -694,12 +751,14 @@ pub fn serve<T: BeaconChainTypes>(
})?; })?;
// Use either the supplied slot or all slots in the epoch. // Use either the supplied slot or all slots in the epoch.
let slots = query.slot.map(|slot| vec![slot]).unwrap_or_else(|| { let slots =
query.slot.map(|slot| vec![slot]).unwrap_or_else(|| {
epoch.slot_iter(T::EthSpec::slots_per_epoch()).collect() epoch.slot_iter(T::EthSpec::slots_per_epoch()).collect()
}); });
// Use either the supplied committee index or all available indices. // Use either the supplied committee index or all available indices.
let indices = query.index.map(|index| vec![index]).unwrap_or_else(|| { let indices =
query.index.map(|index| vec![index]).unwrap_or_else(|| {
(0..committee_cache.committees_per_slot()).collect() (0..committee_cache.committees_per_slot()).collect()
}); });
@ -709,10 +768,9 @@ pub fn serve<T: BeaconChainTypes>(
// It is not acceptable to query with a slot that is not within the // It is not acceptable to query with a slot that is not within the
// specified epoch. // specified epoch.
if slot.epoch(T::EthSpec::slots_per_epoch()) != epoch { if slot.epoch(T::EthSpec::slots_per_epoch()) != epoch {
return Err(warp_utils::reject::custom_bad_request(format!( return Err(warp_utils::reject::custom_bad_request(
"{} is not in epoch {}", format!("{} is not in epoch {}", slot, epoch),
slot, epoch ));
)));
} }
for &index in &indices { for &index in &indices {
@ -737,7 +795,12 @@ pub fn serve<T: BeaconChainTypes>(
} }
} }
Ok(api_types::GenericResponse::from(response)) Ok((response, execution_optimistic))
},
)?;
Ok(api_types::ExecutionOptimisticResponse {
data,
execution_optimistic: Some(execution_optimistic),
}) })
}) })
}, },
@ -754,9 +817,13 @@ pub fn serve<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>, chain: Arc<BeaconChain<T>>,
query: api_types::SyncCommitteesQuery| { query: api_types::SyncCommitteesQuery| {
blocking_json_task(move || { blocking_json_task(move || {
let sync_committee = state_id.map_state(&chain, |state| { let (sync_committee, execution_optimistic) = state_id
.map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let current_epoch = state.current_epoch(); let current_epoch = state.current_epoch();
let epoch = query.epoch.unwrap_or(current_epoch); let epoch = query.epoch.unwrap_or(current_epoch);
Ok((
state state
.get_built_sync_committee(epoch, &chain.spec) .get_built_sync_committee(epoch, &chain.spec)
.map(|committee| committee.clone()) .map(|committee| committee.clone())
@ -774,8 +841,11 @@ pub fn serve<T: BeaconChainTypes>(
)) ))
} }
e => warp_utils::reject::beacon_state_error(e), e => warp_utils::reject::beacon_state_error(e),
}) })?,
})?; execution_optimistic,
))
},
)?;
let validators = chain let validators = chain
.validator_indices(sync_committee.pubkeys.iter()) .validator_indices(sync_committee.pubkeys.iter())
@ -793,7 +863,8 @@ pub fn serve<T: BeaconChainTypes>(
validator_aggregates, validator_aggregates,
}; };
Ok(api_types::GenericResponse::from(response)) Ok(api_types::GenericResponse::from(response)
.add_execution_optimistic(execution_optimistic))
}) })
}, },
); );
@ -805,7 +876,7 @@ pub fn serve<T: BeaconChainTypes>(
// things. Returning non-canonical things is hard for us since we don't already have a // things. Returning non-canonical things is hard for us since we don't already have a
// mechanism for arbitrary forwards block iteration, we only support iterating forwards along // mechanism for arbitrary forwards block iteration, we only support iterating forwards along
// the canonical chain. // the canonical chain.
let get_beacon_headers = eth1_v1 let get_beacon_headers = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("headers")) .and(warp::path("headers"))
.and(warp::query::<api_types::HeadersQuery>()) .and(warp::query::<api_types::HeadersQuery>())
@ -814,15 +885,24 @@ pub fn serve<T: BeaconChainTypes>(
.and_then( .and_then(
|query: api_types::HeadersQuery, chain: Arc<BeaconChain<T>>| { |query: api_types::HeadersQuery, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
let (root, block) = match (query.slot, query.parent_root) { let (root, block, execution_optimistic) = match (query.slot, query.parent_root)
{
// No query parameters, return the canonical head block. // No query parameters, return the canonical head block.
(None, None) => { (None, None) => {
let block = chain.head_beacon_block(); let (cached_head, execution_status) = chain
(block.canonical_root(), block.clone_as_blinded()) .canonical_head
.head_and_execution_status()
.map_err(warp_utils::reject::beacon_chain_error)?;
(
cached_head.head_block_root(),
cached_head.snapshot.beacon_block.clone_as_blinded(),
execution_status.is_optimistic(),
)
} }
// Only the parent root parameter, do a forwards-iterator lookup. // Only the parent root parameter, do a forwards-iterator lookup.
(None, Some(parent_root)) => { (None, Some(parent_root)) => {
let parent = BlockId::from_root(parent_root).blinded_block(&chain)?; let (parent, execution_optimistic) =
BlockId::from_root(parent_root).blinded_block(&chain)?;
let (root, _slot) = chain let (root, _slot) = chain
.forwards_iter_block_roots(parent.slot()) .forwards_iter_block_roots(parent.slot())
.map_err(warp_utils::reject::beacon_chain_error)? .map_err(warp_utils::reject::beacon_chain_error)?
@ -841,13 +921,21 @@ pub fn serve<T: BeaconChainTypes>(
BlockId::from_root(root) BlockId::from_root(root)
.blinded_block(&chain) .blinded_block(&chain)
.map(|block| (root, block))? // Ignore this `execution_optimistic` since the first value has
// more information about the original request.
.map(|(block, _execution_optimistic)| {
(root, block, execution_optimistic)
})?
} }
// Slot is supplied, search by slot and optionally filter by // Slot is supplied, search by slot and optionally filter by
// parent root. // parent root.
(Some(slot), parent_root_opt) => { (Some(slot), parent_root_opt) => {
let root = BlockId::from_slot(slot).root(&chain)?; let (root, execution_optimistic) =
let block = BlockId::from_root(root).blinded_block(&chain)?; BlockId::from_slot(slot).root(&chain)?;
// Ignore the second `execution_optimistic`, the first one is the
// most relevant since it knows that we queried by slot.
let (block, _execution_optimistic) =
BlockId::from_root(root).blinded_block(&chain)?;
// If the parent root was supplied, check that it matches the block // If the parent root was supplied, check that it matches the block
// obtained via a slot lookup. // obtained via a slot lookup.
@ -860,7 +948,7 @@ pub fn serve<T: BeaconChainTypes>(
} }
} }
(root, block) (root, block, execution_optimistic)
} }
}; };
@ -873,13 +961,14 @@ pub fn serve<T: BeaconChainTypes>(
}, },
}; };
Ok(api_types::GenericResponse::from(vec![data])) Ok(api_types::GenericResponse::from(vec![data])
.add_execution_optimistic(execution_optimistic))
}) })
}, },
); );
// GET beacon/headers/{block_id} // GET beacon/headers/{block_id}
let get_beacon_headers_block_id = eth1_v1 let get_beacon_headers_block_id = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("headers")) .and(warp::path("headers"))
.and(warp::path::param::<BlockId>().or_else(|_| async { .and(warp::path::param::<BlockId>().or_else(|_| async {
@ -891,8 +980,11 @@ pub fn serve<T: BeaconChainTypes>(
.and(chain_filter.clone()) .and(chain_filter.clone())
.and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| { .and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
let root = block_id.root(&chain)?; let (root, execution_optimistic) = block_id.root(&chain)?;
let block = BlockId::from_root(root).blinded_block(&chain)?; // Ignore the second `execution_optimistic` since the first one has more
// information about the original request.
let (block, _execution_optimistic) =
BlockId::from_root(root).blinded_block(&chain)?;
let canonical = chain let canonical = chain
.block_root_at_slot(block.slot(), WhenSlotSkipped::None) .block_root_at_slot(block.slot(), WhenSlotSkipped::None)
@ -908,7 +1000,10 @@ pub fn serve<T: BeaconChainTypes>(
}, },
}; };
Ok(api_types::GenericResponse::from(data)) Ok(api_types::ExecutionOptimisticResponse {
execution_optimistic: Some(execution_optimistic),
data,
})
}) })
}); });
@ -917,7 +1012,7 @@ pub fn serve<T: BeaconChainTypes>(
*/ */
// POST beacon/blocks // POST beacon/blocks
let post_beacon_blocks = eth1_v1 let post_beacon_blocks = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("blocks")) .and(warp::path("blocks"))
.and(warp::path::end()) .and(warp::path::end())
@ -1013,7 +1108,7 @@ pub fn serve<T: BeaconChainTypes>(
*/ */
// POST beacon/blocks // POST beacon/blocks
let post_beacon_blinded_blocks = eth1_v1 let post_beacon_blinded_blocks = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("blinded_blocks")) .and(warp::path("blinded_blocks"))
.and(warp::path::end()) .and(warp::path::end())
@ -1115,7 +1210,7 @@ pub fn serve<T: BeaconChainTypes>(
)) ))
}); });
let beacon_blocks_path_v1 = eth1_v1 let beacon_blocks_path_v1 = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("blocks")) .and(warp::path("blocks"))
.and(block_id_or_err) .and(block_id_or_err)
@ -1138,10 +1233,11 @@ pub fn serve<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>, chain: Arc<BeaconChain<T>>,
accept_header: Option<api_types::Accept>| { accept_header: Option<api_types::Accept>| {
async move { async move {
let block = block_id.full_block(&chain).await?; let (block, execution_optimistic) = block_id.full_block(&chain).await?;
let fork_name = block let fork_name = block
.fork_name(&chain.spec) .fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?; .map_err(inconsistent_fork_rejection)?;
match accept_header { match accept_header {
Some(api_types::Accept::Ssz) => Response::builder() Some(api_types::Accept::Ssz) => Response::builder()
.status(200) .status(200)
@ -1153,7 +1249,12 @@ pub fn serve<T: BeaconChainTypes>(
e e
)) ))
}), }),
_ => fork_versioned_response(endpoint_version, fork_name, block) _ => execution_optimistic_fork_versioned_response(
endpoint_version,
fork_name,
execution_optimistic,
block,
)
.map(|res| warp::reply::json(&res).into_response()), .map(|res| warp::reply::json(&res).into_response()),
} }
.map(|resp| add_consensus_version_header(resp, fork_name)) .map(|resp| add_consensus_version_header(resp, fork_name))
@ -1168,10 +1269,12 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end()) .and(warp::path::end())
.and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| { .and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
block_id let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
.root(&chain)
.map(api_types::RootData::from) Ok(api_types::GenericResponse::from(api_types::RootData::from(
.map(api_types::GenericResponse::from) block.canonical_root(),
))
.add_execution_optimistic(execution_optimistic))
}) })
}); });
@ -1182,10 +1285,12 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end()) .and(warp::path::end())
.and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| { .and_then(|block_id: BlockId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
block_id let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
.blinded_block(&chain)
.map(|block| block.message().body().attestations().clone()) Ok(
.map(api_types::GenericResponse::from) api_types::GenericResponse::from(block.message().body().attestations().clone())
.add_execution_optimistic(execution_optimistic),
)
}) })
}); });
@ -1193,7 +1298,7 @@ pub fn serve<T: BeaconChainTypes>(
* beacon/pool * beacon/pool
*/ */
let beacon_pool_path = eth1_v1 let beacon_pool_path = eth_v1
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("pool")) .and(warp::path("pool"))
.and(chain_filter.clone()); .and(chain_filter.clone());
@ -1519,7 +1624,7 @@ pub fn serve<T: BeaconChainTypes>(
* config * config
*/ */
let config_path = eth1_v1.and(warp::path("config")); let config_path = eth_v1.and(warp::path("config"));
// GET config/fork_schedule // GET config/fork_schedule
let get_config_fork_schedule = config_path let get_config_fork_schedule = config_path
@ -1593,7 +1698,10 @@ pub fn serve<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>| { chain: Arc<BeaconChain<T>>| {
blocking_task(move || match accept_header { blocking_task(move || match accept_header {
Some(api_types::Accept::Ssz) => { Some(api_types::Accept::Ssz) => {
let state = state_id.state(&chain)?; // We can ignore the optimistic status for the "fork" since it's a
// specification constant that doesn't change across competing heads of the
// beacon chain.
let (state, _execution_optimistic) = state_id.state(&chain)?;
let fork_name = state let fork_name = state
.fork_name(&chain.spec) .fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?; .map_err(inconsistent_fork_rejection)?;
@ -1609,44 +1717,71 @@ pub fn serve<T: BeaconChainTypes>(
)) ))
}) })
} }
_ => state_id.map_state(&chain, |state| { _ => state_id.map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let fork_name = state let fork_name = state
.fork_name(&chain.spec) .fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?; .map_err(inconsistent_fork_rejection)?;
let res = fork_versioned_response(endpoint_version, fork_name, &state)?; let res = execution_optimistic_fork_versioned_response(
endpoint_version,
fork_name,
execution_optimistic,
&state,
)?;
Ok(add_consensus_version_header( Ok(add_consensus_version_header(
warp::reply::json(&res).into_response(), warp::reply::json(&res).into_response(),
fork_name, fork_name,
)) ))
}), },
),
}) })
}, },
); );
// GET debug/beacon/heads // GET debug/beacon/heads
let get_debug_beacon_heads = eth1_v1 let get_debug_beacon_heads = any_version
.and(warp::path("debug")) .and(warp::path("debug"))
.and(warp::path("beacon")) .and(warp::path("beacon"))
.and(warp::path("heads")) .and(warp::path("heads"))
.and(warp::path::end()) .and(warp::path::end())
.and(chain_filter.clone()) .and(chain_filter.clone())
.and_then(|chain: Arc<BeaconChain<T>>| { .and_then(
|endpoint_version: EndpointVersion, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || { blocking_json_task(move || {
let heads = chain let heads = chain
.heads() .heads()
.into_iter() .into_iter()
.map(|(root, slot)| api_types::ChainHeadData { slot, root }) .map(|(root, slot)| {
.collect::<Vec<_>>(); let execution_optimistic = if endpoint_version == V1 {
Ok(api_types::GenericResponse::from(heads)) None
} else if endpoint_version == V2 {
chain
.canonical_head
.fork_choice_read_lock()
.is_optimistic_block(&root)
.ok()
} else {
return Err(unsupported_version_rejection(endpoint_version));
};
Ok(api_types::ChainHeadData {
slot,
root,
execution_optimistic,
}) })
}); })
.collect::<Result<Vec<_>, warp::Rejection>>();
Ok(api_types::GenericResponse::from(heads?))
})
},
);
/* /*
* node * node
*/ */
// GET node/identity // GET node/identity
let get_node_identity = eth1_v1 let get_node_identity = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("identity")) .and(warp::path("identity"))
.and(warp::path::end()) .and(warp::path::end())
@ -1684,7 +1819,7 @@ pub fn serve<T: BeaconChainTypes>(
}); });
// GET node/version // GET node/version
let get_node_version = eth1_v1 let get_node_version = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("version")) .and(warp::path("version"))
.and(warp::path::end()) .and(warp::path::end())
@ -1697,7 +1832,7 @@ pub fn serve<T: BeaconChainTypes>(
}); });
// GET node/syncing // GET node/syncing
let get_node_syncing = eth1_v1 let get_node_syncing = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("syncing")) .and(warp::path("syncing"))
.and(warp::path::end()) .and(warp::path::end())
@ -1726,7 +1861,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET node/health // GET node/health
let get_node_health = eth1_v1 let get_node_health = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("health")) .and(warp::path("health"))
.and(warp::path::end()) .and(warp::path::end())
@ -1751,7 +1886,7 @@ pub fn serve<T: BeaconChainTypes>(
}); });
// GET node/peers/{peer_id} // GET node/peers/{peer_id}
let get_node_peers_by_id = eth1_v1 let get_node_peers_by_id = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("peers")) .and(warp::path("peers"))
.and(warp::path::param::<String>()) .and(warp::path::param::<String>())
@ -1808,7 +1943,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET node/peers // GET node/peers
let get_node_peers = eth1_v1 let get_node_peers = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("peers")) .and(warp::path("peers"))
.and(warp::path::end()) .and(warp::path::end())
@ -1877,7 +2012,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET node/peer_count // GET node/peer_count
let get_node_peer_count = eth1_v1 let get_node_peer_count = eth_v1
.and(warp::path("node")) .and(warp::path("node"))
.and(warp::path("peer_count")) .and(warp::path("peer_count"))
.and(warp::path::end()) .and(warp::path::end())
@ -1918,7 +2053,7 @@ pub fn serve<T: BeaconChainTypes>(
*/ */
// GET validator/duties/proposer/{epoch} // GET validator/duties/proposer/{epoch}
let get_validator_duties_proposer = eth1_v1 let get_validator_duties_proposer = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("duties")) .and(warp::path("duties"))
.and(warp::path("proposer")) .and(warp::path("proposer"))
@ -2061,7 +2196,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET validator/attestation_data?slot,committee_index // GET validator/attestation_data?slot,committee_index
let get_validator_attestation_data = eth1_v1 let get_validator_attestation_data = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("attestation_data")) .and(warp::path("attestation_data"))
.and(warp::path::end()) .and(warp::path::end())
@ -2093,7 +2228,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET validator/aggregate_attestation?attestation_data_root,slot // GET validator/aggregate_attestation?attestation_data_root,slot
let get_validator_aggregate_attestation = eth1_v1 let get_validator_aggregate_attestation = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("aggregate_attestation")) .and(warp::path("aggregate_attestation"))
.and(warp::path::end()) .and(warp::path::end())
@ -2125,7 +2260,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/duties/attester/{epoch} // POST validator/duties/attester/{epoch}
let post_validator_duties_attester = eth1_v1 let post_validator_duties_attester = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("duties")) .and(warp::path("duties"))
.and(warp::path("attester")) .and(warp::path("attester"))
@ -2147,7 +2282,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/duties/sync // POST validator/duties/sync
let post_validator_duties_sync = eth1_v1 let post_validator_duties_sync = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("duties")) .and(warp::path("duties"))
.and(warp::path("sync")) .and(warp::path("sync"))
@ -2169,7 +2304,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// GET validator/sync_committee_contribution // GET validator/sync_committee_contribution
let get_validator_sync_committee_contribution = eth1_v1 let get_validator_sync_committee_contribution = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("sync_committee_contribution")) .and(warp::path("sync_committee_contribution"))
.and(warp::path::end()) .and(warp::path::end())
@ -2192,7 +2327,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/aggregate_and_proofs // POST validator/aggregate_and_proofs
let post_validator_aggregate_and_proofs = eth1_v1 let post_validator_aggregate_and_proofs = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("aggregate_and_proofs")) .and(warp::path("aggregate_and_proofs"))
.and(warp::path::end()) .and(warp::path::end())
@ -2292,7 +2427,7 @@ pub fn serve<T: BeaconChainTypes>(
}, },
); );
let post_validator_contribution_and_proofs = eth1_v1 let post_validator_contribution_and_proofs = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("contribution_and_proofs")) .and(warp::path("contribution_and_proofs"))
.and(warp::path::end()) .and(warp::path::end())
@ -2319,7 +2454,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/beacon_committee_subscriptions // POST validator/beacon_committee_subscriptions
let post_validator_beacon_committee_subscriptions = eth1_v1 let post_validator_beacon_committee_subscriptions = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("beacon_committee_subscriptions")) .and(warp::path("beacon_committee_subscriptions"))
.and(warp::path::end()) .and(warp::path::end())
@ -2359,7 +2494,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/prepare_beacon_proposer // POST validator/prepare_beacon_proposer
let post_validator_prepare_beacon_proposer = eth1_v1 let post_validator_prepare_beacon_proposer = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("prepare_beacon_proposer")) .and(warp::path("prepare_beacon_proposer"))
.and(warp::path::end()) .and(warp::path::end())
@ -2407,7 +2542,7 @@ pub fn serve<T: BeaconChainTypes>(
); );
// POST validator/register_validator // POST validator/register_validator
let post_validator_register_validator = eth1_v1 let post_validator_register_validator = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("register_validator")) .and(warp::path("register_validator"))
.and(warp::path::end()) .and(warp::path::end())
@ -2480,7 +2615,7 @@ pub fn serve<T: BeaconChainTypes>(
}, },
); );
// POST validator/sync_committee_subscriptions // POST validator/sync_committee_subscriptions
let post_validator_sync_committee_subscriptions = eth1_v1 let post_validator_sync_committee_subscriptions = eth_v1
.and(warp::path("validator")) .and(warp::path("validator"))
.and(warp::path("sync_committee_subscriptions")) .and(warp::path("sync_committee_subscriptions"))
.and(warp::path::end()) .and(warp::path::end())
@ -2760,7 +2895,8 @@ pub fn serve<T: BeaconChainTypes>(
.and(chain_filter.clone()) .and(chain_filter.clone())
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| { .and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
blocking_task(move || { blocking_task(move || {
let state = state_id.state(&chain)?; // This debug endpoint provides no indication of optimistic status.
let (state, _execution_optimistic) = state_id.state(&chain)?;
Response::builder() Response::builder()
.status(200) .status(200)
.header("Content-Type", "application/ssz") .header("Content-Type", "application/ssz")
@ -2899,7 +3035,7 @@ pub fn serve<T: BeaconChainTypes>(
))) )))
}); });
let get_events = eth1_v1 let get_events = eth_v1
.and(warp::path("events")) .and(warp::path("events"))
.and(warp::path::end()) .and(warp::path::end())
.and(multi_key_query::<api_types::EventQuery>()) .and(multi_key_query::<api_types::EventQuery>())

View File

@ -55,10 +55,16 @@ pub fn proposer_duties<T: BeaconChainTypes>(
.safe_add(1) .safe_add(1)
.map_err(warp_utils::reject::arith_error)? .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) compute_proposer_duties_from_head(request_epoch, chain)
.map_err(warp_utils::reject::beacon_chain_error)?; .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 } else if request_epoch
> current_epoch > current_epoch
.safe_add(1) .safe_add(1)
@ -88,17 +94,18 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
request_epoch: Epoch, request_epoch: Epoch,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<Option<ApiDuties>, warp::reject::Rejection> { ) -> Result<Option<ApiDuties>, warp::reject::Rejection> {
let (head_slot, head_block_root, head_decision_root) = {
let head = chain.canonical_head.cached_head(); let head = chain.canonical_head.cached_head();
let head_block = &head.snapshot.beacon_block;
let head_block_root = head.head_block_root(); let head_block_root = head.head_block_root();
let decision_root = head let head_decision_root = head
.snapshot .snapshot
.beacon_state .beacon_state
.proposer_shuffling_decision_root(head_block_root) .proposer_shuffling_decision_root(head_block_root)
.map_err(warp_utils::reject::beacon_state_error)?; .map_err(warp_utils::reject::beacon_state_error)?;
(head.head_slot(), head_block_root, decision_root) let head_epoch = head_block.slot().epoch(T::EthSpec::slots_per_epoch());
}; let execution_optimistic = chain
let head_epoch = head_slot.epoch(T::EthSpec::slots_per_epoch()); .is_optimistic_head_block(head_block)
.map_err(warp_utils::reject::beacon_chain_error)?;
let dependent_root = match head_epoch.cmp(&request_epoch) { let dependent_root = match head_epoch.cmp(&request_epoch) {
// head_epoch == 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) .get_epoch::<T::EthSpec>(dependent_root, request_epoch)
.cloned() .cloned()
.map(|indices| { .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() .transpose()
} }
@ -139,7 +152,7 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
current_epoch: Epoch, current_epoch: Epoch,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> 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) compute_proposer_duties_from_head(current_epoch, chain)
.map_err(warp_utils::reject::beacon_chain_error)?; .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(BeaconChainError::from)
.map_err(warp_utils::reject::beacon_chain_error)?; .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 /// Compute some proposer duties by reading a `BeaconState` from disk, completely ignoring the
@ -162,29 +181,35 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> Result<ApiDuties, warp::reject::Rejection> {
// If the head is quite old then it might still be relevant for a historical request. // 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. // Avoid holding the `cached_head` longer than necessary.
let state_opt = chain let state_opt = {
.with_head(|head| { 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;
if head.beacon_state.current_epoch() <= epoch { if head.beacon_state.current_epoch() <= epoch {
Ok(Some(( Some((
head.beacon_state_root(), head.beacon_state_root(),
head.beacon_state head.beacon_state
.clone_with(CloneConfig::committee_caches_only()), .clone_with(CloneConfig::committee_caches_only()),
))) execution_status.is_optimistic(),
))
} else { } else {
Ok(None) None
} }
}) };
.map_err(warp_utils::reject::beacon_chain_error)?;
let state = if let Some((state_root, mut state)) = state_opt { 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 // If we've loaded the head state it might be from a previous epoch, ensure it's in a
// suitable epoch. // suitable epoch.
ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec) ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec)
.map_err(warp_utils::reject::beacon_chain_error)?; .map_err(warp_utils::reject::beacon_chain_error)?;
state (state, execution_optimistic)
} else { } else {
StateId::slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)? StateId::from_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
}; };
// Ensure the state lookup was correct. // Ensure the state lookup was correct.
@ -208,7 +233,7 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
.map_err(BeaconChainError::from) .map_err(BeaconChainError::from)
.map_err(warp_utils::reject::beacon_chain_error)?; .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 /// 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>, chain: &BeaconChain<T>,
epoch: Epoch, epoch: Epoch,
dependent_root: Hash256, dependent_root: Hash256,
execution_optimistic: bool,
indices: Vec<usize>, indices: Vec<usize>,
) -> Result<ApiDuties, warp::reject::Rejection> { ) -> Result<ApiDuties, warp::reject::Rejection> {
let index_to_pubkey_map = chain let index_to_pubkey_map = chain
@ -251,6 +277,7 @@ fn convert_to_api_response<T: BeaconChainTypes>(
} else { } else {
Ok(api_types::DutiesResponse { Ok(api_types::DutiesResponse {
dependent_root, dependent_root,
execution_optimistic: Some(execution_optimistic),
data: proposer_data, 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 eth2::types::StateId as CoreStateId;
use std::fmt;
use std::str::FromStr; 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 /// Wraps `eth2::types::StateId` and provides common state-access functionality. E.g., reading
/// states or parts of states from the database. /// states or parts of states from the database.
pub struct StateId(CoreStateId); #[derive(Debug)]
pub struct StateId(pub CoreStateId);
impl StateId { impl StateId {
pub fn slot(slot: Slot) -> Self { pub fn from_slot(slot: Slot) -> Self {
Self(CoreStateId::Slot(slot)) Self(CoreStateId::Slot(slot))
} }
@ -16,54 +19,128 @@ impl StateId {
pub fn root<T: BeaconChainTypes>( pub fn root<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<Hash256, warp::Rejection> { ) -> Result<(Hash256, ExecutionOptimistic), warp::Rejection> {
let slot = match &self.0 { let (slot, execution_optimistic) = match &self.0 {
CoreStateId::Head => return Ok(chain.canonical_head.cached_head().head_state_root()), CoreStateId::Head => {
CoreStateId::Genesis => return Ok(chain.genesis_state_root), let (cached_head, execution_status) = chain
CoreStateId::Finalized => chain
.canonical_head .canonical_head
.cached_head() .head_and_execution_status()
.finalized_checkpoint() .map_err(warp_utils::reject::beacon_chain_error)?;
.epoch return Ok((
.start_slot(T::EthSpec::slots_per_epoch()), cached_head.head_state_root(),
CoreStateId::Justified => chain 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 .canonical_head
.cached_head() .fork_choice_read_lock()
.justified_checkpoint() .is_optimistic_block_no_fallback(&hot_summary.latest_block_root)
.epoch .map_err(BeaconChainError::ForkChoiceError)
.start_slot(T::EthSpec::slots_per_epoch()), .map_err(warp_utils::reject::beacon_chain_error)?;
CoreStateId::Slot(slot) => *slot, return Ok((*root, execution_optimistic));
CoreStateId::Root(root) => return Ok(*root), } 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) .state_root_at_slot(slot)
.map_err(warp_utils::reject::beacon_chain_error)? .map_err(warp_utils::reject::beacon_chain_error)?
.ok_or_else(|| { .ok_or_else(|| {
warp_utils::reject::custom_not_found(format!("beacon state at slot {}", slot)) 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`. /// 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>( pub fn fork<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<Fork, warp::Rejection> { ) -> 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`. /// Return the `BeaconState` identified by `self`.
pub fn state<T: BeaconChainTypes>( pub fn state<T: BeaconChainTypes>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<BeaconState<T::EthSpec>, warp::Rejection> { ) -> Result<(BeaconState<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
let (state_root, slot_opt) = match &self.0 { let ((state_root, execution_optimistic), slot_opt) = match &self.0 {
CoreStateId::Head => return Ok(chain.head_beacon_state_cloned()), 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)), CoreStateId::Slot(slot) => (self.root(chain)?, Some(*slot)),
_ => (self.root(chain)?, None), _ => (self.root(chain)?, None),
}; };
chain let state = chain
.get_state(&state_root, slot_opt) .get_state(&state_root, slot_opt)
.map_err(warp_utils::reject::beacon_chain_error) .map_err(warp_utils::reject::beacon_chain_error)
.and_then(|opt| { .and_then(|opt| {
@ -73,13 +150,17 @@ impl StateId {
state_root state_root
)) ))
}) })
}) })?;
Ok((state, execution_optimistic))
} }
/*
/// Map a function across the `BeaconState` identified by `self`. /// Map a function across the `BeaconState` identified by `self`.
/// ///
/// This function will avoid instantiating/copying a new state when `self` points to the head /// This function will avoid instantiating/copying a new state when `self` points to the head
/// of the chain. /// of the chain.
#[allow(dead_code)]
pub fn map_state<T: BeaconChainTypes, F, U>( pub fn map_state<T: BeaconChainTypes, F, U>(
&self, &self,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
@ -95,6 +176,36 @@ impl StateId {
_ => func(&self.state(chain)?), _ => 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 { impl FromStr for StateId {
@ -104,3 +215,35 @@ impl FromStr for StateId {
CoreStateId::from_str(s).map(Self) 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. /// 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. /// Handles a request from the HTTP API for sync committee duties.
pub fn sync_committee_duties<T: BeaconChainTypes>( pub fn sync_committee_duties<T: BeaconChainTypes>(
@ -34,14 +34,20 @@ pub fn sync_committee_duties<T: BeaconChainTypes>(
altair_fork_epoch altair_fork_epoch
} else { } else {
// Empty response for networks with Altair disabled. // 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 // 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 // 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. // 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) { 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 { Err(BeaconChainError::SyncDutiesError(BeaconStateError::SyncCommitteeNotKnown {
.. ..
})) }))
@ -60,7 +66,7 @@ pub fn sync_committee_duties<T: BeaconChainTypes>(
)), )),
e => warp_utils::reject::beacon_chain_error(e), 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. /// 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<_>>()) 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. /// 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>, chain: &BeaconChain<T>,
) -> Result<BeaconState<T::EthSpec>, warp::reject::Rejection> { ) -> Result<BeaconState<T::EthSpec>, warp::reject::Rejection> {
let target_slot = epoch.end_slot(T::EthSpec::slots_per_epoch()); 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`. /// 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 eth2::CONSENSUS_VERSION_HEADER;
use serde::Serialize; use serde::Serialize;
use types::{ForkName, InconsistentFork}; 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. /// Add the `Eth-Consensus-Version` header to a response.
pub fn add_consensus_version_header<T: Reply>(reply: T, fork_name: ForkName) -> WithHeader<T> { 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()) reply::with_header(reply, CONSENSUS_VERSION_HEADER, fork_name.to_string())

View File

@ -8,13 +8,15 @@ use environment::null_logger;
use eth2::{ use eth2::{
mixin::{RequestAccept, ResponseForkName, ResponseOptional}, mixin::{RequestAccept, ResponseForkName, ResponseOptional},
reqwest::RequestBuilder, reqwest::RequestBuilder,
types::*, types::{BlockId as CoreBlockId, StateId as CoreStateId, *},
BeaconNodeHttpClient, Error, StatusCode, Timeouts, BeaconNodeHttpClient, Error, StatusCode, Timeouts,
}; };
use futures::stream::{Stream, StreamExt}; use futures::stream::{Stream, StreamExt};
use futures::FutureExt; use futures::FutureExt;
use http_api::{BlockId, StateId};
use lighthouse_network::{Enr, EnrExt, PeerId}; use lighthouse_network::{Enr, EnrExt, PeerId};
use network::NetworkMessage; use network::NetworkMessage;
use proto_array::ExecutionStatus;
use sensitive_url::SensitiveUrl; use sensitive_url::SensitiveUrl;
use slot_clock::SlotClock; use slot_clock::SlotClock;
use state_processing::per_slot_processing; use state_processing::per_slot_processing;
@ -25,8 +27,8 @@ use tokio::time::Duration;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::application_domain::ApplicationDomain; use types::application_domain::ApplicationDomain;
use types::{ use types::{
AggregateSignature, BeaconState, BitList, Domain, EthSpec, Hash256, Keypair, MainnetEthSpec, AggregateSignature, BitList, Domain, EthSpec, ExecutionBlockHash, Hash256, Keypair,
RelativeEpoch, SelectionProof, SignedRoot, Slot, MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot,
}; };
type E = MainnetEthSpec; type E = MainnetEthSpec;
@ -74,6 +76,19 @@ impl ApiTester {
Self::new_from_spec(spec).await Self::new_from_spec(spec).await
} }
pub async fn new_with_hard_forks(altair: bool, bellatrix: bool) -> Self {
let mut spec = E::default_spec();
spec.shard_committee_period = 2;
// Set whether the chain has undergone each hard fork.
if altair {
spec.altair_fork_epoch = Some(Epoch::new(0));
}
if bellatrix {
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
}
Self::new_from_spec(spec).await
}
pub async fn new_from_spec(spec: ChainSpec) -> Self { pub async fn new_from_spec(spec: ChainSpec) -> Self {
let harness = Arc::new( let harness = Arc::new(
BeaconChainHarness::builder(MainnetEthSpec) BeaconChainHarness::builder(MainnetEthSpec)
@ -325,99 +340,43 @@ impl ApiTester {
fn interesting_state_ids(&self) -> Vec<StateId> { fn interesting_state_ids(&self) -> Vec<StateId> {
let mut ids = vec![ let mut ids = vec![
StateId::Head, StateId(CoreStateId::Head),
StateId::Genesis, StateId(CoreStateId::Genesis),
StateId::Finalized, StateId(CoreStateId::Finalized),
StateId::Justified, StateId(CoreStateId::Justified),
StateId::Slot(Slot::new(0)), StateId(CoreStateId::Slot(Slot::new(0))),
StateId::Slot(Slot::new(32)), StateId(CoreStateId::Slot(Slot::new(32))),
StateId::Slot(Slot::from(SKIPPED_SLOTS[0])), StateId(CoreStateId::Slot(Slot::from(SKIPPED_SLOTS[0]))),
StateId::Slot(Slot::from(SKIPPED_SLOTS[1])), StateId(CoreStateId::Slot(Slot::from(SKIPPED_SLOTS[1]))),
StateId::Slot(Slot::from(SKIPPED_SLOTS[2])), StateId(CoreStateId::Slot(Slot::from(SKIPPED_SLOTS[2]))),
StateId::Slot(Slot::from(SKIPPED_SLOTS[3])), StateId(CoreStateId::Slot(Slot::from(SKIPPED_SLOTS[3]))),
StateId::Root(Hash256::zero()), StateId(CoreStateId::Root(Hash256::zero())),
]; ];
ids.push(StateId::Root( ids.push(StateId(CoreStateId::Root(
self.chain.canonical_head.cached_head().head_state_root(), self.chain.canonical_head.cached_head().head_state_root(),
)); )));
ids ids
} }
fn interesting_block_ids(&self) -> Vec<BlockId> { fn interesting_block_ids(&self) -> Vec<BlockId> {
let mut ids = vec![ let mut ids = vec![
BlockId::Head, BlockId(CoreBlockId::Head),
BlockId::Genesis, BlockId(CoreBlockId::Genesis),
BlockId::Finalized, BlockId(CoreBlockId::Finalized),
BlockId::Justified, BlockId(CoreBlockId::Justified),
BlockId::Slot(Slot::new(0)), BlockId(CoreBlockId::Slot(Slot::new(0))),
BlockId::Slot(Slot::new(32)), BlockId(CoreBlockId::Slot(Slot::new(32))),
BlockId::Slot(Slot::from(SKIPPED_SLOTS[0])), BlockId(CoreBlockId::Slot(Slot::from(SKIPPED_SLOTS[0]))),
BlockId::Slot(Slot::from(SKIPPED_SLOTS[1])), BlockId(CoreBlockId::Slot(Slot::from(SKIPPED_SLOTS[1]))),
BlockId::Slot(Slot::from(SKIPPED_SLOTS[2])), BlockId(CoreBlockId::Slot(Slot::from(SKIPPED_SLOTS[2]))),
BlockId::Slot(Slot::from(SKIPPED_SLOTS[3])), BlockId(CoreBlockId::Slot(Slot::from(SKIPPED_SLOTS[3]))),
BlockId::Root(Hash256::zero()), BlockId(CoreBlockId::Root(Hash256::zero())),
]; ];
ids.push(BlockId::Root( ids.push(BlockId(CoreBlockId::Root(
self.chain.canonical_head.cached_head().head_block_root(), self.chain.canonical_head.cached_head().head_block_root(),
)); )));
ids ids
} }
fn get_state(&self, state_id: StateId) -> Option<BeaconState<E>> {
match state_id {
StateId::Head => Some(
self.chain
.head_snapshot()
.beacon_state
.clone_with_only_committee_caches(),
),
StateId::Genesis => self
.chain
.get_state(&self.chain.genesis_state_root, None)
.unwrap(),
StateId::Finalized => {
let finalized_slot = self
.chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(E::slots_per_epoch());
let root = self
.chain
.state_root_at_slot(finalized_slot)
.unwrap()
.unwrap();
self.chain.get_state(&root, Some(finalized_slot)).unwrap()
}
StateId::Justified => {
let justified_slot = self
.chain
.canonical_head
.cached_head()
.justified_checkpoint()
.epoch
.start_slot(E::slots_per_epoch());
let root = self
.chain
.state_root_at_slot(justified_slot)
.unwrap()
.unwrap();
self.chain.get_state(&root, Some(justified_slot)).unwrap()
}
StateId::Slot(slot) => {
let root = self.chain.state_root_at_slot(slot).unwrap().unwrap();
self.chain.get_state(&root, Some(slot)).unwrap()
}
StateId::Root(root) => self.chain.get_state(&root, None).unwrap(),
}
}
pub async fn test_beacon_genesis(self) -> Self { pub async fn test_beacon_genesis(self) -> Self {
let result = self.client.get_beacon_genesis().await.unwrap().data; let result = self.client.get_beacon_genesis().await.unwrap().data;
@ -437,39 +396,15 @@ impl ApiTester {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let result = self let result = self
.client .client
.get_beacon_states_root(state_id) .get_beacon_states_root(state_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data.root); .map(|res| res.data.root);
let expected = match state_id { let expected = state_id
StateId::Head => Some(self.chain.canonical_head.cached_head().head_state_root()), .root(&self.chain)
StateId::Genesis => Some(self.chain.genesis_state_root), .ok()
StateId::Finalized => { .map(|(root, _execution_optimistic)| root);
let finalized_slot = self
.chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(E::slots_per_epoch());
self.chain.state_root_at_slot(finalized_slot).unwrap()
}
StateId::Justified => {
let justified_slot = self
.chain
.canonical_head
.cached_head()
.justified_checkpoint()
.epoch
.start_slot(E::slots_per_epoch());
self.chain.state_root_at_slot(justified_slot).unwrap()
}
StateId::Slot(slot) => self.chain.state_root_at_slot(slot).unwrap(),
StateId::Root(root) => Some(root),
};
assert_eq!(result, expected, "{:?}", state_id); assert_eq!(result, expected, "{:?}", state_id);
} }
@ -481,12 +416,12 @@ impl ApiTester {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let result = self let result = self
.client .client
.get_beacon_states_fork(state_id) .get_beacon_states_fork(state_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
let expected = self.get_state(state_id).map(|state| state.fork()); let expected = state_id.fork(&self.chain).ok();
assert_eq!(result, expected, "{:?}", state_id); assert_eq!(result, expected, "{:?}", state_id);
} }
@ -498,14 +433,16 @@ impl ApiTester {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let result = self let result = self
.client .client
.get_beacon_states_finality_checkpoints(state_id) .get_beacon_states_finality_checkpoints(state_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
let expected = self let expected =
.get_state(state_id) state_id
.map(|state| FinalityCheckpointsData { .state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| FinalityCheckpointsData {
previous_justified: state.previous_justified_checkpoint(), previous_justified: state.previous_justified_checkpoint(),
current_justified: state.current_justified_checkpoint(), current_justified: state.current_justified_checkpoint(),
finalized: state.finalized_checkpoint(), finalized: state.finalized_checkpoint(),
@ -520,9 +457,9 @@ impl ApiTester {
pub async fn test_beacon_states_validator_balances(self) -> Self { pub async fn test_beacon_states_validator_balances(self) -> Self {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
for validator_indices in self.interesting_validator_indices() { for validator_indices in self.interesting_validator_indices() {
let state_opt = self.get_state(state_id); let state_opt = state_id.state(&self.chain).ok();
let validators: Vec<Validator> = match state_opt.as_ref() { let validators: Vec<Validator> = match state_opt.as_ref() {
Some(state) => state.validators().clone().into(), Some((state, _execution_optimistic)) => state.validators().clone().into(),
None => vec![], None => vec![],
}; };
let validator_index_ids = validator_indices let validator_index_ids = validator_indices
@ -545,7 +482,7 @@ impl ApiTester {
let result_index_ids = self let result_index_ids = self
.client .client
.get_beacon_states_validator_balances( .get_beacon_states_validator_balances(
state_id, state_id.0,
Some(validator_index_ids.as_slice()), Some(validator_index_ids.as_slice()),
) )
.await .await
@ -554,14 +491,14 @@ impl ApiTester {
let result_pubkey_ids = self let result_pubkey_ids = self
.client .client
.get_beacon_states_validator_balances( .get_beacon_states_validator_balances(
state_id, state_id.0,
Some(validator_pubkey_ids.as_slice()), Some(validator_pubkey_ids.as_slice()),
) )
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
let expected = state_opt.map(|state| { let expected = state_opt.map(|(state, _execution_optimistic)| {
let mut validators = Vec::with_capacity(validator_indices.len()); let mut validators = Vec::with_capacity(validator_indices.len());
for i in validator_indices { for i in validator_indices {
@ -588,7 +525,10 @@ impl ApiTester {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
for statuses in self.interesting_validator_statuses() { for statuses in self.interesting_validator_statuses() {
for validator_indices in self.interesting_validator_indices() { for validator_indices in self.interesting_validator_indices() {
let state_opt = self.get_state(state_id); let state_opt = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
let validators: Vec<Validator> = match state_opt.as_ref() { let validators: Vec<Validator> = match state_opt.as_ref() {
Some(state) => state.validators().clone().into(), Some(state) => state.validators().clone().into(),
None => vec![], None => vec![],
@ -613,7 +553,7 @@ impl ApiTester {
let result_index_ids = self let result_index_ids = self
.client .client
.get_beacon_states_validators( .get_beacon_states_validators(
state_id, state_id.0,
Some(validator_index_ids.as_slice()), Some(validator_index_ids.as_slice()),
None, None,
) )
@ -624,7 +564,7 @@ impl ApiTester {
let result_pubkey_ids = self let result_pubkey_ids = self
.client .client
.get_beacon_states_validators( .get_beacon_states_validators(
state_id, state_id.0,
Some(validator_pubkey_ids.as_slice()), Some(validator_pubkey_ids.as_slice()),
None, None,
) )
@ -675,7 +615,10 @@ impl ApiTester {
pub async fn test_beacon_states_validator_id(self) -> Self { pub async fn test_beacon_states_validator_id(self) -> Self {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let state_opt = self.get_state(state_id); let state_opt = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
let validators = match state_opt.as_ref() { let validators = match state_opt.as_ref() {
Some(state) => state.validators().clone().into(), Some(state) => state.validators().clone().into(),
None => vec![], None => vec![],
@ -690,7 +633,7 @@ impl ApiTester {
for validator_id in validator_ids { for validator_id in validator_ids {
let result = self let result = self
.client .client
.get_beacon_states_validator_id(state_id, validator_id) .get_beacon_states_validator_id(state_id.0, validator_id)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
@ -727,12 +670,15 @@ impl ApiTester {
pub async fn test_beacon_states_committees(self) -> Self { pub async fn test_beacon_states_committees(self) -> Self {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let mut state_opt = self.get_state(state_id); let mut state_opt = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
let epoch_opt = state_opt.as_ref().map(|state| state.current_epoch()); let epoch_opt = state_opt.as_ref().map(|state| state.current_epoch());
let results = self let results = self
.client .client
.get_beacon_states_committees(state_id, None, None, epoch_opt) .get_beacon_states_committees(state_id.0, None, None, epoch_opt)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
@ -769,37 +715,6 @@ impl ApiTester {
self self
} }
fn get_block_root(&self, block_id: BlockId) -> Option<Hash256> {
match block_id {
BlockId::Head => Some(self.chain.canonical_head.cached_head().head_block_root()),
BlockId::Genesis => Some(self.chain.genesis_block_root),
BlockId::Finalized => Some(
self.chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.root,
),
BlockId::Justified => Some(
self.chain
.canonical_head
.cached_head()
.justified_checkpoint()
.root,
),
BlockId::Slot(slot) => self
.chain
.block_root_at_slot(slot, WhenSlotSkipped::None)
.unwrap(),
BlockId::Root(root) => Some(root),
}
}
async fn get_block(&self, block_id: BlockId) -> Option<SignedBeaconBlock<E>> {
let root = self.get_block_root(block_id)?;
self.chain.get_block(&root).await.unwrap()
}
pub async fn test_beacon_headers_all_slots(self) -> Self { pub async fn test_beacon_headers_all_slots(self) -> Self {
for slot in 0..CHAIN_LENGTH { for slot in 0..CHAIN_LENGTH {
let slot = Slot::from(slot); let slot = Slot::from(slot);
@ -877,14 +792,17 @@ impl ApiTester {
for block_id in self.interesting_block_ids() { for block_id in self.interesting_block_ids() {
let result = self let result = self
.client .client
.get_beacon_headers_block_id(block_id) .get_beacon_headers_block_id(block_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
let block_root_opt = self.get_block_root(block_id); let block_root_opt = block_id
.root(&self.chain)
.ok()
.map(|(root, _execution_optimistic)| root);
if let BlockId::Slot(slot) = block_id { if let CoreBlockId::Slot(slot) = block_id.0 {
if block_root_opt.is_none() { if block_root_opt.is_none() {
assert!(SKIPPED_SLOTS.contains(&slot.as_u64())); assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
} else { } else {
@ -892,11 +810,11 @@ impl ApiTester {
} }
} }
let block_opt = if let Some(root) = block_root_opt { let block_opt = block_id
self.chain.get_block(&root).await.unwrap() .full_block(&self.chain)
} else { .await
None .ok()
}; .map(|(block, _execution_optimistic)| block);
if block_opt.is_none() && result.is_none() { if block_opt.is_none() && result.is_none() {
continue; continue;
@ -934,13 +852,16 @@ impl ApiTester {
for block_id in self.interesting_block_ids() { for block_id in self.interesting_block_ids() {
let result = self let result = self
.client .client
.get_beacon_blocks_root(block_id) .get_beacon_blocks_root(block_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data.root); .map(|res| res.data.root);
let expected = self.get_block_root(block_id); let expected = block_id
if let BlockId::Slot(slot) = block_id { .root(&self.chain)
.ok()
.map(|(root, _execution_optimistic)| root);
if let CoreBlockId::Slot(slot) = block_id.0 {
if expected.is_none() { if expected.is_none() {
assert!(SKIPPED_SLOTS.contains(&slot.as_u64())); assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
} else { } else {
@ -982,9 +903,13 @@ impl ApiTester {
pub async fn test_beacon_blocks(self) -> Self { pub async fn test_beacon_blocks(self) -> Self {
for block_id in self.interesting_block_ids() { for block_id in self.interesting_block_ids() {
let expected = self.get_block(block_id).await; let expected = block_id
.full_block(&self.chain)
.await
.ok()
.map(|(block, _execution_optimistic)| block);
if let BlockId::Slot(slot) = block_id { if let CoreBlockId::Slot(slot) = block_id.0 {
if expected.is_none() { if expected.is_none() {
assert!(SKIPPED_SLOTS.contains(&slot.as_u64())); assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
} else { } else {
@ -993,10 +918,10 @@ impl ApiTester {
} }
// Check the JSON endpoint. // Check the JSON endpoint.
let json_result = self.client.get_beacon_blocks(block_id).await.unwrap(); let json_result = self.client.get_beacon_blocks(block_id.0).await.unwrap();
if let (Some(json), Some(expected)) = (&json_result, &expected) { if let (Some(json), Some(expected)) = (&json_result, &expected) {
assert_eq!(json.data, *expected, "{:?}", block_id); assert_eq!(&json.data, expected.as_ref(), "{:?}", block_id);
assert_eq!( assert_eq!(
json.version, json.version,
Some(expected.fork_name(&self.chain.spec).unwrap()) Some(expected.fork_name(&self.chain.spec).unwrap())
@ -1009,23 +934,28 @@ impl ApiTester {
// Check the SSZ endpoint. // Check the SSZ endpoint.
let ssz_result = self let ssz_result = self
.client .client
.get_beacon_blocks_ssz(block_id, &self.chain.spec) .get_beacon_blocks_ssz(block_id.0, &self.chain.spec)
.await .await
.unwrap(); .unwrap();
assert_eq!(ssz_result, expected, "{:?}", block_id); assert_eq!(
ssz_result.as_ref(),
expected.as_ref().map(|b| b.as_ref()),
"{:?}",
block_id
);
// Check that the legacy v1 API still works but doesn't return a version field. // Check that the legacy v1 API still works but doesn't return a version field.
let v1_result = self.client.get_beacon_blocks_v1(block_id).await.unwrap(); let v1_result = self.client.get_beacon_blocks_v1(block_id.0).await.unwrap();
if let (Some(v1_result), Some(expected)) = (&v1_result, &expected) { if let (Some(v1_result), Some(expected)) = (&v1_result, &expected) {
assert_eq!(v1_result.version, None); assert_eq!(v1_result.version, None);
assert_eq!(v1_result.data, *expected); assert_eq!(&v1_result.data, expected.as_ref());
} else { } else {
assert_eq!(v1_result, None); assert_eq!(v1_result, None);
assert_eq!(expected, None); assert_eq!(expected, None);
} }
// Check that version headers are provided. // Check that version headers are provided.
let url = self.client.get_beacon_blocks_path(block_id).unwrap(); let url = self.client.get_beacon_blocks_path(block_id.0).unwrap();
let builders: Vec<fn(RequestBuilder) -> RequestBuilder> = vec![ let builders: Vec<fn(RequestBuilder) -> RequestBuilder> = vec![
|b| b, |b| b,
@ -1060,17 +990,18 @@ impl ApiTester {
for block_id in self.interesting_block_ids() { for block_id in self.interesting_block_ids() {
let result = self let result = self
.client .client
.get_beacon_blocks_attestations(block_id) .get_beacon_blocks_attestations(block_id.0)
.await .await
.unwrap() .unwrap()
.map(|res| res.data); .map(|res| res.data);
let expected = self let expected = block_id.full_block(&self.chain).await.ok().map(
.get_block(block_id) |(block, _execution_optimistic)| {
.await block.message().body().attestations().clone().into()
.map(|block| block.message().body().attestations().clone().into()); },
);
if let BlockId::Slot(slot) = block_id { if let CoreBlockId::Slot(slot) = block_id.0 {
if expected.is_none() { if expected.is_none() {
assert!(SKIPPED_SLOTS.contains(&slot.as_u64())); assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
} else { } else {
@ -1473,9 +1404,16 @@ impl ApiTester {
pub async fn test_get_debug_beacon_states(self) -> Self { pub async fn test_get_debug_beacon_states(self) -> Self {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let result_json = self.client.get_debug_beacon_states(state_id).await.unwrap(); let result_json = self
.client
.get_debug_beacon_states(state_id.0)
.await
.unwrap();
let mut expected = self.get_state(state_id); let mut expected = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
expected.as_mut().map(|state| state.drop_all_caches()); expected.as_mut().map(|state| state.drop_all_caches());
if let (Some(json), Some(expected)) = (&result_json, &expected) { if let (Some(json), Some(expected)) = (&result_json, &expected) {
@ -1492,7 +1430,7 @@ impl ApiTester {
// Check SSZ API. // Check SSZ API.
let result_ssz = self let result_ssz = self
.client .client
.get_debug_beacon_states_ssz(state_id, &self.chain.spec) .get_debug_beacon_states_ssz(state_id.0, &self.chain.spec)
.await .await
.unwrap(); .unwrap();
assert_eq!(result_ssz, expected, "{:?}", state_id); assert_eq!(result_ssz, expected, "{:?}", state_id);
@ -1500,7 +1438,7 @@ impl ApiTester {
// Check legacy v1 API. // Check legacy v1 API.
let result_v1 = self let result_v1 = self
.client .client
.get_debug_beacon_states_v1(state_id) .get_debug_beacon_states_v1(state_id.0)
.await .await
.unwrap(); .unwrap();
@ -1513,7 +1451,10 @@ impl ApiTester {
} }
// Check that version headers are provided. // Check that version headers are provided.
let url = self.client.get_debug_beacon_states_path(state_id).unwrap(); let url = self
.client
.get_debug_beacon_states_path(state_id.0)
.unwrap();
let builders: Vec<fn(RequestBuilder) -> RequestBuilder> = let builders: Vec<fn(RequestBuilder) -> RequestBuilder> =
vec![|b| b, |b| b.accept(Accept::Ssz)]; vec![|b| b, |b| b.accept(Accept::Ssz)];
@ -1791,6 +1732,7 @@ impl ApiTester {
let expected = DutiesResponse { let expected = DutiesResponse {
data: expected_duties, data: expected_duties,
execution_optimistic: Some(false),
dependent_root, dependent_root,
}; };
@ -2391,11 +2333,14 @@ impl ApiTester {
for state_id in self.interesting_state_ids() { for state_id in self.interesting_state_ids() {
let result = self let result = self
.client .client
.get_lighthouse_beacon_states_ssz(&state_id, &self.chain.spec) .get_lighthouse_beacon_states_ssz(&state_id.0, &self.chain.spec)
.await .await
.unwrap(); .unwrap();
let mut expected = self.get_state(state_id); let mut expected = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
expected.as_mut().map(|state| state.drop_all_caches()); expected.as_mut().map(|state| state.drop_all_caches());
assert_eq!(result, expected, "{:?}", state_id); assert_eq!(result, expected, "{:?}", state_id);
@ -2562,6 +2507,7 @@ impl ApiTester {
let expected_block = EventKind::Block(SseBlock { let expected_block = EventKind::Block(SseBlock {
block: block_root, block: block_root,
slot: next_slot, slot: next_slot,
execution_optimistic: false,
}); });
let expected_head = EventKind::Head(SseHead { let expected_head = EventKind::Head(SseHead {
@ -2575,6 +2521,7 @@ impl ApiTester {
.unwrap() .unwrap()
.unwrap(), .unwrap(),
epoch_transition: true, epoch_transition: true,
execution_optimistic: false,
}); });
let finalized_block_root = self let finalized_block_root = self
@ -2593,6 +2540,7 @@ impl ApiTester {
block: finalized_block_root, block: finalized_block_root,
state: finalized_state_root, state: finalized_state_root,
epoch: Epoch::new(3), epoch: Epoch::new(3),
execution_optimistic: false,
}); });
self.client self.client
@ -2621,6 +2569,7 @@ impl ApiTester {
new_head_block: self.reorg_block.canonical_root(), new_head_block: self.reorg_block.canonical_root(),
new_head_state: self.reorg_block.state_root(), new_head_state: self.reorg_block.state_root(),
epoch: self.next_block.slot().epoch(E::slots_per_epoch()), epoch: self.next_block.slot().epoch(E::slots_per_epoch()),
execution_optimistic: false,
}); });
self.client self.client
@ -2687,6 +2636,7 @@ impl ApiTester {
let expected_block = EventKind::Block(SseBlock { let expected_block = EventKind::Block(SseBlock {
block: block_root, block: block_root,
slot: next_slot, slot: next_slot,
execution_optimistic: false,
}); });
let expected_head = EventKind::Head(SseHead { let expected_head = EventKind::Head(SseHead {
@ -2696,6 +2646,7 @@ impl ApiTester {
current_duty_dependent_root: self.chain.genesis_block_root, current_duty_dependent_root: self.chain.genesis_block_root,
previous_duty_dependent_root: self.chain.genesis_block_root, previous_duty_dependent_root: self.chain.genesis_block_root,
epoch_transition: false, epoch_transition: false,
execution_optimistic: false,
}); });
self.client self.client
@ -2708,6 +2659,40 @@ impl ApiTester {
self self
} }
pub async fn test_check_optimistic_responses(&mut self) {
// Check responses are not optimistic.
let result = self
.client
.get_beacon_headers_block_id(CoreBlockId::Head)
.await
.unwrap()
.unwrap();
assert_eq!(result.execution_optimistic, Some(false));
// Change head to be optimistic.
self.chain
.canonical_head
.fork_choice_write_lock()
.proto_array_mut()
.core_proto_array_mut()
.nodes
.last_mut()
.map(|head_node| {
head_node.execution_status = ExecutionStatus::Optimistic(ExecutionBlockHash::zero())
});
// Check responses are now optimistic.
let result = self
.client
.get_beacon_headers_block_id(CoreBlockId::Head)
.await
.unwrap()
.unwrap();
assert_eq!(result.execution_optimistic, Some(true));
}
} }
async fn poll_events<S: Stream<Item = Result<EventKind<T>, eth2::Error>> + Unpin, T: EthSpec>( async fn poll_events<S: Stream<Item = Result<EventKind<T>, eth2::Error>> + Unpin, T: EthSpec>(
@ -3105,3 +3090,11 @@ async fn lighthouse_endpoints() {
.test_post_lighthouse_liveness() .test_post_lighthouse_liveness()
.await; .await;
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn optimistic_responses() {
ApiTester::new_with_hard_forks(true, true)
.await
.test_check_optimistic_responses()
.await;
}

View File

@ -1317,7 +1317,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
} }
/// Load a frozen state's slot, given its root. /// Load a frozen state's slot, given its root.
fn load_cold_state_slot(&self, state_root: &Hash256) -> Result<Option<Slot>, Error> { pub fn load_cold_state_slot(&self, state_root: &Hash256) -> Result<Option<Slot>, Error> {
Ok(self Ok(self
.cold_db .cold_db
.get(state_root)? .get(state_root)?
@ -1583,7 +1583,7 @@ fn no_state_root_iter() -> Option<std::iter::Empty<Result<(Hash256, Slot), Error
#[derive(Debug, Clone, Copy, Default, Encode, Decode)] #[derive(Debug, Clone, Copy, Default, Encode, Decode)]
pub struct HotStateSummary { pub struct HotStateSummary {
slot: Slot, slot: Slot,
latest_block_root: Hash256, pub latest_block_root: Hash256,
epoch_boundary_state_root: Hash256, epoch_boundary_state_root: Hash256,
} }

View File

@ -332,7 +332,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_root( pub async fn get_beacon_states_root(
&self, &self,
state_id: StateId, state_id: StateId,
) -> Result<Option<GenericResponse<RootData>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<RootData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -351,7 +351,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_fork( pub async fn get_beacon_states_fork(
&self, &self,
state_id: StateId, state_id: StateId,
) -> Result<Option<GenericResponse<Fork>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Fork>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -370,7 +370,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_finality_checkpoints( pub async fn get_beacon_states_finality_checkpoints(
&self, &self,
state_id: StateId, state_id: StateId,
) -> Result<Option<GenericResponse<FinalityCheckpointsData>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<FinalityCheckpointsData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -390,7 +390,7 @@ impl BeaconNodeHttpClient {
&self, &self,
state_id: StateId, state_id: StateId,
ids: Option<&[ValidatorId]>, ids: Option<&[ValidatorId]>,
) -> Result<Option<GenericResponse<Vec<ValidatorBalanceData>>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Vec<ValidatorBalanceData>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -420,7 +420,7 @@ impl BeaconNodeHttpClient {
state_id: StateId, state_id: StateId,
ids: Option<&[ValidatorId]>, ids: Option<&[ValidatorId]>,
statuses: Option<&[ValidatorStatus]>, statuses: Option<&[ValidatorStatus]>,
) -> Result<Option<GenericResponse<Vec<ValidatorData>>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Vec<ValidatorData>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -460,7 +460,7 @@ impl BeaconNodeHttpClient {
slot: Option<Slot>, slot: Option<Slot>,
index: Option<u64>, index: Option<u64>,
epoch: Option<Epoch>, epoch: Option<Epoch>,
) -> Result<Option<GenericResponse<Vec<CommitteeData>>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Vec<CommitteeData>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -493,7 +493,7 @@ impl BeaconNodeHttpClient {
&self, &self,
state_id: StateId, state_id: StateId,
epoch: Option<Epoch>, epoch: Option<Epoch>,
) -> Result<GenericResponse<SyncCommitteeByValidatorIndices>, Error> { ) -> Result<ExecutionOptimisticResponse<SyncCommitteeByValidatorIndices>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -518,7 +518,7 @@ impl BeaconNodeHttpClient {
&self, &self,
state_id: StateId, state_id: StateId,
validator_id: &ValidatorId, validator_id: &ValidatorId,
) -> Result<Option<GenericResponse<ValidatorData>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<ValidatorData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -539,7 +539,7 @@ impl BeaconNodeHttpClient {
&self, &self,
slot: Option<Slot>, slot: Option<Slot>,
parent_root: Option<Hash256>, parent_root: Option<Hash256>,
) -> Result<Option<GenericResponse<Vec<BlockHeaderData>>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Vec<BlockHeaderData>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -566,7 +566,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_headers_block_id( pub async fn get_beacon_headers_block_id(
&self, &self,
block_id: BlockId, block_id: BlockId,
) -> Result<Option<GenericResponse<BlockHeaderData>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<BlockHeaderData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -635,7 +635,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks<T: EthSpec>( pub async fn get_beacon_blocks<T: EthSpec>(
&self, &self,
block_id: BlockId, block_id: BlockId,
) -> Result<Option<ForkVersionedResponse<SignedBeaconBlock<T>>>, Error> { ) -> Result<Option<ExecutionOptimisticForkVersionedResponse<SignedBeaconBlock<T>>>, Error> {
let path = self.get_beacon_blocks_path(block_id)?; let path = self.get_beacon_blocks_path(block_id)?;
let response = match self.get_response(path, |b| b).await.optional()? { let response = match self.get_response(path, |b| b).await.optional()? {
Some(res) => res, Some(res) => res,
@ -644,20 +644,31 @@ impl BeaconNodeHttpClient {
// If present, use the fork provided in the headers to decode the block. Gracefully handle // If present, use the fork provided in the headers to decode the block. Gracefully handle
// missing and malformed fork names by falling back to regular deserialisation. // missing and malformed fork names by falling back to regular deserialisation.
let (block, version) = match response.fork_name_from_header() { let (block, version, execution_optimistic) = match response.fork_name_from_header() {
Ok(Some(fork_name)) => { Ok(Some(fork_name)) => {
let (data, (version, execution_optimistic)) =
map_fork_name_with!(fork_name, SignedBeaconBlock, { map_fork_name_with!(fork_name, SignedBeaconBlock, {
let ForkVersionedResponse { version, data } = response.json().await?; let ExecutionOptimisticForkVersionedResponse {
(data, version) version,
}) execution_optimistic,
data,
} = response.json().await?;
(data, (version, execution_optimistic))
});
(data, version, execution_optimistic)
} }
Ok(None) | Err(_) => { Ok(None) | Err(_) => {
let ForkVersionedResponse { version, data } = response.json().await?; let ExecutionOptimisticForkVersionedResponse {
(data, version) version,
execution_optimistic,
data,
} = response.json().await?;
(data, version, execution_optimistic)
} }
}; };
Ok(Some(ForkVersionedResponse { Ok(Some(ExecutionOptimisticForkVersionedResponse {
version, version,
execution_optimistic,
data: block, data: block,
})) }))
} }
@ -702,7 +713,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks_root( pub async fn get_beacon_blocks_root(
&self, &self,
block_id: BlockId, block_id: BlockId,
) -> Result<Option<GenericResponse<RootData>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<RootData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -721,7 +732,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks_attestations<T: EthSpec>( pub async fn get_beacon_blocks_attestations<T: EthSpec>(
&self, &self,
block_id: BlockId, block_id: BlockId,
) -> Result<Option<GenericResponse<Vec<Attestation<T>>>>, Error> { ) -> Result<Option<ExecutionOptimisticResponse<Vec<Attestation<T>>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -1123,7 +1134,7 @@ impl BeaconNodeHttpClient {
pub async fn get_debug_beacon_states<T: EthSpec>( pub async fn get_debug_beacon_states<T: EthSpec>(
&self, &self,
state_id: StateId, state_id: StateId,
) -> Result<Option<ForkVersionedResponse<BeaconState<T>>>, Error> { ) -> Result<Option<ExecutionOptimisticForkVersionedResponse<BeaconState<T>>>, Error> {
let path = self.get_debug_beacon_states_path(state_id)?; let path = self.get_debug_beacon_states_path(state_id)?;
self.get_opt(path).await self.get_opt(path).await
} }
@ -1132,7 +1143,7 @@ impl BeaconNodeHttpClient {
pub async fn get_debug_beacon_states_v1<T: EthSpec>( pub async fn get_debug_beacon_states_v1<T: EthSpec>(
&self, &self,
state_id: StateId, state_id: StateId,
) -> Result<Option<ForkVersionedResponse<BeaconState<T>>>, Error> { ) -> Result<Option<ExecutionOptimisticForkVersionedResponse<BeaconState<T>>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()
@ -1160,9 +1171,24 @@ impl BeaconNodeHttpClient {
.transpose() .transpose()
} }
/// `GET debug/beacon/heads` /// `GET v2/debug/beacon/heads`
pub async fn get_debug_beacon_heads( pub async fn get_debug_beacon_heads(
&self, &self,
) -> Result<GenericResponse<Vec<ChainHeadData>>, Error> {
let mut path = self.eth_path(V2)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("debug")
.push("beacon")
.push("heads");
self.get(path).await
}
/// `GET v1/debug/beacon/heads` (LEGACY)
pub async fn get_debug_beacon_heads_v1(
&self,
) -> Result<GenericResponse<Vec<ChainHeadData>>, Error> { ) -> Result<GenericResponse<Vec<ChainHeadData>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
@ -1494,7 +1520,7 @@ impl BeaconNodeHttpClient {
&self, &self,
epoch: Epoch, epoch: Epoch,
indices: &[u64], indices: &[u64],
) -> Result<GenericResponse<Vec<SyncDuty>>, Error> { ) -> Result<ExecutionOptimisticResponse<Vec<SyncDuty>>, Error> {
let mut path = self.eth_path(V1)?; let mut path = self.eth_path(V1)?;
path.path_segments_mut() path.path_segments_mut()

View File

@ -189,6 +189,14 @@ impl fmt::Display for StateId {
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")] #[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
pub struct DutiesResponse<T: Serialize + serde::de::DeserializeOwned> { pub struct DutiesResponse<T: Serialize + serde::de::DeserializeOwned> {
pub dependent_root: Hash256, pub dependent_root: Hash256,
pub execution_optimistic: Option<bool>,
pub data: T,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
pub struct ExecutionOptimisticResponse<T: Serialize + serde::de::DeserializeOwned> {
pub execution_optimistic: Option<bool>,
pub data: T, pub data: T,
} }
@ -204,6 +212,18 @@ impl<T: Serialize + serde::de::DeserializeOwned> From<T> for GenericResponse<T>
} }
} }
impl<T: Serialize + serde::de::DeserializeOwned> GenericResponse<T> {
pub fn add_execution_optimistic(
self,
execution_optimistic: bool,
) -> ExecutionOptimisticResponse<T> {
ExecutionOptimisticResponse {
execution_optimistic: Some(execution_optimistic),
data: self.data,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize)] #[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(bound = "T: Serialize")] #[serde(bound = "T: Serialize")]
pub struct GenericResponseRef<'a, T: Serialize> { pub struct GenericResponseRef<'a, T: Serialize> {
@ -216,6 +236,14 @@ impl<'a, T: Serialize> From<&'a T> for GenericResponseRef<'a, T> {
} }
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ExecutionOptimisticForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<ForkName>,
pub execution_optimistic: Option<bool>,
pub data: T,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ForkVersionedResponse<T> { pub struct ForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -495,6 +523,8 @@ pub struct DepositContractData {
pub struct ChainHeadData { pub struct ChainHeadData {
pub slot: Slot, pub slot: Slot,
pub root: Hash256, pub root: Hash256,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_optimistic: Option<bool>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -794,6 +824,7 @@ pub struct PeerCount {
pub struct SseBlock { pub struct SseBlock {
pub slot: Slot, pub slot: Slot,
pub block: Hash256, pub block: Hash256,
pub execution_optimistic: bool,
} }
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@ -801,6 +832,7 @@ pub struct SseFinalizedCheckpoint {
pub block: Hash256, pub block: Hash256,
pub state: Hash256, pub state: Hash256,
pub epoch: Epoch, pub epoch: Epoch,
pub execution_optimistic: bool,
} }
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@ -811,6 +843,7 @@ pub struct SseHead {
pub current_duty_dependent_root: Hash256, pub current_duty_dependent_root: Hash256,
pub previous_duty_dependent_root: Hash256, pub previous_duty_dependent_root: Hash256,
pub epoch_transition: bool, pub epoch_transition: bool,
pub execution_optimistic: bool,
} }
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@ -823,6 +856,7 @@ pub struct SseChainReorg {
pub new_head_block: Hash256, pub new_head_block: Hash256,
pub new_head_state: Hash256, pub new_head_state: Hash256,
pub epoch: Epoch, pub epoch: Epoch,
pub execution_optimistic: bool,
} }
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@ -837,6 +871,7 @@ pub struct SseLateHead {
pub observed_delay: Option<Duration>, pub observed_delay: Option<Duration>,
pub imported_delay: Option<Duration>, pub imported_delay: Option<Duration>,
pub set_as_head_delay: Option<Duration>, pub set_as_head_delay: Option<Duration>,
pub execution_optimistic: bool,
} }
#[derive(PartialEq, Debug, Serialize, Clone)] #[derive(PartialEq, Debug, Serialize, Clone)]

View File

@ -1175,6 +1175,12 @@ where
&self.proto_array &self.proto_array
} }
/// Returns a mutable reference to `proto_array`.
/// Should only be used in testing.
pub fn proto_array_mut(&mut self) -> &mut ProtoArrayForkChoice {
&mut self.proto_array
}
/// Returns a reference to the underlying `fc_store`. /// Returns a reference to the underlying `fc_store`.
pub fn fc_store(&self) -> &T { pub fn fc_store(&self) -> &T {
&self.fc_store &self.fc_store