Skip to content

Commit

Permalink
Support SSZ request body for POST /beacon/blinded_blocks endpoints (v…
Browse files Browse the repository at this point in the history
…1 & v2) (#4504)

## Issue Addressed

#4262 

## Proposed Changes

add SSZ support in request body for POST /beacon/blinded_blocks endpoints (v1 & v2)

## Additional Info
  • Loading branch information
eserilev committed Aug 7, 2023
1 parent 31daf3a commit 43819dc
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 1 deletion.
99 changes: 98 additions & 1 deletion beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,46 @@ pub fn serve<T: BeaconChainTypes>(
},
);

// POST beacon/blocks
let post_beacon_blinded_blocks_ssz =
eth_v1
.and(warp::path("beacon"))
.and(warp::path("blinded_blocks"))
.and(warp::path::end())
.and(warp::body::bytes())
.and(chain_filter.clone())
.and(network_tx_filter.clone())
.and(log_filter.clone())
.and_then(
|block_bytes: Bytes,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| async move {
let block =
match SignedBeaconBlock::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
&block_bytes,
&chain.spec,
) {
Ok(data) => data,
Err(e) => {
return Err(warp_utils::reject::custom_bad_request(format!(
"{:?}",
e
)))
}
};
publish_blocks::publish_blinded_block(
block,
chain,
&network_tx,
log,
BroadcastValidation::default(),
)
.await
.map(|()| warp::reply().into_response())
},
);

let post_beacon_blinded_blocks_v2 = eth_v2
.and(warp::path("beacon"))
.and(warp::path("blinded_blocks"))
Expand Down Expand Up @@ -1428,6 +1468,58 @@ pub fn serve<T: BeaconChainTypes>(
},
);

let post_beacon_blinded_blocks_v2_ssz =
eth_v2
.and(warp::path("beacon"))
.and(warp::path("blinded_blocks"))
.and(warp::query::<api_types::BroadcastValidationQuery>())
.and(warp::path::end())
.and(warp::body::bytes())
.and(chain_filter.clone())
.and(network_tx_filter.clone())
.and(log_filter.clone())
.then(
|validation_level: api_types::BroadcastValidationQuery,
block_bytes: Bytes,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| async move {
let block =
match SignedBeaconBlock::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
&block_bytes,
&chain.spec,
) {
Ok(data) => data,
Err(_) => {
return warp::reply::with_status(
StatusCode::BAD_REQUEST,
eth2::StatusCode::BAD_REQUEST,
)
.into_response();
}
};
match publish_blocks::publish_blinded_block(
block,
chain,
&network_tx,
log,
validation_level.broadcast_validation,
)
.await
{
Ok(()) => warp::reply().into_response(),
Err(e) => match warp_utils::reject::handle_rejection(e).await {
Ok(reply) => reply.into_response(),
Err(_) => warp::reply::with_status(
StatusCode::INTERNAL_SERVER_ERROR,
eth2::StatusCode::INTERNAL_SERVER_ERROR,
)
.into_response(),
},
}
},
);

