diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index bcd878846..59e6554ae 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -25,6 +25,7 @@ use beacon_chain::{ BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped, }; pub use block_id::BlockId; +use eth2::types::ValidatorStatus; use eth2::types::{self as api_types, EndpointVersion, ValidatorId}; use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_version::version_with_platform; @@ -2481,19 +2482,47 @@ pub fn serve( "count" => register_val_data.len(), ); - let preparation_data = register_val_data - .iter() + let head_snapshot = chain.head_snapshot(); + let spec = &chain.spec; + + let (preparation_data, filtered_registration_data): ( + Vec, + Vec, + ) = register_val_data + .into_iter() .filter_map(|register_data| { chain .validator_index(®ister_data.message.pubkey) .ok() .flatten() - .map(|validator_index| ProposerPreparationData { - validator_index: validator_index as u64, - fee_recipient: register_data.message.fee_recipient, + .and_then(|validator_index| { + let validator = head_snapshot + .beacon_state + .get_validator(validator_index) + .ok()?; + let validator_status = ValidatorStatus::from_validator( + validator, + current_epoch, + spec.far_future_epoch, + ) + .superstatus(); + let is_active_or_pending = + matches!(validator_status, ValidatorStatus::Pending) + || matches!(validator_status, ValidatorStatus::Active); + + // Filter out validators who are not 'active' or 'pending'. + is_active_or_pending.then(|| { + ( + ProposerPreparationData { + validator_index: validator_index as u64, + fee_recipient: register_data.message.fee_recipient, + }, + register_data, + ) + }) }) }) - .collect::>(); + .unzip(); // Update the prepare beacon proposer cache based on this request. execution_layer @@ -2522,11 +2551,11 @@ pub fn serve( info!( log, "Forwarding register validator request to connected builder"; - "count" => register_val_data.len(), + "count" => filtered_registration_data.len(), ); builder - .post_builder_validators(®ister_val_data) + .post_builder_validators(&filtered_registration_data) .await .map(|resp| warp::reply::json(&resp)) .map_err(|e| { diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index bd25450a4..3144060f1 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2459,6 +2459,93 @@ impl ApiTester { self } + pub async fn test_post_validator_register_validator_slashed(self) -> Self { + // slash a validator + self.client + .post_beacon_pool_attester_slashings(&self.attester_slashing) + .await + .unwrap(); + + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + let mut registrations = vec![]; + let mut fee_recipients = vec![]; + + let genesis_epoch = self.chain.spec.genesis_slot.epoch(E::slots_per_epoch()); + let fork = Fork { + current_version: self.chain.spec.genesis_fork_version, + previous_version: self.chain.spec.genesis_fork_version, + epoch: genesis_epoch, + }; + + let expected_gas_limit = 11_111_111; + + for (val_index, keypair) in self.validator_keypairs().iter().enumerate() { + let pubkey = keypair.pk.compress(); + let fee_recipient = Address::from_low_u64_be(val_index as u64); + + let data = ValidatorRegistrationData { + fee_recipient, + gas_limit: expected_gas_limit, + timestamp: 0, + pubkey, + }; + + let domain = self.chain.spec.get_domain( + genesis_epoch, + Domain::ApplicationMask(ApplicationDomain::Builder), + &fork, + Hash256::zero(), + ); + let message = data.signing_root(domain); + let signature = keypair.sk.sign(message); + + let signed = SignedValidatorRegistrationData { + message: data, + signature, + }; + + fee_recipients.push(fee_recipient); + registrations.push(signed); + } + + self.client + .post_validator_register_validator(®istrations) + .await + .unwrap(); + + for (val_index, (_, fee_recipient)) in self + .chain + .head_snapshot() + .beacon_state + .validators() + .into_iter() + .zip(fee_recipients.into_iter()) + .enumerate() + { + let actual = self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_suggested_fee_recipient(val_index as u64) + .await; + if val_index == 0 || val_index == 1 { + assert_eq!(actual, Address::from_low_u64_be(val_index as u64)); + } else { + assert_eq!(actual, fee_recipient); + } + } + + self + } + // Helper function for tests that require a valid RANDAO signature. async fn get_test_randao(&self, slot: Slot, epoch: Epoch) -> (u64, SignatureBytes) { let fork = self.chain.canonical_head.cached_head().head_fork(); @@ -3964,6 +4051,14 @@ async fn post_validator_register_validator() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_validator_slashed() { + ApiTester::new() + .await + .test_post_validator_register_validator_slashed() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn post_validator_register_valid() { ApiTester::new_mev_tester()