Support SSZ request body for POST /beacon/blinded_blocks endpoints (v1 & v2) (#4504)
## Issue Addressed #4262 ## Proposed Changes add SSZ support in request body for POST /beacon/blinded_blocks endpoints (v1 & v2) ## Additional Info
This commit is contained in:
parent
31daf3a87c
commit
521432129d
@ -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
|
let post_beacon_blinded_blocks_v2 = eth_v2
|
||||||
.and(warp::path("beacon"))
|
.and(warp::path("beacon"))
|
||||||
.and(warp::path("blinded_blocks"))
|
.and(warp::path("blinded_blocks"))
|
||||||
@ -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 {
|
let block_id_or_err = warp::path::param::<BlockId>().or_else(|_| async {
|
||||||
Err(warp_utils::reject::custom_bad_request(
|
Err(warp_utils::reject::custom_bad_request(
|
||||||
"Invalid block ID".to_string(),
|
"Invalid block ID".to_string(),
|
||||||
@ -4073,7 +4165,12 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
warp::post().and(
|
warp::post().and(
|
||||||
warp::header::exact("Content-Type", "application/octet-stream")
|
warp::header::exact("Content-Type", "application/octet-stream")
|
||||||
// Routes which expect `application/octet-stream` go within this `and`.
|
// 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_blocks)
|
||||||
.uor(post_beacon_blinded_blocks)
|
.uor(post_beacon_blinded_blocks)
|
||||||
.uor(post_beacon_blocks_v2)
|
.uor(post_beacon_blocks_v2)
|
||||||
|
@ -851,6 +851,49 @@ pub async fn blinded_gossip_full_pass() {
|
|||||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
.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`.
|
/// 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)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
pub async fn blinded_consensus_invalid() {
|
pub async fn blinded_consensus_invalid() {
|
||||||
|
@ -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>>(
|
pub async fn test_blinded_block_production_no_verify_randao<Payload: AbstractExecPayload<E>>(
|
||||||
self,
|
self,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -4704,6 +4764,14 @@ async fn blinded_block_production_full_payload_premerge() {
|
|||||||
.await;
|
.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)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
|
async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
|
||||||
ApiTester::new()
|
ApiTester::new()
|
||||||
@ -4713,6 +4781,15 @@ async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
|
|||||||
.await;
|
.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)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn blinded_block_production_no_verify_randao_full_payload_premerge() {
|
async fn blinded_block_production_no_verify_randao_full_payload_premerge() {
|
||||||
ApiTester::new()
|
ApiTester::new()
|
||||||
|
@ -742,6 +742,26 @@ impl BeaconNodeHttpClient {
|
|||||||
Ok(())
|
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(
|
pub fn post_beacon_blocks_v2_path(
|
||||||
&self,
|
&self,
|
||||||
validation_level: Option<BroadcastValidation>,
|
validation_level: Option<BroadcastValidation>,
|
||||||
@ -829,6 +849,23 @@ impl BeaconNodeHttpClient {
|
|||||||
Ok(())
|
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`
|
/// Path for `v2/beacon/blocks`
|
||||||
pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
|
pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
|
||||||
let mut path = self.eth_path(V2)?;
|
let mut path = self.eth_path(V2)?;
|
||||||
|
Loading…
Reference in New Issue
Block a user