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:
parent
21dec6f603
commit
bb5a6d2cca
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -1289,23 +1289,28 @@ 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
|
||||||
let duties = validator_indices
|
.canonical_head
|
||||||
.iter()
|
.fork_choice_read_lock()
|
||||||
.map(|validator_index| {
|
.get_block_execution_status(&head_block_root)
|
||||||
let validator_index = *validator_index as usize;
|
.ok_or(Error::AttestationHeadNotInForkChoice(head_block_root))?;
|
||||||
committee_cache.get_attestation_duties(validator_index)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let execution_status = self
|
let (duties, dependent_root) = self.with_committee_cache(
|
||||||
.canonical_head
|
head_block_root,
|
||||||
.fork_choice_read_lock()
|
epoch,
|
||||||
.get_block_execution_status(&head_block_root)
|
|committee_cache, dependent_root| {
|
||||||
.ok_or(Error::AttestationHeadNotInForkChoice(head_block_root))?;
|
let duties = validator_indices
|
||||||
|
.iter()
|
||||||
|
.map(|validator_index| {
|
||||||
|
let validator_index = *validator_index as usize;
|
||||||
|
committee_cache.get_attestation_duties(validator_index)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok((duties, dependent_root, execution_status))
|
Ok((duties, dependent_root))
|
||||||
})
|
},
|
||||||
|
)?;
|
||||||
|
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()) {
|
||||||
|
@ -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,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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,35 +82,42 @@ 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
|
||||||
if head.beacon_state.current_epoch() <= request_epoch {
|
.canonical_head
|
||||||
Ok(Some((
|
.head_and_execution_status()
|
||||||
head.beacon_state_root(),
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
head.beacon_state
|
let head = &cached_head.snapshot;
|
||||||
.clone_with(CloneConfig::committee_caches_only()),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
|
||||||
|
|
||||||
let mut state = if let Some((state_root, mut state)) = state_opt {
|
if head.beacon_state.current_epoch() <= request_epoch {
|
||||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
Some((
|
||||||
// suitable epoch.
|
head.beacon_state_root(),
|
||||||
ensure_state_knows_attester_duties_for_epoch(
|
head.beacon_state
|
||||||
&mut state,
|
.clone_with(CloneConfig::committee_caches_only()),
|
||||||
state_root,
|
execution_status.is_optimistic(),
|
||||||
request_epoch,
|
))
|
||||||
&chain.spec,
|
} else {
|
||||||
)?;
|
None
|
||||||
state
|
}
|
||||||
} else {
|
|
||||||
StateId::slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (mut state, execution_optimistic) =
|
||||||
|
if let Some((state_root, mut state, execution_optimistic)) = state_opt {
|
||||||
|
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||||
|
// suitable epoch.
|
||||||
|
ensure_state_knows_attester_duties_for_epoch(
|
||||||
|
&mut state,
|
||||||
|
state_root,
|
||||||
|
request_epoch,
|
||||||
|
&chain.spec,
|
||||||
|
)?;
|
||||||
|
(state, execution_optimistic)
|
||||||
|
} else {
|
||||||
|
StateId::from_slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch()))
|
||||||
|
.state(chain)?
|
||||||
|
};
|
||||||
|
|
||||||
// Sanity-check the state lookup.
|
// Sanity-check the state lookup.
|
||||||
if !(state.current_epoch() == request_epoch || state.current_epoch() + 1 == request_epoch) {
|
if !(state.current_epoch() == request_epoch || state.current_epoch() + 1 == request_epoch) {
|
||||||
return Err(warp_utils::reject::custom_server_error(format!(
|
return Err(warp_utils::reject::custom_server_error(format!(
|
||||||
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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,32 +24,78 @@ 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
|
.head_and_execution_status()
|
||||||
.cached_head()
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
.finalized_checkpoint()
|
Ok((
|
||||||
.root),
|
cached_head.head_block_root(),
|
||||||
CoreBlockId::Justified => Ok(chain
|
execution_status.is_optimistic(),
|
||||||
.canonical_head
|
))
|
||||||
.cached_head()
|
}
|
||||||
.justified_checkpoint()
|
CoreBlockId::Genesis => Ok((chain.genesis_block_root, false)),
|
||||||
.root),
|
CoreBlockId::Finalized => {
|
||||||
CoreBlockId::Slot(slot) => chain
|
let finalized_checkpoint =
|
||||||
.block_root_at_slot(*slot, WhenSlotSkipped::None)
|
chain.canonical_head.cached_head().finalized_checkpoint();
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)
|
let (_slot, execution_optimistic) =
|
||||||
.and_then(|root_opt| {
|
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?;
|
||||||
root_opt.ok_or_else(|| {
|
Ok((finalized_checkpoint.root, execution_optimistic))
|
||||||
warp_utils::reject::custom_not_found(format!(
|
}
|
||||||
"beacon block at slot {}",
|
CoreBlockId::Justified => {
|
||||||
slot
|
let justified_checkpoint =
|
||||||
))
|
chain.canonical_head.cached_head().justified_checkpoint();
|
||||||
})
|
let (_slot, execution_optimistic) =
|
||||||
}),
|
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?;
|
||||||
CoreBlockId::Root(root) => Ok(*root),
|
Ok((justified_checkpoint.root, execution_optimistic))
|
||||||
|
}
|
||||||
|
CoreBlockId::Slot(slot) => {
|
||||||
|
let execution_optimistic = chain
|
||||||
|
.is_optimistic_head()
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
let root = chain
|
||||||
|
.block_root_at_slot(*slot, WhenSlotSkipped::None)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)
|
||||||
|
.and_then(|root_opt| {
|
||||||
|
root_opt.ok_or_else(|| {
|
||||||
|
warp_utils::reject::custom_not_found(format!(
|
||||||
|
"beacon block at slot {}",
|
||||||
|
slot
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok((root, execution_optimistic))
|
||||||
|
}
|
||||||
|
CoreBlockId::Root(root) => {
|
||||||
|
// This matches the behaviour of other consensus clients (e.g. Teku).
|
||||||
|
if root == &Hash256::zero() {
|
||||||
|
return Err(warp_utils::reject::custom_not_found(format!(
|
||||||
|
"beacon block with root {}",
|
||||||
|
root
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
if chain
|
||||||
|
.store
|
||||||
|
.block_exists(root)
|
||||||
|
.map_err(BeaconChainError::DBError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||||
|
{
|
||||||
|
let execution_optimistic = chain
|
||||||
|
.canonical_head
|
||||||
|
.fork_choice_read_lock()
|
||||||
|
.is_optimistic_block(root)
|
||||||
|
.map_err(BeaconChainError::ForkChoiceError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
Ok((*root, execution_optimistic))
|
||||||
|
} else {
|
||||||
|
return Err(warp_utils::reject::custom_not_found(format!(
|
||||||
|
"beacon block with root {}",
|
||||||
|
root
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,11 +103,20 @@ impl BlockId {
|
|||||||
pub fn blinded_block<T: BeaconChainTypes>(
|
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,18 +193,20 @@ 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
|
||||||
warp_utils::reject::custom_not_found(format!(
|
.map(|block| (Arc::new(block), execution_optimistic))
|
||||||
"beacon block with root {}",
|
.ok_or_else(|| {
|
||||||
root
|
warp_utils::reject::custom_not_found(format!(
|
||||||
))
|
"beacon block with root {}",
|
||||||
})
|
root
|
||||||
|
))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,3 +220,9 @@ impl FromStr for BlockId {
|
|||||||
CoreBlockId::from_str(s).map(Self)
|
CoreBlockId::from_str(s).map(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BlockId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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,31 +181,37 @@ 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
|
||||||
if head.beacon_state.current_epoch() <= epoch {
|
.canonical_head
|
||||||
Ok(Some((
|
.head_and_execution_status()
|
||||||
head.beacon_state_root(),
|
|
||||||
head.beacon_state
|
|
||||||
.clone_with(CloneConfig::committee_caches_only()),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
|
||||||
|
|
||||||
let state = if let Some((state_root, mut state)) = state_opt {
|
|
||||||
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
|
||||||
// suitable epoch.
|
|
||||||
ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec)
|
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
state
|
let head = &cached_head.snapshot;
|
||||||
} else {
|
|
||||||
StateId::slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
if head.beacon_state.current_epoch() <= epoch {
|
||||||
|
Some((
|
||||||
|
head.beacon_state_root(),
|
||||||
|
head.beacon_state
|
||||||
|
.clone_with(CloneConfig::committee_caches_only()),
|
||||||
|
execution_status.is_optimistic(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (state, execution_optimistic) =
|
||||||
|
if let Some((state_root, mut state, execution_optimistic)) = state_opt {
|
||||||
|
// If we've loaded the head state it might be from a previous epoch, ensure it's in a
|
||||||
|
// suitable epoch.
|
||||||
|
ensure_state_is_in_epoch(&mut state, state_root, epoch, &chain.spec)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
(state, execution_optimistic)
|
||||||
|
} else {
|
||||||
|
StateId::from_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure the state lookup was correct.
|
// Ensure the state lookup was correct.
|
||||||
if state.current_epoch() != epoch {
|
if state.current_epoch() != epoch {
|
||||||
return Err(warp_utils::reject::custom_server_error(format!(
|
return Err(warp_utils::reject::custom_server_error(format!(
|
||||||
@ -208,7 +233,7 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
|||||||
.map_err(BeaconChainError::from)
|
.map_err(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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
.head_and_execution_status()
|
||||||
.cached_head()
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
.finalized_checkpoint()
|
return Ok((
|
||||||
.epoch
|
cached_head.head_state_root(),
|
||||||
.start_slot(T::EthSpec::slots_per_epoch()),
|
execution_status.is_optimistic(),
|
||||||
CoreStateId::Justified => chain
|
));
|
||||||
.canonical_head
|
}
|
||||||
.cached_head()
|
CoreStateId::Genesis => return Ok((chain.genesis_state_root, false)),
|
||||||
.justified_checkpoint()
|
CoreStateId::Finalized => {
|
||||||
.epoch
|
let finalized_checkpoint =
|
||||||
.start_slot(T::EthSpec::slots_per_epoch()),
|
chain.canonical_head.cached_head().finalized_checkpoint();
|
||||||
CoreStateId::Slot(slot) => *slot,
|
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?
|
||||||
CoreStateId::Root(root) => return Ok(*root),
|
}
|
||||||
|
CoreStateId::Justified => {
|
||||||
|
let justified_checkpoint =
|
||||||
|
chain.canonical_head.cached_head().justified_checkpoint();
|
||||||
|
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?
|
||||||
|
}
|
||||||
|
CoreStateId::Slot(slot) => (
|
||||||
|
*slot,
|
||||||
|
chain
|
||||||
|
.is_optimistic_head()
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?,
|
||||||
|
),
|
||||||
|
CoreStateId::Root(root) => {
|
||||||
|
if let Some(hot_summary) = chain
|
||||||
|
.store
|
||||||
|
.load_hot_state_summary(root)
|
||||||
|
.map_err(BeaconChainError::DBError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||||
|
{
|
||||||
|
let execution_optimistic = chain
|
||||||
|
.canonical_head
|
||||||
|
.fork_choice_read_lock()
|
||||||
|
.is_optimistic_block_no_fallback(&hot_summary.latest_block_root)
|
||||||
|
.map_err(BeaconChainError::ForkChoiceError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
return Ok((*root, execution_optimistic));
|
||||||
|
} else if let Some(_cold_state_slot) = chain
|
||||||
|
.store
|
||||||
|
.load_cold_state_slot(root)
|
||||||
|
.map_err(BeaconChainError::DBError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||||
|
{
|
||||||
|
let fork_choice = chain.canonical_head.fork_choice_read_lock();
|
||||||
|
let finalized_root = fork_choice
|
||||||
|
.cached_fork_choice_view()
|
||||||
|
.finalized_checkpoint
|
||||||
|
.root;
|
||||||
|
let execution_optimistic = fork_choice
|
||||||
|
.is_optimistic_block_no_fallback(&finalized_root)
|
||||||
|
.map_err(BeaconChainError::ForkChoiceError)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
return Ok((*root, execution_optimistic));
|
||||||
|
} else {
|
||||||
|
return Err(warp_utils::reject::custom_not_found(format!(
|
||||||
|
"beacon state for state root {}",
|
||||||
|
root
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
chain
|
let root = chain
|
||||||
.state_root_at_slot(slot)
|
.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))
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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`.
|
||||||
|
@ -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())
|
||||||
|
@ -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,18 +433,20 @@ 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)
|
||||||
previous_justified: state.previous_justified_checkpoint(),
|
.ok()
|
||||||
current_justified: state.current_justified_checkpoint(),
|
.map(|(state, _execution_optimistic)| FinalityCheckpointsData {
|
||||||
finalized: state.finalized_checkpoint(),
|
previous_justified: state.previous_justified_checkpoint(),
|
||||||
});
|
current_justified: state.current_justified_checkpoint(),
|
||||||
|
finalized: state.finalized_checkpoint(),
|
||||||
|
});
|
||||||
|
|
||||||
assert_eq!(result, expected, "{:?}", state_id);
|
assert_eq!(result, expected, "{:?}", state_id);
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)) => {
|
||||||
map_fork_name_with!(fork_name, SignedBeaconBlock, {
|
let (data, (version, execution_optimistic)) =
|
||||||
let ForkVersionedResponse { version, data } = response.json().await?;
|
map_fork_name_with!(fork_name, SignedBeaconBlock, {
|
||||||
(data, version)
|
let ExecutionOptimisticForkVersionedResponse {
|
||||||
})
|
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()
|
||||||
|
@ -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)]
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user