Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion beacon_node/http_api/src/beacon/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ use crate::task_spawner::{Priority, TaskSpawner};
use crate::utils::ResponseFilter;
use crate::validator::pubkey_to_validator_index;
use crate::version::{
ResponseIncludesVersion, add_consensus_version_header,
ResponseIncludesVersion, add_consensus_version_header, add_ssz_content_type_header,
execution_optimistic_finalized_beacon_response,
};
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
use eth2::types::{
ValidatorBalancesRequestBody, ValidatorId, ValidatorIdentitiesRequestBody,
ValidatorsRequestBody,
};
use ssz::Encode;
use std::sync::Arc;
use types::{AttestationShufflingId, BeaconStateError, CommitteeCache, EthSpec, RelativeEpoch};
use warp::filters::BoxedFilter;
use warp::http::Response;
use warp::hyper::Body;
use warp::{Filter, Reply};
use warp_utils::query::multi_key_query;

Expand Down Expand Up @@ -68,6 +71,67 @@ pub fn get_beacon_state_pending_consolidations<T: BeaconChainTypes>(
.boxed()
}

// GET beacon/states/{state_id}/proposer_lookahead
pub fn get_beacon_state_proposer_lookahead<T: BeaconChainTypes>(
beacon_states_path: BeaconStatesPath<T>,
) -> ResponseFilter {
beacon_states_path
.clone()
.and(warp::path("proposer_lookahead"))
.and(warp::path::end())
.and(warp::header::optional::<eth2::types::Accept>("accept"))
.then(
|state_id: StateId,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
accept_header: Option<eth2::types::Accept>| {
task_spawner.blocking_response_task(Priority::P1, move || {
let (data, execution_optimistic, finalized, fork_name) = state_id
.map_state_and_execution_optimistic_and_finalized(
&chain,
|state, execution_optimistic, finalized| {
let Ok(lookahead) = state.proposer_lookahead() else {
return Err(warp_utils::reject::custom_bad_request(
"Proposer lookahead is not available for pre-Fulu states"
.to_string(),
));
};

Ok((
lookahead.to_vec(),
execution_optimistic,
finalized,
state.fork_name_unchecked(),
))
},
)?;

match accept_header {
Some(eth2::types::Accept::Ssz) => Response::builder()
.status(200)
.body(data.as_ssz_bytes().into())
.map(|res: Response<Body>| add_ssz_content_type_header(res))
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
_ => execution_optimistic_finalized_beacon_response(
ResponseIncludesVersion::Yes(fork_name),
execution_optimistic,
finalized,
data,
)
.map(|res| warp::reply::json(&res).into_response()),
}
.map(|resp| add_consensus_version_header(resp, fork_name))
})
},
)
.boxed()
}

// GET beacon/states/{state_id}/pending_partial_withdrawals
pub fn get_beacon_state_pending_partial_withdrawals<T: BeaconChainTypes>(
beacon_states_path: BeaconStatesPath<T>,
Expand Down
5 changes: 5 additions & 0 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ pub fn serve<T: BeaconChainTypes>(
let get_beacon_state_pending_consolidations =
states::get_beacon_state_pending_consolidations(beacon_states_path.clone());

// GET beacon/states/{state_id}/proposer_lookahead
let get_beacon_state_proposer_lookahead =
states::get_beacon_state_proposer_lookahead(beacon_states_path.clone());

// GET beacon/headers
//
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
Expand Down Expand Up @@ -3333,6 +3337,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(get_beacon_state_pending_deposits)
.uor(get_beacon_state_pending_partial_withdrawals)
.uor(get_beacon_state_pending_consolidations)
.uor(get_beacon_state_proposer_lookahead)
.uor(get_beacon_headers)
.uor(get_beacon_headers_block_id)
.uor(get_beacon_block)
Expand Down
49 changes: 49 additions & 0 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,40 @@ impl ApiTester {
self
}

pub async fn test_beacon_states_proposer_lookahead(self) -> Self {
for state_id in self.interesting_state_ids() {
let mut state_opt = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic, _finalized)| state);

let result = match self
.client
.get_beacon_states_proposer_lookahead(state_id.0)
.await
{
Ok(response) => response,
Err(e) => panic!("query failed incorrectly: {e:?}"),
};

if result.is_none() && state_opt.is_none() {
continue;
}

let state = state_opt.as_mut().expect("result should be none");
let expected = state.proposer_lookahead().unwrap();

let response = result.unwrap();
assert_eq!(response.data(), &expected.to_vec());

// Check that the version header is returned in the response
let fork_name = state.fork_name(&self.chain.spec).unwrap();
assert_eq!(response.version(), Some(fork_name),);
}

self
}

pub async fn test_beacon_headers_all_slots(self) -> Self {
for slot in 0..CHAIN_LENGTH {
let slot = Slot::from(slot);
Expand Down Expand Up @@ -7295,6 +7329,21 @@ async fn beacon_get_state_info_electra() {
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn beacon_get_state_info_fulu() {
let mut config = ApiTesterConfig::default();
config.spec.altair_fork_epoch = Some(Epoch::new(0));
config.spec.bellatrix_fork_epoch = Some(Epoch::new(0));
config.spec.capella_fork_epoch = Some(Epoch::new(0));
config.spec.deneb_fork_epoch = Some(Epoch::new(0));
config.spec.electra_fork_epoch = Some(Epoch::new(0));
config.spec.fulu_fork_epoch = Some(Epoch::new(0));
ApiTester::new_from_config(config)
.await
.test_beacon_states_proposer_lookahead()
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn beacon_get_blocks() {
ApiTester::new()
Expand Down
21 changes: 21 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,27 @@ impl BeaconNodeHttpClient {
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}

/// `GET beacon/states/{state_id}/proposer_lookahead`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_proposer_lookahead(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<u64>>>, Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("states")
.push(&state_id.to_string())
.push("proposer_lookahead");

self.get_fork_contextual(path, |fork| fork)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test too for the SSZ variant?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yh , done @dapplion


/// `GET beacon/light_client/updates`
///
/// Returns `Ok(None)` on a 404 error.
Expand Down