Skip to content

Commit

Permalink
Add SSZ support to validator block production endpoints (#4534)
Browse files Browse the repository at this point in the history
## Issue Addressed

#4531 

## Proposed Changes

add SSZ support to the following block production endpoints:

GET /eth/v2/validator/blocks/{slot}
GET /eth/v1/validator/blinded_blocks/{slot}

## Additional Info

i updated a few existing tests to use ssz instead of writing completely new tests
  • Loading branch information
eserilev committed Sep 21, 2023
1 parent fe3bd03 commit a13478d
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 19 deletions.
48 changes: 41 additions & 7 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3002,13 +3002,15 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end())
.and(not_while_syncing_filter.clone())
.and(warp::query::<api_types::ValidatorBlocksQuery>())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and(task_spawner_filter.clone())
.and(chain_filter.clone())
.and(log_filter.clone())
.then(
|endpoint_version: EndpointVersion,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
accept_header: Option<api_types::Accept>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
log: Logger| {
Expand Down Expand Up @@ -3053,9 +3055,24 @@ pub fn serve<T: BeaconChainTypes>(
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;

fork_versioned_response(endpoint_version, fork_name, block)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name))
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(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
_ => fork_versioned_response(endpoint_version, fork_name, block)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name)),
}
})
},
);
Expand All @@ -3072,11 +3089,13 @@ pub fn serve<T: BeaconChainTypes>(
.and(warp::path::end())
.and(not_while_syncing_filter.clone())
.and(warp::query::<api_types::ValidatorBlocksQuery>())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and(task_spawner_filter.clone())
.and(chain_filter.clone())
.then(
|slot: Slot,
query: api_types::ValidatorBlocksQuery,
accept_header: Option<api_types::Accept>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
Expand Down Expand Up @@ -3114,10 +3133,25 @@ pub fn serve<T: BeaconChainTypes>(
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;

// Pose as a V2 endpoint so we return the fork `version`.
fork_versioned_response(V2, fork_name, block)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name))
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(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
// Pose as a V2 endpoint so we return the fork `version`.
_ => fork_versioned_response(V2, fork_name, block)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name)),
}
})
},
);
Expand Down
87 changes: 84 additions & 3 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2513,6 +2513,70 @@ impl ApiTester {
self
}

pub async fn test_block_production_ssz(self) -> Self {
let fork = self.chain.canonical_head.cached_head().head_fork();
let genesis_validators_root = self.chain.genesis_validators_root;

for _ in 0..E::slots_per_epoch() * 3 {
let slot = self.chain.slot().unwrap();
let epoch = self.chain.epoch().unwrap();

let proposer_pubkey_bytes = self
.client
.get_validator_duties_proposer(epoch)
.await
.unwrap()
.data
.into_iter()
.find(|duty| duty.slot == slot)
.map(|duty| duty.pubkey)
.unwrap();
let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap();

let sk = self
.validator_keypairs()
.iter()
.find(|kp| kp.pk == proposer_pubkey)
.map(|kp| kp.sk.clone())
.unwrap();

let randao_reveal = {
let domain = self.chain.spec.get_domain(
epoch,
Domain::Randao,
&fork,
genesis_validators_root,
);
let message = epoch.signing_root(domain);
sk.sign(message).into()
};

let block_bytes = self
.client
.get_validator_blocks_ssz::<E, FullPayload<E>>(slot, &randao_reveal, None)
.await
.unwrap()
.expect("block bytes");

let block =
BeaconBlock::<E, FullPayload<E>>::from_ssz_bytes(&block_bytes, &self.chain.spec)
.expect("block bytes can be decoded");

let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);

self.client
.post_beacon_blocks_ssz(&signed_block)
.await
.unwrap();

assert_eq!(self.chain.head_beacon_block().as_ref(), &signed_block);

self.chain.slot_clock.set_slot(slot.as_u64() + 1);
}

self
}

