Skip to content

Commit

Permalink
Add finalized to HTTP API responses (sigp#3753)
Browse files Browse the repository at this point in the history
- Add `is_finalized_block` method to `BeaconChain` in `beacon_node/beacon_chain/src/beacon_chain.rs`.
- Add `is_finalized_state` method to `BeaconChain` in `beacon_node/beacon_chain/src/beacon_chain.rs`.
- Add `fork_and_execution_optimistic_and_finalized` in `beacon_node/http_api/src/state_id.rs`.
- Add `ExecutionOptimisticFinalizedForkVersionedResponse` type in `consensus/types/src/fork_versioned_response.rs`.
- Add `execution_optimistic_finalized_fork_versioned_response`function in  `beacon_node/http_api/src/version.rs`.
- Add `ExecutionOptimisticFinalizedResponse` type in `common/eth2/src/types.rs`.
- Add `add_execution_optimistic_finalized` method in  `common/eth2/src/types.rs`.
- Update API response methods to include finalized.
- Remove `execution_optimistic_fork_versioned_response`

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
  • Loading branch information
2 people authored and Woodpile37 committed Jan 6, 2024
1 parent 63b7156 commit 3d4bea3
Show file tree
Hide file tree
Showing 13 changed files with 500 additions and 78 deletions.
40 changes: 40 additions & 0 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,46 @@ pub struct BeaconChain<T: BeaconChainTypes> {
type BeaconBlockAndState<T, Payload> = (BeaconBlock<T, Payload>, BeaconState<T>);

impl<T: BeaconChainTypes> BeaconChain<T> {
/// Checks if a block is finalized.
/// The finalization check is done with the block slot. The block root is used to verify that
/// the finalized slot is in the canonical chain.
pub fn is_finalized_block(
&self,
block_root: &Hash256,
block_slot: Slot,
) -> Result<bool, Error> {
let finalized_slot = self
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
let is_canonical = self
.block_root_at_slot(block_slot, WhenSlotSkipped::None)?
.map_or(false, |canonical_root| block_root == &canonical_root);
Ok(block_slot <= finalized_slot && is_canonical)
}

/// Checks if a state is finalized.
/// The finalization check is done with the slot. The state root is used to verify that
/// the finalized state is in the canonical chain.
pub fn is_finalized_state(
&self,
state_root: &Hash256,
state_slot: Slot,
) -> Result<bool, Error> {
let finalized_slot = self
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
let is_canonical = self
.state_root_at_slot(state_slot)?
.map_or(false, |canonical_root| state_root == &canonical_root);
Ok(state_slot <= finalized_slot && is_canonical)
}

/// Persists the head tracker and fork choice.
///
/// We do it atomically even though no guarantees need to be made about blocks from
Expand Down
6 changes: 4 additions & 2 deletions beacon_node/http_api/src/attester_duties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ fn compute_historic_attester_duties<T: BeaconChainTypes>(
)?;
(state, execution_optimistic)
} else {
StateId::from_slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch()))
.state(chain)?
let (state, execution_optimistic, _finalized) =
StateId::from_slot(request_epoch.start_slot(T::EthSpec::slots_per_epoch()))
.state(chain)?;
(state, execution_optimistic)
};

// Sanity-check the state lookup.
Expand Down
4 changes: 3 additions & 1 deletion beacon_node/http_api/src/proposer_duties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
.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)?
let (state, execution_optimistic, _finalized) =
StateId::from_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())).state(chain)?;
(state, execution_optimistic)
};

