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
|
// GET beacon/headers
|
||||||
//
|
//
|
||||||
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
|
// 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
|
* beacon/pool
|
||||||
*/
|
*/
|
||||||
@ -3164,10 +3240,12 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.or(get_beacon_state_validators.boxed())
|
.or(get_beacon_state_validators.boxed())
|
||||||
.or(get_beacon_state_committees.boxed())
|
.or(get_beacon_state_committees.boxed())
|
||||||
.or(get_beacon_state_sync_committees.boxed())
|
.or(get_beacon_state_sync_committees.boxed())
|
||||||
|
.or(get_beacon_state_randao.boxed())
|
||||||
.or(get_beacon_headers.boxed())
|
.or(get_beacon_headers.boxed())
|
||||||
.or(get_beacon_headers_block_id.boxed())
|
.or(get_beacon_headers_block_id.boxed())
|
||||||
.or(get_beacon_block.boxed())
|
.or(get_beacon_block.boxed())
|
||||||
.or(get_beacon_block_attestations.boxed())
|
.or(get_beacon_block_attestations.boxed())
|
||||||
|
.or(get_beacon_blinded_block.boxed())
|
||||||
.or(get_beacon_block_root.boxed())
|
.or(get_beacon_block_root.boxed())
|
||||||
.or(get_beacon_pool_attestations.boxed())
|
.or(get_beacon_pool_attestations.boxed())
|
||||||
.or(get_beacon_pool_attester_slashings.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_lighthouse_merge_readiness.boxed())
|
||||||
.or(get_events.boxed()),
|
.or(get_events.boxed()),
|
||||||
)
|
)
|
||||||
|
.boxed()
|
||||||
.or(warp::post().and(
|
.or(warp::post().and(
|
||||||
post_beacon_blocks
|
post_beacon_blocks
|
||||||
.boxed()
|
.boxed()
|
||||||
|
@ -745,6 +745,36 @@ impl ApiTester {
|
|||||||
self
|
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 {
|
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);
|
||||||
@ -1016,6 +1046,82 @@ impl ApiTester {
|
|||||||
self
|
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 {
|
pub async fn test_beacon_blocks_attestations(self) -> Self {
|
||||||
for block_id in self.interesting_block_ids() {
|
for block_id in self.interesting_block_ids() {
|
||||||
let result = self
|
let result = self
|
||||||
@ -3696,6 +3802,8 @@ async fn beacon_get() {
|
|||||||
.await
|
.await
|
||||||
.test_beacon_states_validator_id()
|
.test_beacon_states_validator_id()
|
||||||
.await
|
.await
|
||||||
|
.test_beacon_states_randao()
|
||||||
|
.await
|
||||||
.test_beacon_headers_all_slots()
|
.test_beacon_headers_all_slots()
|
||||||
.await
|
.await
|
||||||
.test_beacon_headers_all_parents()
|
.test_beacon_headers_all_parents()
|
||||||
@ -3704,6 +3812,8 @@ async fn beacon_get() {
|
|||||||
.await
|
.await
|
||||||
.test_beacon_blocks()
|
.test_beacon_blocks()
|
||||||
.await
|
.await
|
||||||
|
.test_beacon_blinded_blocks()
|
||||||
|
.await
|
||||||
.test_beacon_blocks_attestations()
|
.test_beacon_blocks_attestations()
|
||||||
.await
|
.await
|
||||||
.test_beacon_blocks_root()
|
.test_beacon_blocks_root()
|
||||||
|
@ -518,6 +518,29 @@ impl BeaconNodeHttpClient {
|
|||||||
self.get(path).await
|
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}`
|
/// `GET beacon/states/{state_id}/validators/{validator_id}`
|
||||||
///
|
///
|
||||||
/// Returns `Ok(None)` on a 404 error.
|
/// Returns `Ok(None)` on a 404 error.
|
||||||
@ -636,6 +659,17 @@ impl BeaconNodeHttpClient {
|
|||||||
Ok(path)
|
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`
|
/// `GET v2/beacon/blocks`
|
||||||
///
|
///
|
||||||
/// Returns `Ok(None)` on a 404 error.
|
/// 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)
|
/// `GET v1/beacon/blocks` (LEGACY)
|
||||||
///
|
///
|
||||||
/// Returns `Ok(None)` on a 404 error.
|
/// Returns `Ok(None)` on a 404 error.
|
||||||
@ -714,6 +793,24 @@ impl BeaconNodeHttpClient {
|
|||||||
.transpose()
|
.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`
|
/// `GET beacon/blocks/{block_id}/root`
|
||||||
///
|
///
|
||||||
/// Returns `Ok(None)` on a 404 error.
|
/// Returns `Ok(None)` on a 404 error.
|
||||||
|
@ -455,6 +455,11 @@ pub struct SyncCommitteesQuery {
|
|||||||
pub epoch: Option<Epoch>,
|
pub epoch: Option<Epoch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RandaoQuery {
|
||||||
|
pub epoch: Option<Epoch>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AttestationPoolQuery {
|
pub struct AttestationPoolQuery {
|
||||||
pub slot: Option<Slot>,
|
pub slot: Option<Slot>,
|
||||||
@ -486,6 +491,11 @@ pub struct SyncCommitteeByValidatorIndices {
|
|||||||
pub validator_aggregates: Vec<SyncSubcommittee>,
|
pub validator_aggregates: Vec<SyncSubcommittee>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct RandaoMix {
|
||||||
|
pub randao: Hash256,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct SyncSubcommittee {
|
pub struct SyncSubcommittee {
|
||||||
|
Loading…
Reference in New Issue
Block a user