let block_id_or_err = warp::path::param::<BlockId>().or_else(|_| async {
Err(warp_utils::reject::custom_bad_request(
"Invalid block ID".to_string(),
Expand Down Expand Up @@ -4073,7 +4165,12 @@ pub fn serve<T: BeaconChainTypes>(
warp::post().and(
warp::header::exact("Content-Type", "application/octet-stream")
// Routes which expect `application/octet-stream` go within this `and`.
.and(post_beacon_blocks_ssz.uor(post_beacon_blocks_v2_ssz))
.and(
post_beacon_blocks_ssz
.uor(post_beacon_blocks_v2_ssz)
.uor(post_beacon_blinded_blocks_ssz)
.uor(post_beacon_blinded_blocks_v2_ssz),
)
.uor(post_beacon_blocks)
.uor(post_beacon_blinded_blocks)
.uor(post_beacon_blocks_v2)
Expand Down
43 changes: 43 additions & 0 deletions beacon_node/http_api/tests/broadcast_validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,49 @@ pub async fn blinded_gossip_full_pass() {
.block_is_known_to_fork_choice(&block.canonical_root()));
}

// This test checks that a block that is valid from both a gossip and consensus perspective is accepted when using `broadcast_validation=gossip`.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn blinded_gossip_full_pass_ssz() {
/* this test targets gossip-level validation */
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);

// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
// `validator_count // 32`.
let validator_count = 64;
let num_initial: u64 = 31;
let tester = InteractiveTester::<E>::new(None, validator_count).await;

// Create some chain depth.
tester.harness.advance_slot();
tester
.harness
.extend_chain(
num_initial as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await;
tester.harness.advance_slot();

let slot_a = Slot::new(num_initial);
let slot_b = slot_a + 1;

let state_a = tester.harness.get_current_state();
let (block, _): (SignedBlindedBeaconBlock<E>, _) =
tester.harness.make_blinded_block(state_a, slot_b).await;

let response: Result<(), eth2::Error> = tester
.client
.post_beacon_blinded_blocks_v2_ssz(&block, validation_level)
.await;

assert!(response.is_ok());
assert!(tester
.harness
.chain
.block_is_known_to_fork_choice(&block.canonical_root()));
}

/// This test checks that a block that is **invalid** from a gossip perspective gets rejected when using `broadcast_validation=consensus`.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn blinded_consensus_invalid() {
Expand Down
77 changes: 77 additions & 0 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2578,6 +2578,66 @@ impl ApiTester {
}
}

pub async fn test_blinded_block_production_ssz<Payload: AbstractExecPayload<E>>(&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 = self
.client
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
.await
.unwrap()
.data;

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

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

// This converts the generic `Payload` to a concrete type for comparison.
let head_block = SignedBeaconBlock::from(signed_block.clone());
assert_eq!(head_block, signed_block);

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

pub async fn test_blinded_block_production_no_verify_randao<Payload: AbstractExecPayload<E>>(
self,
) -> Self {
Expand Down Expand Up @@ -4704,6 +4764,14 @@ async fn blinded_block_production_full_payload_premerge() {
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_ssz_full_payload_premerge() {
ApiTester::new()
.await
.test_blinded_block_production_ssz::<FullPayload<_>>()
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
ApiTester::new()
Expand All @@ -4713,6 +4781,15 @@ async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_ssz_with_skip_slots_full_payload_premerge() {
ApiTester::new()
.await
.skip_slots(E::slots_per_epoch() * 2)
.test_blinded_block_production_ssz::<FullPayload<_>>()
.await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_no_verify_randao_full_payload_premerge() {
ApiTester::new()
Expand Down
37 changes: 37 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,26 @@ impl BeaconNodeHttpClient {
Ok(())
}

/// `POST beacon/blinded_blocks`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn post_beacon_blinded_blocks_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
block: &SignedBeaconBlock<T, Payload>,
) -> Result<(), Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("blinded_blocks");

self.post_generic_with_ssz_body(path, block.as_ssz_bytes(), Some(self.timeouts.proposal))
.await?;

Ok(())
}

pub fn post_beacon_blocks_v2_path(
&self,
validation_level: Option<BroadcastValidation>,
Expand Down Expand Up @@ -829,6 +849,23 @@ impl BeaconNodeHttpClient {
Ok(())
}

/// `POST v2/beacon/blinded_blocks`
pub async fn post_beacon_blinded_blocks_v2_ssz<T: EthSpec>(
&self,
block: &SignedBlindedBeaconBlock<T>,
validation_level: Option<BroadcastValidation>,
) -> Result<(), Error> {
self.post_generic_with_consensus_version_and_ssz_body(
self.post_beacon_blinded_blocks_v2_path(validation_level)?,
block.as_ssz_bytes(),
Some(self.timeouts.proposal),
block.message().body().fork_name(),
)
.await?;

Ok(())
}

/// Path for `v2/beacon/blocks`
pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
let mut path = self.eth_path(V2)?;
Expand Down

0 comments on commit 43819dc

Please sign in to comment.