// Ensure the state lookup was correct.
Expand Down
6 changes: 3 additions & 3 deletions beacon_node/http_api/src/standard_block_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use warp_utils::reject::beacon_chain_error;
pub fn compute_beacon_block_rewards<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_id: BlockId,
) -> Result<(StandardBlockReward, ExecutionOptimistic), warp::Rejection> {
let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
) -> Result<(StandardBlockReward, ExecutionOptimistic, bool), warp::Rejection> {
let (block, execution_optimistic, finalized) = block_id.blinded_block(&chain)?;

let block_ref = block.message();

Expand All @@ -23,5 +23,5 @@ pub fn compute_beacon_block_rewards<T: BeaconChainTypes>(
.compute_beacon_block_reward(block_ref, block_root, &mut state)
.map_err(beacon_chain_error)?;

Ok((rewards, execution_optimistic))
Ok((rewards, execution_optimistic, finalized))
}
75 changes: 56 additions & 19 deletions beacon_node/http_api/src/state_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use types::{BeaconState, Checkpoint, EthSpec, Fork, Hash256, Slot};
#[derive(Debug)]
pub struct StateId(pub CoreStateId);

// More clarity when returning if the state is finalized or not in the root function.
type Finalized = bool;

impl StateId {
pub fn from_slot(slot: Slot) -> Self {
Self(CoreStateId::Slot(slot))
Expand All @@ -19,8 +22,8 @@ impl StateId {
pub fn root<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<(Hash256, ExecutionOptimistic), warp::Rejection> {
let (slot, execution_optimistic) = match &self.0 {
) -> Result<(Hash256, ExecutionOptimistic, Finalized), warp::Rejection> {
let (slot, execution_optimistic, finalized) = match &self.0 {
CoreStateId::Head => {
let (cached_head, execution_status) = chain
.canonical_head
Expand All @@ -29,24 +32,36 @@ impl StateId {
return Ok((
cached_head.head_state_root(),
execution_status.is_optimistic_or_invalid(),
false,
));
}
CoreStateId::Genesis => return Ok((chain.genesis_state_root, false)),
CoreStateId::Genesis => return Ok((chain.genesis_state_root, false, true)),
CoreStateId::Finalized => {
let finalized_checkpoint =
chain.canonical_head.cached_head().finalized_checkpoint();
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?
let (slot, execution_optimistic) =
checkpoint_slot_and_execution_optimistic(chain, finalized_checkpoint)?;
(slot, execution_optimistic, true)
}
CoreStateId::Justified => {
let justified_checkpoint =
chain.canonical_head.cached_head().justified_checkpoint();
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?
let (slot, execution_optimistic) =
checkpoint_slot_and_execution_optimistic(chain, justified_checkpoint)?;
(slot, execution_optimistic, false)
}
CoreStateId::Slot(slot) => (
*slot,
chain
.is_optimistic_or_invalid_head()
.map_err(warp_utils::reject::beacon_chain_error)?,
*slot
<= chain
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch()),
),
CoreStateId::Root(root) => {
if let Some(hot_summary) = chain
Expand All @@ -61,7 +76,10 @@ impl StateId {
.is_optimistic_or_invalid_block_no_fallback(&hot_summary.latest_block_root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?;
return Ok((*root, execution_optimistic));
let finalized = chain
.is_finalized_state(root, hot_summary.slot)
.map_err(warp_utils::reject::beacon_chain_error)?;
return Ok((*root, execution_optimistic, finalized));
} else if let Some(_cold_state_slot) = chain
.store
.load_cold_state_slot(root)
Expand All @@ -77,7 +95,7 @@ impl StateId {
.is_optimistic_or_invalid_block_no_fallback(&finalized_root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?;
return Ok((*root, execution_optimistic));
return Ok((*root, execution_optimistic, true));
} else {
return Err(warp_utils::reject::custom_not_found(format!(
"beacon state for state root {}",
Expand All @@ -94,7 +112,7 @@ impl StateId {
warp_utils::reject::custom_not_found(format!("beacon state at slot {}", slot))
})?;

Ok((root, execution_optimistic))
Ok((root, execution_optimistic, finalized))
}

/// Return the `fork` field of the state identified by `self`.
Expand All @@ -103,9 +121,25 @@ impl StateId {
&self,
chain: &BeaconChain<T>,
) -> Result<(Fork, bool), warp::Rejection> {
self.map_state_and_execution_optimistic(chain, |state, execution_optimistic| {
Ok((state.fork(), execution_optimistic))
})
self.map_state_and_execution_optimistic_and_finalized(
chain,
|state, execution_optimistic, _finalized| Ok((state.fork(), execution_optimistic)),
)
}

/// Return the `fork` field of the state identified by `self`.
/// Also returns the `execution_optimistic` value of the state.
/// Also returns the `finalized` value of the state.
pub fn fork_and_execution_optimistic_and_finalized<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<(Fork, bool, bool), warp::Rejection> {
self.map_state_and_execution_optimistic_and_finalized(
chain,
|state, execution_optimistic, finalized| {
Ok((state.fork(), execution_optimistic, finalized))
},
)
}

/// Convenience function to compute `fork` when `execution_optimistic` isn't desired.
Expand All @@ -121,8 +155,8 @@ impl StateId {
pub fn state<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<(BeaconState<T::EthSpec>, ExecutionOptimistic), warp::Rejection> {
let ((state_root, execution_optimistic), slot_opt) = match &self.0 {
) -> Result<(BeaconState<T::EthSpec>, ExecutionOptimistic, Finalized), warp::Rejection> {
let ((state_root, execution_optimistic, finalized), slot_opt) = match &self.0 {
CoreStateId::Head => {
let (cached_head, execution_status) = chain
.canonical_head
Expand All @@ -134,6 +168,7 @@ impl StateId {
.beacon_state
.clone_with_only_committee_caches(),
execution_status.is_optimistic_or_invalid(),
false,
));
}
CoreStateId::Slot(slot) => (self.root(chain)?, Some(*slot)),
Expand All @@ -152,24 +187,25 @@ impl StateId {
})
})?;

Ok((state, execution_optimistic))
Ok((state, execution_optimistic, finalized))
}

/// Map a function across the `BeaconState` identified by `self`.
///
/// The optimistic status of the requested state is also provided to the `func` closure.
/// The optimistic and finalization status of the requested state is also provided to the `func`
/// closure.
///
/// This function will avoid instantiating/copying a new state when `self` points to the head
/// of the chain.
pub fn map_state_and_execution_optimistic<T: BeaconChainTypes, F, U>(
pub fn map_state_and_execution_optimistic_and_finalized<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>,
F: Fn(&BeaconState<T::EthSpec>, bool, bool) -> Result<U, warp::Rejection>,
{
let (state, execution_optimistic) = match &self.0 {
let (state, execution_optimistic, finalized) = match &self.0 {
CoreStateId::Head => {
let (head, execution_status) = chain
.canonical_head
Expand All @@ -178,12 +214,13 @@ impl StateId {
return func(
&head.snapshot.beacon_state,
execution_status.is_optimistic_or_invalid(),
false,
);
}
_ => self.state(chain)?,
};

func(&state, execution_optimistic)
func(&state, execution_optimistic, finalized)
}
}

Expand Down
6 changes: 3 additions & 3 deletions beacon_node/http_api/src/sync_committee_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub fn compute_sync_committee_rewards<T: BeaconChainTypes>(
block_id: BlockId,
validators: Vec<ValidatorId>,
log: Logger,
) -> Result<(Option<Vec<SyncCommitteeReward>>, ExecutionOptimistic), warp::Rejection> {
let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
) -> Result<(Option<Vec<SyncCommitteeReward>>, ExecutionOptimistic, bool), warp::Rejection> {
let (block, execution_optimistic, finalized) = block_id.blinded_block(&chain)?;

let mut state = get_state_before_applying_block(chain.clone(), &block)?;

Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn compute_sync_committee_rewards<T: BeaconChainTypes>(
)
};

Ok((data, execution_optimistic))
Ok((data, execution_optimistic, finalized))
}

pub fn get_state_before_applying_block<T: BeaconChainTypes>(
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/src/validator_inclusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn end_of_epoch_state<T: BeaconChainTypes>(
let target_slot = epoch.end_slot(T::EthSpec::slots_per_epoch());
// 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)?;
let (state, _execution_status, _finalized) = StateId::from_slot(target_slot).state(chain)?;
Ok(state)
}

Expand Down
13 changes: 7 additions & 6 deletions beacon_node/http_api/src/version.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::api_types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse;
use crate::api_types::EndpointVersion;
use eth2::CONSENSUS_VERSION_HEADER;
use serde::Serialize;
use types::{
ExecutionOptimisticForkVersionedResponse, ForkName, ForkVersionedResponse, InconsistentFork,
};
use types::{ForkName, ForkVersionedResponse, InconsistentFork};
use warp::reply::{self, Reply, Response};

pub const V1: EndpointVersion = EndpointVersion(1);
Expand All @@ -27,22 +26,24 @@ pub fn fork_versioned_response<T: Serialize>(
})
}

pub fn execution_optimistic_fork_versioned_response<T: Serialize>(
pub fn execution_optimistic_finalized_fork_versioned_response<T: Serialize>(
endpoint_version: EndpointVersion,
fork_name: ForkName,
execution_optimistic: bool,
finalized: bool,
data: T,
) -> Result<ExecutionOptimisticForkVersionedResponse<T>, warp::reject::Rejection> {
) -> Result<ExecutionOptimisticFinalizedForkVersionedResponse<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 {
Ok(ExecutionOptimisticFinalizedForkVersionedResponse {
version: fork_name,
execution_optimistic: Some(execution_optimistic),
finalized: Some(finalized),
data,
})
}
Expand Down
Loading

0 comments on commit 3d4bea3

Please sign in to comment.