pub async fn test_block_production_no_verify_randao(self) -> Self {
for _ in 0..E::slots_per_epoch() {
let slot = self.chain.slot().unwrap();
Expand Down Expand Up @@ -2694,12 +2758,15 @@ impl ApiTester {
sk.sign(message).into()
};

let block = self
let block_bytes = self
.client
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
.get_validator_blinded_blocks_ssz::<E, Payload>(slot, &randao_reveal, None)
.await
.unwrap()
.data;
.expect("block bytes");

let block = BeaconBlock::<E, Payload>::from_ssz_bytes(&block_bytes, &self.chain.spec)
.expect("block bytes can be decoded");

let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);

Expand Down Expand Up @@ -4891,6 +4958,20 @@ async fn block_production_verify_randao_invalid() {
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn block_production_ssz_full_payload() {
ApiTester::new().await.test_block_production_ssz().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn block_production_ssz_with_skip_slots() {
ApiTester::new()
.await
.skip_slots(E::slots_per_epoch() * 2)
.test_block_production_ssz()
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_full_payload_premerge() {
ApiTester::new()
Expand Down
136 changes: 127 additions & 9 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub struct Timeouts {
pub get_beacon_blocks_ssz: Duration,
pub get_debug_beacon_states: Duration,
pub get_deposit_snapshot: Duration,
pub get_validator_block_ssz: Duration,
}

impl Timeouts {
Expand All @@ -135,6 +136,7 @@ impl Timeouts {
get_beacon_blocks_ssz: timeout,
get_debug_beacon_states: timeout,
get_deposit_snapshot: timeout,
get_validator_block_ssz: timeout,
}
}
}
Expand Down Expand Up @@ -1598,14 +1600,14 @@ impl BeaconNodeHttpClient {
.await
}

/// `GET v2/validator/blocks/{slot}`
pub async fn get_validator_blocks_modular<T: EthSpec, Payload: AbstractExecPayload<T>>(
/// returns `GET v2/validator/blocks/{slot}` URL path
pub async fn get_validator_blocks_path<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
) -> Result<Url, Error> {
let mut path = self.eth_path(V2)?;

path.path_segments_mut()
Expand All @@ -1627,9 +1629,66 @@ impl BeaconNodeHttpClient {
.append_pair("skip_randao_verification", "");
}

Ok(path)
}

/// `GET v2/validator/blocks/{slot}`
pub async fn get_validator_blocks_modular<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
let path = self
.get_validator_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;

self.get(path).await
}

/// `GET v2/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<Option<Vec<u8>>, Error> {
self.get_validator_blocks_modular_ssz::<T, Payload>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
)
.await
}

/// `GET v2/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_modular_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Option<Vec<u8>>, Error> {
let path = self
.get_validator_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;

self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block_ssz)
.await
}

/// `GET v2/validator/blinded_blocks/{slot}`
pub async fn get_validator_blinded_blocks<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
Expand All @@ -1646,17 +1705,14 @@ impl BeaconNodeHttpClient {
.await
}

/// `GET v1/validator/blinded_blocks/{slot}`
pub async fn get_validator_blinded_blocks_modular<
T: EthSpec,
Payload: AbstractExecPayload<T>,
>(
/// returns `GET v1/validator/blinded_blocks/{slot}` URL path
pub async fn get_validator_blinded_blocks_path<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
) -> Result<Url, Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
Expand All @@ -1678,9 +1734,71 @@ impl BeaconNodeHttpClient {
.append_key_only("skip_randao_verification");
}

Ok(path)
}

/// `GET v1/validator/blinded_blocks/{slot}`
pub async fn get_validator_blinded_blocks_modular<
T: EthSpec,
Payload: AbstractExecPayload<T>,
>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
let path = self
.get_validator_blinded_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;

self.get(path).await
}

/// `GET v2/validator/blinded_blocks/{slot}` in ssz format
pub async fn get_validator_blinded_blocks_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<Option<Vec<u8>>, Error> {
self.get_validator_blinded_blocks_modular_ssz::<T, Payload>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
)
.await
}

pub async fn get_validator_blinded_blocks_modular_ssz<
T: EthSpec,
Payload: AbstractExecPayload<T>,
>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Option<Vec<u8>>, Error> {
let path = self
.get_validator_blinded_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;

self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block_ssz)
.await
}

/// `GET validator/attestation_data?slot,committee_index`
pub async fn get_validator_attestation_data(
&self,
Expand Down
Loading

0 comments on commit a13478d

Please sign in to comment.