Blinded block and RANDAO APIs (#3571)
## Issue Addressed https://github.com/ethereum/beacon-APIs/pull/241 https://github.com/ethereum/beacon-APIs/pull/242 ## Proposed Changes Implement two new endpoints for fetching blinded blocks and RANDAO mixes. Co-authored-by: realbigsean <sean@sigmaprime.io>
This commit is contained in:
parent
266d765285
commit
d99bfcf1a5
@ -891,6 +891,37 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
// GET beacon/states/{state_id}/randao?epoch
|
||||
let get_beacon_state_randao = beacon_states_path
|
||||
.clone()
|
||||
.and(warp::path("randao"))
|
||||
.and(warp::query::<api_types::RandaoQuery>())
|
||||
.and(warp::path::end())
|
||||
.and_then(
|
||||
|state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::RandaoQuery| {
|
||||
blocking_json_task(move || {
|
||||
let (randao, execution_optimistic) = state_id
|
||||
.map_state_and_execution_optimistic(
|
||||
&chain,
|
||||
|state, execution_optimistic| {
|
||||
let epoch = query.epoch.unwrap_or_else(|| state.current_epoch());
|
||||
let randao = *state.get_randao_mix(epoch).map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!(
|
||||
"epoch out of range: {e:?}"
|
||||
))
|
||||
})?;
|
||||
Ok((randao, execution_optimistic))
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(
|
||||
api_types::GenericResponse::from(api_types::RandaoMix { randao })
|
||||
.add_execution_optimistic(execution_optimistic),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// GET beacon/headers
|
||||
//
|
||||
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
|
||||
@ -1167,6 +1198,51 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
})
|
||||
});
|
||||
|
||||
// GET beacon/blinded_blocks/{block_id}
|
||||
let get_beacon_blinded_block = eth_v1
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blinded_blocks"))
|
||||
.and(block_id_or_err)
|
||||
.and(chain_filter.clone())
|
||||
.and(warp::path::end())
|
||||
.and(warp::header::optional::<api_types::Accept>("accept"))
|
||||
.and_then(
|
||||
|block_id: BlockId,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
accept_header: Option<api_types::Accept>| {
|
||||
blocking_task(move || {
|
||||
let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
|
||||
let fork_name = block
|
||||
.fork_name(&chain.spec)
|
||||
.map_err(inconsistent_fork_rejection)?;
|
||||
|
||||
match accept_header {
|
||||
Some(api_types::Accept::Ssz) => Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.body(block.as_ssz_bytes().into())
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"failed to create response: {}",
|
||||
e
|
||||
))
|
||||
}),
|
||||
_ => {
|
||||
// Post as a V2 endpoint so we return the fork version.
|
||||
execution_optimistic_fork_versioned_response(
|
||||
V2,
|
||||
fork_name,
|
||||
execution_optimistic,
|
||||
block,
|
||||
)
|
||||
.map(|res| warp::reply::json(&res).into_response())
|
||||
}
|
||||
}
|
||||
.map(|resp| add_consensus_version_header(resp, fork_name))
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
/*
|
||||
* beacon/pool
|
||||
*/
|
||||
@ -3164,10 +3240,12 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.or(get_beacon_state_validators.boxed())
|
||||
.or(get_beacon_state_committees.boxed())
|
||||
.or(get_beacon_state_sync_committees.boxed())
|
||||
.or(get_beacon_state_randao.boxed())
|
||||
.or(get_beacon_headers.boxed())
|
||||
.or(get_beacon_headers_block_id.boxed())
|
||||
.or(get_beacon_block.boxed())
|
||||
.or(get_beacon_block_attestations.boxed())
|
||||
.or(get_beacon_blinded_block.boxed())
|
||||
.or(get_beacon_block_root.boxed())
|
||||
.or(get_beacon_pool_attestations.boxed())
|
||||
.or(get_beacon_pool_attester_slashings.boxed())
|
||||
@ -3212,6 +3290,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.or(get_lighthouse_merge_readiness.boxed())
|
||||
.or(get_events.boxed()),
|
||||
)
|
||||
.boxed()
|
||||
.or(warp::post().and(
|
||||
post_beacon_blocks
|
||||
.boxed()
|
||||
|
@ -745,6 +745,36 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_beacon_states_randao(self) -> Self {
|
||||
for state_id in self.interesting_state_ids() {
|
||||
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 result = self
|
||||
.client
|
||||
.get_beacon_states_randao(state_id.0, epoch_opt)
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|res| res.data);
|
||||
|
||||
if result.is_none() && state_opt.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let state = state_opt.as_mut().expect("result should be none");
|
||||
let randao_mix = state
|
||||
.get_randao_mix(state.slot().epoch(E::slots_per_epoch()))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.unwrap().randao, *randao_mix);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_beacon_headers_all_slots(self) -> Self {
|
||||
for slot in 0..CHAIN_LENGTH {
|
||||
let slot = Slot::from(slot);
|
||||
@ -1016,6 +1046,82 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_beacon_blinded_blocks(self) -> Self {
|
||||
for block_id in self.interesting_block_ids() {
|
||||
let expected = block_id
|
||||
.blinded_block(&self.chain)
|
||||
.ok()
|
||||
.map(|(block, _execution_optimistic)| block);
|
||||
|
||||
if let CoreBlockId::Slot(slot) = block_id.0 {
|
||||
if expected.is_none() {
|
||||
assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
|
||||
} else {
|
||||
assert!(!SKIPPED_SLOTS.contains(&slot.as_u64()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the JSON endpoint.
|
||||
let json_result = self
|
||||
.client
|
||||
.get_beacon_blinded_blocks(block_id.0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let (Some(json), Some(expected)) = (&json_result, &expected) {
|
||||
assert_eq!(&json.data, expected, "{:?}", block_id);
|
||||
assert_eq!(
|
||||
json.version,
|
||||
Some(expected.fork_name(&self.chain.spec).unwrap())
|
||||
);
|
||||
} else {
|
||||
assert_eq!(json_result, None);
|
||||
assert_eq!(expected, None);
|
||||
}
|
||||
|
||||
// Check the SSZ endpoint.
|
||||
let ssz_result = self
|
||||
.client
|
||||
.get_beacon_blinded_blocks_ssz(block_id.0, &self.chain.spec)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(ssz_result.as_ref(), expected.as_ref(), "{:?}", block_id);
|
||||
|
||||
// Check that version headers are provided.
|
||||
let url = self
|
||||
.client
|
||||
.get_beacon_blinded_blocks_path(block_id.0)
|
||||
.unwrap();
|
||||
|
||||
let builders: Vec<fn(RequestBuilder) -> RequestBuilder> = vec![
|
||||
|b| b,
|
||||
|b| b.accept(Accept::Ssz),
|
||||
|b| b.accept(Accept::Json),
|
||||
|b| b.accept(Accept::Any),
|
||||
];
|
||||
|
||||
for req_builder in builders {
|
||||
let raw_res = self
|
||||
.client
|
||||
.get_response(url.clone(), req_builder)
|
||||
.await
|
||||
.optional()
|
||||
.unwrap();
|
||||
if let (Some(raw_res), Some(expected)) = (&raw_res, &expected) {
|
||||
assert_eq!(
|
||||
raw_res.fork_name_from_header().unwrap(),
|
||||
Some(expected.fork_name(&self.chain.spec).unwrap())
|
||||
);
|
||||
} else {
|
||||
assert!(raw_res.is_none());
|
||||
assert_eq!(expected, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_beacon_blocks_attestations(self) -> Self {
|
||||
for block_id in self.interesting_block_ids() {
|
||||
let result = self
|
||||
@ -3696,6 +3802,8 @@ async fn beacon_get() {
|
||||
.await
|
||||
.test_beacon_states_validator_id()
|
||||
.await
|
||||
.test_beacon_states_randao()
|
||||
.await
|
||||
.test_beacon_headers_all_slots()
|
||||
.await
|
||||
.test_beacon_headers_all_parents()
|
||||
@ -3704,6 +3812,8 @@ async fn beacon_get() {
|
||||
.await
|
||||
.test_beacon_blocks()
|
||||
.await
|
||||
.test_beacon_blinded_blocks()
|
||||
.await
|
||||
.test_beacon_blocks_attestations()
|
||||
.await
|
||||
.test_beacon_blocks_root()
|
||||
|
@ -518,6 +518,29 @@ impl BeaconNodeHttpClient {
|
||||
self.get(path).await
|
||||
}
|
||||
|
||||
/// `GET beacon/states/{state_id}/randao?epoch`
|
||||
pub async fn get_beacon_states_randao(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
epoch: Option<Epoch>,
|
||||
) -> Result<Option<ExecutionOptimisticResponse<RandaoMix>>, 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("randao");
|
||||
|
||||
if let Some(epoch) = epoch {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("epoch", &epoch.to_string());
|
||||
}
|
||||
|
||||
self.get_opt(path).await
|
||||
}
|
||||
|
||||
/// `GET beacon/states/{state_id}/validators/{validator_id}`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
@ -636,6 +659,17 @@ impl BeaconNodeHttpClient {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Path for `v1/beacon/blinded_blocks/{block_id}`
|
||||
pub fn get_beacon_blinded_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("beacon")
|
||||
.push("blinded_blocks")
|
||||
.push(&block_id.to_string());
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// `GET v2/beacon/blocks`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
@ -680,6 +714,51 @@ impl BeaconNodeHttpClient {
|
||||
}))
|
||||
}
|
||||
|
||||
/// `GET v1/beacon/blinded_blocks/{block_id}`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn get_beacon_blinded_blocks<T: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<ExecutionOptimisticForkVersionedResponse<SignedBlindedBeaconBlock<T>>>, Error>
|
||||
{
|
||||
let path = self.get_beacon_blinded_blocks_path(block_id)?;
|
||||
let response = match self.get_response(path, |b| b).await.optional()? {
|
||||
Some(res) => res,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// 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.
|
||||
let (block, version, execution_optimistic) = match response.fork_name_from_header() {
|
||||
Ok(Some(fork_name)) => {
|
||||
let (data, (version, execution_optimistic)) =
|
||||
map_fork_name_with!(fork_name, SignedBlindedBeaconBlock, {
|
||||
let ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data,
|
||||
} = response.json().await?;
|
||||
(data, (version, execution_optimistic))
|
||||
});
|
||||
(data, version, execution_optimistic)
|
||||
}
|
||||
Ok(None) | Err(_) => {
|
||||
let ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data,
|
||||
} = response.json().await?;
|
||||
(data, version, execution_optimistic)
|
||||
}
|
||||
};
|
||||
Ok(Some(ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data: block,
|
||||
}))
|
||||
}
|
||||
|
||||
/// `GET v1/beacon/blocks` (LEGACY)
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
@ -714,6 +793,24 @@ impl BeaconNodeHttpClient {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// `GET beacon/blinded_blocks/{block_id}` as SSZ
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn get_beacon_blinded_blocks_ssz<T: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<SignedBlindedBeaconBlock<T>>, Error> {
|
||||
let path = self.get_beacon_blinded_blocks_path(block_id)?;
|
||||
|
||||
self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_beacon_blocks_ssz)
|
||||
.await?
|
||||
.map(|bytes| {
|
||||
SignedBlindedBeaconBlock::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// `GET beacon/blocks/{block_id}/root`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
|
@ -455,6 +455,11 @@ pub struct SyncCommitteesQuery {
|
||||
pub epoch: Option<Epoch>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RandaoQuery {
|
||||
pub epoch: Option<Epoch>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AttestationPoolQuery {
|
||||
pub slot: Option<Slot>,
|
||||
@ -486,6 +491,11 @@ pub struct SyncCommitteeByValidatorIndices {
|
||||
pub validator_aggregates: Vec<SyncSubcommittee>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RandaoMix {
|
||||
pub randao: Hash256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SyncSubcommittee {
|
||||
|
Loading…
Reference in New Issue
Block a user