From 228180bb35f5fb52e90cc8449bbcf6bc9d45f2c2 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 20:46:13 -0800 Subject: [PATCH 1/3] add some more block v3 tests --- beacon_node/http_api/tests/tests.rs | 1025 +++++++++++++++++++++++++-- 1 file changed, 983 insertions(+), 42 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index bd238a77990..dd7ea1387ed 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -38,6 +38,8 @@ use types::{ MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, }; +use eth2::types::ForkVersionedBeaconBlockType::{Blinded, Full}; + type E = MainnetEthSpec; const SECONDS_PER_SLOT: u64 = 12; @@ -3452,6 +3454,46 @@ impl ApiTester { (proposer_index, randao_reveal) } + pub async fn test_payload_v3_respects_registration(self) -> Self { + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + assert_eq!(payload.gas_limit(), 11_111_111); + + // If this cache is empty, it indicates fallback was not used, so the payload came from the + // mock builder. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + self + } + pub async fn test_payload_respects_registration(self) -> Self { let slot = self.chain.slot().unwrap(); let epoch = self.chain.epoch().unwrap(); @@ -3526,6 +3568,50 @@ impl ApiTester { self } + pub async fn test_payload_v3_accepts_mutated_gas_limit(self) -> Self { + // Mutate gas limit. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::GasLimit(30_000_000)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + assert_eq!(payload.gas_limit(), 30_000_000); + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_payload_accepts_changed_fee_recipient(self) -> Self { let test_fee_recipient = "0x4242424242424242424242424242424242424242" .parse::
() @@ -3567,6 +3653,52 @@ impl ApiTester { self } + pub async fn test_payload_v3_accepts_changed_fee_recipient(self) -> Self { + let test_fee_recipient = "0x4242424242424242424242424242424242424242" + .parse::
() + .unwrap(); + + // Mutate fee recipient. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::FeeRecipient(test_fee_recipient)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + assert_eq!(payload.fee_recipient(), test_fee_recipient); + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_payload_rejects_invalid_parent_hash(self) -> Self { let invalid_parent_hash = "0x4242424242424242424242424242424242424242424242424242424242424242" @@ -3616,6 +3748,60 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_parent_hash(self) -> Self { + let invalid_parent_hash = + "0x4242424242424242424242424242424242424242424242424242424242424242" + .parse::() + .unwrap(); + + // Mutate parent hash. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::ParentHash(invalid_parent_hash)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let expected_parent_hash = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .block_hash(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a blinded payload"), + }; + + assert_eq!(payload.parent_hash(), expected_parent_hash); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_prev_randao(self) -> Self { let invalid_prev_randao = "0x4242424242424242424242424242424242424242424242424242424242424242" @@ -3663,6 +3849,58 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_prev_randao(self) -> Self { + let invalid_prev_randao = + "0x4242424242424242424242424242424242424242424242424242424242424242" + .parse::() + .unwrap(); + + // Mutate prev randao. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::PrevRandao(invalid_prev_randao)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let expected_prev_randao = self + .chain + .canonical_head + .cached_head() + .head_random() + .unwrap(); + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + assert_eq!(payload.prev_randao(), expected_prev_randao); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_block_number(self) -> Self { let invalid_block_number = 2; @@ -3710,40 +3948,46 @@ impl ApiTester { self } - pub async fn test_payload_rejects_invalid_timestamp(self) -> Self { - let invalid_timestamp = 2; + pub async fn test_payload_v3_rejects_invalid_block_number(self) -> Self { + let invalid_block_number = 2; - // Mutate timestamp. + // Mutate block number. self.mock_builder .as_ref() .unwrap() - .add_operation(Operation::Timestamp(invalid_timestamp)); + .add_operation(Operation::BlockNumber(invalid_block_number)); let slot = self.chain.slot().unwrap(); let epoch = self.chain.epoch().unwrap(); - let min_expected_timestamp = self + let expected_block_number = self .chain .head_snapshot() .beacon_state .latest_execution_payload_header() .unwrap() - .timestamp(); + .block_number() + + 1; let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; - let payload: BlindedPayload = self + let payload_type = self .client - .get_validator_blinded_blocks::>(slot, &randao_reveal, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None) .await - .unwrap() - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(); + .unwrap(); - assert!(payload.timestamp() > min_expected_timestamp); + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + assert_eq!(payload.block_number(), expected_block_number); // If this cache is populated, it indicates fallback to the local EE was correctly used. assert!(self @@ -3756,11 +4000,24 @@ impl ApiTester { self } - pub async fn test_payload_rejects_invalid_signature(self) -> Self { - self.mock_builder.as_ref().unwrap().invalid_signatures(); + pub async fn test_payload_rejects_invalid_timestamp(self) -> Self { + let invalid_timestamp = 2; + + // Mutate timestamp. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Timestamp(invalid_timestamp)); let slot = self.chain.slot().unwrap(); let epoch = self.chain.epoch().unwrap(); + let min_expected_timestamp = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .timestamp(); let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; @@ -3776,6 +4033,8 @@ impl ApiTester { .unwrap() .into(); + assert!(payload.timestamp() > min_expected_timestamp); + // If this cache is populated, it indicates fallback to the local EE was correctly used. assert!(self .chain @@ -3787,16 +4046,134 @@ impl ApiTester { self } - pub async fn test_builder_chain_health_skips(self) -> Self { - let slot = self.chain.slot().unwrap(); + pub async fn test_payload_v3_rejects_invalid_timestamp(self) -> Self { + let invalid_timestamp = 2; - // Since we are proposing this slot, start the count from the previous slot. - let prev_slot = slot - Slot::new(1); - let head_slot = self.chain.canonical_head.cached_head().head_slot(); + // Mutate timestamp. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Timestamp(invalid_timestamp)); + + let slot = self.chain.slot().unwrap(); let epoch = self.chain.epoch().unwrap(); + let min_expected_timestamp = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .timestamp(); - // Inclusive here to make sure we advance one slot past the threshold. - for _ in (prev_slot - head_slot).as_usize()..=self.chain.config.builder_fallback_skips { + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a blinded payload"), + }; + + assert!(payload.timestamp() > min_expected_timestamp); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + + pub async fn test_payload_rejects_invalid_signature(self) -> Self { + self.mock_builder.as_ref().unwrap().invalid_signatures(); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload: BlindedPayload = self + .client + .get_validator_blinded_blocks::>(slot, &randao_reveal, None) + .await + .unwrap() + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + + pub async fn test_payload_v3_rejects_invalid_signature(self) -> Self { + self.mock_builder.as_ref().unwrap().invalid_signatures(); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + + pub async fn test_builder_chain_health_skips(self) -> Self { + let slot = self.chain.slot().unwrap(); + + // Since we are proposing this slot, start the count from the previous slot. + let prev_slot = slot - Slot::new(1); + let head_slot = self.chain.canonical_head.cached_head().head_slot(); + let epoch = self.chain.epoch().unwrap(); + + // Inclusive here to make sure we advance one slot past the threshold. + for _ in (prev_slot - head_slot).as_usize()..=self.chain.config.builder_fallback_skips { self.harness.advance_slot(); } @@ -3825,6 +4202,49 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_skips(self) -> Self { + let slot = self.chain.slot().unwrap(); + + // Since we are proposing this slot, start the count from the previous slot. + let prev_slot = slot - Slot::new(1); + let head_slot = self.chain.canonical_head.cached_head().head_slot(); + let epoch = self.chain.epoch().unwrap(); + + // Inclusive here to make sure we advance one slot past the threshold. + for _ in (prev_slot - head_slot).as_usize()..=self.chain.config.builder_fallback_skips { + self.harness.advance_slot(); + } + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_chain_health_skips_per_epoch(self) -> Self { // Fill an epoch with `builder_fallback_skips_per_epoch` skip slots. for i in 0..E::slots_per_epoch() { @@ -3900,6 +4320,91 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_skips_per_epoch(self) -> Self { + // Fill an epoch with `builder_fallback_skips_per_epoch` skip slots. + for i in 0..E::slots_per_epoch() { + if i == 0 || i as usize > self.chain.config.builder_fallback_skips_per_epoch { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + } + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + // Without proposing, advance into the next slot, this should make us cross the threshold + // number of skips, causing us to use the fallback. + self.harness.advance_slot(); + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + + self + } + pub async fn test_builder_chain_health_epochs_since_finalization(self) -> Self { let skips = E::slots_per_epoch() * self.chain.config.builder_fallback_epochs_since_finalization as u64; @@ -3960,15 +4465,159 @@ impl ApiTester { self.harness.advance_slot(); } - let next_slot = self.chain.slot().unwrap(); + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload: BlindedPayload = self + .client + .get_validator_blinded_blocks::>(next_slot, &randao_reveal, None) + .await + .unwrap() + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(); + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + self + } + + pub async fn test_builder_v3_chain_health_epochs_since_finalization(self) -> Self { + let skips = E::slots_per_epoch() + * self.chain.config.builder_fallback_epochs_since_finalization as u64; + + for _ in 0..skips { + self.harness.advance_slot(); + } + + // Fill the next epoch with blocks, should be enough to justify, not finalize. + for _ in 0..E::slots_per_epoch() { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + + // Fill another epoch with blocks, should be enough to finalize. (Sneaky plus 1 because this + // scenario starts at an epoch boundary). + for _ in 0..E::slots_per_epoch() + 1 { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + self + } + + pub async fn test_builder_chain_health_optimistic_head(self) -> Self { + // Make sure the next payload verification will return optimistic before advancing the chain. + self.harness.mock_execution_layer.as_ref().map(|el| { + el.server.all_payloads_syncing(true); + el + }); + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); - let (_, randao_reveal) = self - .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) - .await; + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; let payload: BlindedPayload = self .client - .get_validator_blinded_blocks::>(next_slot, &randao_reveal, None) + .get_validator_blinded_blocks::>(slot, &randao_reveal, None) .await .unwrap() .data @@ -3978,19 +4627,22 @@ impl ApiTester { .unwrap() .into(); - // This cache should not be populated because fallback should not have been used. + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. assert!(self .chain .execution_layer .as_ref() .unwrap() .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); + .is_some()); self } - pub async fn test_builder_chain_health_optimistic_head(self) -> Self { + pub async fn test_builder_v3_chain_health_optimistic_head(self) -> Self { // Make sure the next payload verification will return optimistic before advancing the chain. self.harness.mock_execution_layer.as_ref().map(|el| { el.server.all_payloads_syncing(true); @@ -4010,17 +4662,22 @@ impl ApiTester { let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; - let payload: BlindedPayload = self + let payload_type = self .client - .get_validator_blinded_blocks::>(slot, &randao_reveal, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None) .await - .unwrap() - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(); + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); assert_eq!(payload.fee_recipient(), expected_fee_recipient); @@ -4074,6 +4731,48 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_inadequate_builder_threshold(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_BUILDER_THRESHOLD_WEI - 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_payload_chosen_when_more_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4111,6 +4810,48 @@ impl ApiTester { self } + pub async fn test_builder_payload_v3_chosen_when_more_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI + 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // The builder's payload should've been chosen, so this cache should not be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_local_payload_chosen_when_equally_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4148,6 +4889,48 @@ impl ApiTester { self } + pub async fn test_local_payload_v3_chosen_when_equally_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // The local payload should've been chosen, so this cache should be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_local_payload_chosen_when_more_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4185,6 +4968,48 @@ impl ApiTester { self } + pub async fn test_local_payload_v3_chosen_when_more_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI - 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // The local payload should've been chosen, so this cache should be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_works_post_capella(self) -> Self { // Ensure builder payload is chosen self.mock_builder @@ -5342,6 +6167,14 @@ async fn post_validator_register_valid() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_valid_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_respects_registration() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn post_validator_register_gas_limit_mutation() { ApiTester::new_mev_tester() @@ -5350,6 +6183,14 @@ async fn post_validator_register_gas_limit_mutation() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_gas_limit_mutation_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_accepts_mutated_gas_limit() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn post_validator_register_fee_recipient_mutation() { ApiTester::new_mev_tester() @@ -5358,6 +6199,14 @@ async fn post_validator_register_fee_recipient_mutation() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_fee_recipient_mutation_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_accepts_changed_fee_recipient() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_parent_hash() { ApiTester::new_mev_tester() @@ -5366,6 +6215,14 @@ async fn get_blinded_block_invalid_parent_hash() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_parent_hash_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_parent_hash() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_prev_randao() { ApiTester::new_mev_tester() @@ -5374,6 +6231,14 @@ async fn get_blinded_block_invalid_prev_randao() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_prev_randao_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_prev_randao() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_block_number() { ApiTester::new_mev_tester() @@ -5382,6 +6247,14 @@ async fn get_blinded_block_invalid_block_number() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_block_number_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_block_number() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_timestamp() { ApiTester::new_mev_tester() @@ -5390,6 +6263,14 @@ async fn get_blinded_block_invalid_timestamp() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_timestamp_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_timestamp() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_signature() { ApiTester::new_mev_tester() @@ -5398,6 +6279,14 @@ async fn get_blinded_block_invalid_signature() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_signature_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_signature() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_skips() { ApiTester::new_mev_tester() @@ -5406,6 +6295,14 @@ async fn builder_chain_health_skips() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_skips_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_skips() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_skips_per_epoch() { ApiTester::new_mev_tester() @@ -5414,6 +6311,14 @@ async fn builder_chain_health_skips_per_epoch() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_skips_per_epoch_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_skips_per_epoch() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_epochs_since_finalization() { ApiTester::new_mev_tester() @@ -5422,6 +6327,14 @@ async fn builder_chain_health_epochs_since_finalization() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_epochs_since_finalization_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_epochs_since_finalization() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_optimistic_head() { ApiTester::new_mev_tester() @@ -5430,6 +6343,14 @@ async fn builder_chain_health_optimistic_head() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_optimistic_head_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_optimistic_head() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_inadequate_builder_threshold() { ApiTester::new_mev_tester() @@ -5438,6 +6359,14 @@ async fn builder_inadequate_builder_threshold() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_inadequate_builder_threshold_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_inadequate_builder_threshold() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_payload_chosen_by_profit() { ApiTester::new_mev_tester_no_builder_threshold() @@ -5450,6 +6379,18 @@ async fn builder_payload_chosen_by_profit() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_payload_chosen_by_profit_v3() { + ApiTester::new_mev_tester_no_builder_threshold() + .await + .test_builder_payload_v3_chosen_when_more_profitable() + .await + .test_local_payload_v3_chosen_when_equally_profitable() + .await + .test_local_payload_v3_chosen_when_more_profitable() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_works_post_capella() { let mut config = ApiTesterConfig { From 24a0a7ffd092d249069ddefbe07572a140f0743d Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 21:37:04 -0800 Subject: [PATCH 2/3] remove cache check --- beacon_node/http_api/tests/tests.rs | 309 ++++++---------------------- 1 file changed, 64 insertions(+), 245 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index dd7ea1387ed..f8b13406d32 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3481,16 +3481,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), expected_fee_recipient); assert_eq!(payload.gas_limit(), 11_111_111); - // If this cache is empty, it indicates fallback was not used, so the payload came from the - // mock builder. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - self } @@ -3601,14 +3591,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), expected_fee_recipient); assert_eq!(payload.gas_limit(), 30_000_000); - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -3688,14 +3670,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), test_fee_recipient); - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -3791,14 +3765,6 @@ impl ApiTester { assert_eq!(payload.parent_hash(), expected_parent_hash); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -3890,14 +3856,6 @@ impl ApiTester { assert_eq!(payload.prev_randao(), expected_prev_randao); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -3989,14 +3947,6 @@ impl ApiTester { assert_eq!(payload.block_number(), expected_block_number); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4086,14 +4036,6 @@ impl ApiTester { assert!(payload.timestamp() > min_expected_timestamp); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4142,25 +4084,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4223,25 +4151,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4347,26 +4261,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - // Without proposing, advance into the next slot, this should make us cross the threshold // number of skips, causing us to use the fallback. self.harness.advance_slot(); @@ -4382,26 +4281,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - self } @@ -4527,26 +4411,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - // Fill another epoch with blocks, should be enough to finalize. (Sneaky plus 1 because this // scenario starts at an epoch boundary). for _ in 0..E::slots_per_epoch() + 1 { @@ -4572,26 +4441,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - self } @@ -4682,15 +4536,6 @@ impl ApiTester { let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); assert_eq!(payload.fee_recipient(), expected_fee_recipient); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - self } @@ -4751,25 +4596,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4830,25 +4661,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // The builder's payload should've been chosen, so this cache should not be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -4909,25 +4726,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // The local payload should've been chosen, so this cache should be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4988,25 +4791,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // The local payload should've been chosen, so this cache should be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -5059,26 +4848,22 @@ impl ApiTester { let epoch = self.chain.epoch().unwrap(); let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; - let block_contents = self + let payload_type = self .client - .get_validator_blinded_blocks::>(slot, &randao_reveal, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None) .await - .unwrap() - .data; - let (block, maybe_sidecars) = block_contents.deconstruct(); + .unwrap(); + + let block_contents = match payload_type { + Blinded(payload) => payload.data, + Full(_) => panic!("Expecting a blinded payload"), + }; + + let (_, maybe_sidecars) = block_contents.deconstruct(); // Response should contain blob sidecars assert!(maybe_sidecars.is_some()); - // The builder's payload should've been chosen, so this cache should not be populated - let payload: BlindedPayload = block.body().execution_payload().unwrap().into(); - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -5122,6 +4907,38 @@ impl ApiTester { .is_some()); self } + + pub async fn test_lighthouse_rejects_invalid_withdrawals_root_v3(self) -> Self { + // Ensure builder payload *would be* chosen + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI + 1, + ))); + // Set withdrawals root to something invalid + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::WithdrawalsRoot(Hash256::repeat_byte(0x42))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + match payload_type { + Full(_) => (), + Blinded(_) => panic!("Expecting a full payload"), + }; + + self + } #[cfg(target_os = "linux")] pub async fn test_get_lighthouse_health(self) -> Self { @@ -6429,6 +6246,8 @@ async fn builder_works_post_deneb() { .test_post_validator_register_validator() .await .test_builder_works_post_deneb() + .await + .test_lighthouse_rejects_invalid_withdrawals_root_v3() .await; } From 98f159cc1884377279416f17823813bb9507a59d Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 21:37:58 -0800 Subject: [PATCH 3/3] fmt --- beacon_node/http_api/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index f8b13406d32..c0aa4fe58b7 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -4907,7 +4907,7 @@ impl ApiTester { .is_some()); self } - + pub async fn test_lighthouse_rejects_invalid_withdrawals_root_v3(self) -> Self { // Ensure builder payload *would be* chosen self.mock_builder