From a380f6ef1f04cd2b483224c7d5494294eeea976c Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 9 Nov 2023 06:05:35 +0200 Subject: [PATCH] Add block-v3 SSZ tests (#4902) * create block-v3 ssz tests * fmt --- beacon_node/http_api/tests/tests.rs | 106 ++++++++++++++++++++++ common/eth2/src/lib.rs | 135 ++++++++++++++++++++++++---- 2 files changed, 222 insertions(+), 19 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index d532859c798..bd238a77990 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2604,6 +2604,98 @@ impl ApiTester { self } + pub async fn test_block_production_v3_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 (fork_version_response_bytes, is_blinded_payload) = self + .client + .get_validator_blocks_v3_ssz::(slot, &randao_reveal, None) + .await + .unwrap(); + + if is_blinded_payload { + let block_contents = >>::from_ssz_bytes( + &fork_version_response_bytes.unwrap(), + &self.chain.spec, + ) + .expect("block contents bytes can be decoded"); + + let signed_block_contents = + block_contents.sign(&sk, &fork, genesis_validators_root, &self.chain.spec); + + self.client + .post_beacon_blocks_ssz(&signed_block_contents) + .await + .unwrap(); + + // This converts the generic `Payload` to a concrete type for comparison. + let signed_block = signed_block_contents.deconstruct().0; + let head_block = SignedBeaconBlock::from(signed_block.clone()); + assert_eq!(head_block, signed_block); + + self.chain.slot_clock.set_slot(slot.as_u64() + 1); + } else { + let block_contents = >>::from_ssz_bytes( + &fork_version_response_bytes.unwrap(), + &self.chain.spec, + ) + .expect("block contents bytes can be decoded"); + + let signed_block_contents = + block_contents.sign(&sk, &fork, genesis_validators_root, &self.chain.spec); + + self.client + .post_beacon_blocks_ssz(&signed_block_contents) + .await + .unwrap(); + + assert_eq!( + self.chain.head_beacon_block().as_ref(), + signed_block_contents.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(); @@ -5053,6 +5145,20 @@ async fn block_production_ssz_with_skip_slots() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn block_production_ssz_v3() { + ApiTester::new().await.test_block_production_v3_ssz().await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn block_production_v3_ssz_with_skip_slots() { + ApiTester::new() + .await + .skip_slots(E::slots_per_epoch() * 2) + .test_block_production_v3_ssz() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn blinded_block_production_full_payload_premerge() { ApiTester::new() diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index c59e59abf5a..043c0f197e5 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -272,6 +272,31 @@ impl BeaconNodeHttpClient { } } + /// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error. + pub async fn get_bytes_response_with_response_headers( + &self, + url: U, + accept_header: Accept, + timeout: Duration, + ) -> Result<(Option>, Option), Error> { + let opt_response = self + .get_response(url, |b| b.accept(accept_header).timeout(timeout)) + .await + .optional()?; + + // let headers = opt_response.headers(); + match opt_response { + Some(resp) => { + let response_headers = resp.headers().clone(); + Ok(( + Some(resp.bytes().await?.into_iter().collect::>()), + Some(response_headers), + )) + } + None => Ok((None, None)), + } + } + /// Perform a HTTP POST request. async fn post(&self, url: U, body: &T) -> Result<(), Error> { self.post_generic(url, body, None).await?; @@ -1684,30 +1709,14 @@ impl BeaconNodeHttpClient { Ok(path) } - /// `GET v3/validator/blocks/{slot}` - pub async fn get_validator_blocks_v3( - &self, - slot: Slot, - randao_reveal: &SignatureBytes, - graffiti: Option<&Graffiti>, - ) -> Result, Error> { - self.get_validator_blocks_v3_modular( - slot, - randao_reveal, - graffiti, - SkipRandaoVerification::No, - ) - .await - } - - /// `GET v3/validator/blocks/{slot}` - pub async fn get_validator_blocks_v3_modular( + /// returns `GET v3/validator/blocks/{slot}` URL path + pub async fn get_validator_blocks_v3_path( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, - ) -> Result, Error> { + ) -> Result { let mut path = self.eth_path(V3)?; path.path_segments_mut() @@ -1729,6 +1738,42 @@ impl BeaconNodeHttpClient { .append_pair("skip_randao_verification", ""); } + Ok(path) + } + + /// `GET v3/validator/blocks/{slot}` + pub async fn get_validator_blocks_v3( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result, Error> { + self.get_validator_blocks_v3_modular( + slot, + randao_reveal, + graffiti, + SkipRandaoVerification::No, + ) + .await + } + + /// `GET v3/validator/blocks/{slot}` + pub async fn get_validator_blocks_v3_modular( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result, Error> { + let path = self + .get_validator_blocks_v3_path::( + slot, + randao_reveal, + graffiti, + skip_randao_verification, + ) + .await?; + let response = self.get_response(path, |b| b).await?; let is_blinded_payload = response @@ -1750,6 +1795,58 @@ impl BeaconNodeHttpClient { } } + /// `GET v3/validator/blocks/{slot}` in ssz format + pub async fn get_validator_blocks_v3_ssz( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result<(Option>, bool), Error> { + self.get_validator_blocks_v3_modular_ssz::( + slot, + randao_reveal, + graffiti, + SkipRandaoVerification::No, + ) + .await + } + + /// `GET v3/validator/blocks/{slot}` in ssz format + pub async fn get_validator_blocks_v3_modular_ssz( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result<(Option>, bool), Error> { + let path = self + .get_validator_blocks_v3_path::( + slot, + randao_reveal, + graffiti, + skip_randao_verification, + ) + .await?; + + let (response_content, response_headers) = self + .get_bytes_response_with_response_headers( + path, + Accept::Ssz, + self.timeouts.get_validator_block_ssz, + ) + .await?; + + let is_blinded_payload = match response_headers { + Some(headers) => headers + .get(EXECUTION_PAYLOAD_BLINDED_HEADER) + .map(|value| value.to_str().unwrap_or_default().to_lowercase() == "true") + .unwrap_or(false), + None => false, + }; + + Ok((response_content, is_blinded_payload)) + } + /// `GET v2/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_ssz>( &self,