Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
85 changes: 84 additions & 1 deletion beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use proto_array::ExecutionStatus;
use reqwest::{RequestBuilder, Response, StatusCode};
use sensitive_url::SensitiveUrl;
use slot_clock::SlotClock;
use ssz::BitList;
use ssz::{BitList, Decode};
use state_processing::per_block_processing::get_expected_withdrawals;
use state_processing::per_slot_processing;
use state_processing::state_advance::partial_state_advance;
Expand Down Expand Up @@ -1418,6 +1418,72 @@ 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_states_proposer_lookahead_ssz(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_ssz(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 ssz_bytes = result.unwrap();
let decoded = Vec::<u64>::from_ssz_bytes(&ssz_bytes)
.expect("should decode SSZ proposer lookahead");
assert_eq!(decoded, expected.to_vec());
}

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 +7361,23 @@ 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
.test_beacon_states_proposer_lookahead_ssz()
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn beacon_get_blocks() {
ApiTester::new()
Expand Down
41 changes: 41 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,47 @@ 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/states/{state_id}/proposer_lookahead`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_proposer_lookahead_ssz(
&self,
state_id: StateId,
) -> Result<Option<Vec<u8>>, 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_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.default)
.await
}

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