From 8b39d217fcad7fda292102ecb0ab3c022f62d0a2 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 22 Mar 2023 16:29:58 +1100 Subject: [PATCH 01/16] Add validator exit endpoint to validator API --- common/eth2/src/lighthouse_vc/types.rs | 5 + .../src/http_api/exit_validator.rs | 96 +++++++++++++++++++ validator_client/src/http_api/mod.rs | 49 +++++++++- validator_client/src/http_api/tests.rs | 11 ++- validator_client/src/http_metrics/metrics.rs | 5 + validator_client/src/lib.rs | 3 + validator_client/src/signing_method.rs | 3 + .../src/signing_method/web3signer.rs | 1 - validator_client/src/validator_store.rs | 31 +++++- 9 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 validator_client/src/http_api/exit_validator.rs diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index 92439337f61..37f665421ab 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -144,3 +144,8 @@ pub struct UpdateGasLimitRequest { #[serde(with = "eth2_serde_utils::quoted_u64")] pub gas_limit: u64, } + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ValidatorExitRequest { + pub pubkey: PublicKeyBytes, +} diff --git a/validator_client/src/http_api/exit_validator.rs b/validator_client/src/http_api/exit_validator.rs new file mode 100644 index 00000000000..c7421d319d5 --- /dev/null +++ b/validator_client/src/http_api/exit_validator.rs @@ -0,0 +1,96 @@ +use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; +use crate::validator_store::ValidatorStore; +use bls::PublicKeyBytes; +use eth2::types::GenesisData; +use slog::{info, Logger}; +use slot_clock::{SlotClock, SystemTimeSlotClock}; +use std::sync::Arc; +use std::time::Duration; +use types::{ChainSpec, Epoch, EthSpec, VoluntaryExit}; + +pub async fn publish_voluntary_exit( + pubkey: PublicKeyBytes, + validator_store: Arc>, + beacon_nodes: Arc>, + spec: Arc, + log: Logger, +) -> Result<(), warp::Rejection> { + let genesis_data = get_genesis_data(&beacon_nodes).await?; + + // TODO: (jimmy) Verify that the beacon node and validator being exited are on the same network. + + let epoch = get_current_epoch::(genesis_data.genesis_time, spec).ok_or_else(|| { + warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) + })?; + + let validator_index = validator_store.validator_index(&pubkey).ok_or_else(|| { + warp_utils::reject::custom_server_error(format!( + "Unable to find validator with public key: {}", + pubkey.as_hex_string() + )) + })?; + + let voluntary_exit = VoluntaryExit { + epoch, + validator_index, + }; + + let signed_voluntary_exit = validator_store + .sign_voluntary_exit(pubkey, voluntary_exit) + .await + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "Failed to sign voluntary exit: {:?}", + e + )) + })?; + + info!(log, "Publishing voluntary exit"; "validator" => pubkey.as_hex_string()); + + beacon_nodes + .first_success(RequireSynced::Yes, OfflineOnFailure::No, |client| async { + client + .post_beacon_pool_voluntary_exits(&signed_voluntary_exit) + .await + }) + .await + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "Failed to publish voluntary exit: {}", + e + )) + })?; + + // TODO: (jimmy) Do we want to wait until the exit to be accepted into the beacon chain? + // i.e. `validator_status == ActiveExiting`, and return `(current_epoch, exit_epoch, withdrawal_epoch)` to the user? + + Ok(()) +} + +/// Get genesis data by querying the beacon node client. +async fn get_genesis_data( + beacon_nodes: &Arc>, +) -> Result { + let genesis_data = beacon_nodes + .first_success( + RequireSynced::No, + OfflineOnFailure::Yes, + |client| async move { client.get_beacon_genesis().await }, + ) + .await + .map_err(|e| { + warp_utils::reject::custom_server_error(format!("Failed to get beacon genesis: {}", e)) + })? + .data; + Ok(genesis_data) +} + +/// Calculates the current epoch from the genesis time and current time. +fn get_current_epoch(genesis_time: u64, spec: Arc) -> Option { + let slot_clock = SystemTimeSlotClock::new( + spec.genesis_slot, + Duration::from_secs(genesis_time), + Duration::from_secs(spec.seconds_per_slot), + ); + slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) +} diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index b87bb083811..9f627aa106d 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -1,9 +1,12 @@ mod api_secret; mod create_validator; +mod exit_validator; mod keystores; mod remotekeys; mod tests; +use crate::beacon_node_fallback::BeaconNodeFallback; +use crate::http_api::exit_validator::publish_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, @@ -68,6 +71,7 @@ pub struct Context { pub validator_dir: Option, pub graffiti_file: Option, pub graffiti_flag: Option, + pub beacon_nodes: Arc>, pub spec: ChainSpec, pub config: Config, pub log: Logger, @@ -186,6 +190,9 @@ pub fn serve( let inner_graffiti_flag = ctx.graffiti_flag; let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag); + let inner_beacon_nodes = ctx.beacon_nodes.clone(); + let beacon_nodes_filter = warp::any().map(move || inner_beacon_nodes.clone()); + let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); @@ -424,7 +431,7 @@ pub fn serve( .and(warp::body::json()) .and(validator_dir_filter.clone()) .and(validator_store_filter.clone()) - .and(spec_filter) + .and(spec_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( @@ -596,6 +603,45 @@ pub fn serve( }, ); + // POST lighthouse/validators/exit + let post_validators_exit = warp::path("lighthouse") + .and(warp::path("validators")) + .and(warp::path("exit")) + .and(warp::path::end()) + .and(warp::body::json()) + .and(validator_store_filter.clone()) + .and(beacon_nodes_filter) + .and(spec_filter) + .and(log_filter.clone()) + .and(signer.clone()) + .and(task_executor_filter.clone()) + .and_then( + |body: api_types::ValidatorExitRequest, + validator_store: Arc>, + beacon_nodes: Arc>, + spec: Arc, + log, + signer, + task_executor: TaskExecutor| { + blocking_signed_json_task(signer, move || { + if let Some(handle) = task_executor.handle() { + handle.block_on(publish_voluntary_exit( + body.pubkey, + validator_store, + beacon_nodes, + spec, + log, + ))?; + Ok(()) + } else { + Err(warp_utils::reject::custom_server_error( + "Lighthouse shutting down".into(), + )) + } + }) + }, + ); + // PATCH lighthouse/validators/{validator_pubkey} let patch_validators = warp::path("lighthouse") .and(warp::path("validators")) @@ -1001,6 +1047,7 @@ pub fn serve( .or(post_validators_keystore) .or(post_validators_mnemonic) .or(post_validators_web3signer) + .or(post_validators_exit) .or(post_fee_recipient) .or(post_gas_limit) .or(post_std_keystores) diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index d453d7038ad..2e8330adfd8 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -3,6 +3,7 @@ mod keystores; +use crate::beacon_node_fallback::BeaconNodeFallback; use crate::doppelganger_service::DoppelgangerService; use crate::{ http_api::{ApiSecret, Config as HttpConfig, Context}, @@ -101,7 +102,7 @@ impl ApiTester { initialized_validators, slashing_protection, Hash256::repeat_byte(42), - spec, + spec.clone(), Some(Arc::new(DoppelgangerService::new(log.clone()))), slot_clock, &config, @@ -129,7 +130,13 @@ impl ApiTester { listen_port: 0, allow_origin: None, }, - log, + log: log.clone(), + beacon_nodes: Arc::new(BeaconNodeFallback::new( + vec![], // FIXME: (jimmy) add a beacon node + false, + spec.clone(), + log.clone(), + )), _phantom: PhantomData, }); let ctx = context.clone(); diff --git a/validator_client/src/http_metrics/metrics.rs b/validator_client/src/http_metrics/metrics.rs index b4e400c3e72..8a52a4d35e9 100644 --- a/validator_client/src/http_metrics/metrics.rs +++ b/validator_client/src/http_metrics/metrics.rs @@ -88,6 +88,11 @@ lazy_static::lazy_static! { "Total count of attempted SyncSelectionProof signings", &["status"] ); + pub static ref SIGNED_VOLUNTARY_EXITS_TOTAL: Result = try_create_int_counter_vec( + "vc_signed_voluntary_exits_total", + "Total count of VoluntaryExit signings", + &["status"] + ); pub static ref SIGNED_VALIDATOR_REGISTRATIONS_TOTAL: Result = try_create_int_counter_vec( "builder_validator_registrations_total", "Total count of ValidatorRegistrationData signings", diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 82cacccc605..25eecaf4275 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,6 +94,7 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, + beacon_nodes: Arc>, http_api_listen_addr: Option, config: Config, } @@ -482,6 +483,7 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, + beacon_nodes: beacon_nodes.clone(), http_api_listen_addr: None, }) } @@ -544,6 +546,7 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), + beacon_nodes: self.beacon_nodes.clone(), log: log.clone(), _phantom: PhantomData, }); diff --git a/validator_client/src/signing_method.rs b/validator_client/src/signing_method.rs index ae9df080965..0de2f2f54fa 100644 --- a/validator_client/src/signing_method.rs +++ b/validator_client/src/signing_method.rs @@ -47,6 +47,7 @@ pub enum SignableMessage<'a, T: EthSpec, Payload: AbstractExecPayload = FullP }, SignedContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), + VoluntaryExit(&'a VoluntaryExit), } impl<'a, T: EthSpec, Payload: AbstractExecPayload> SignableMessage<'a, T, Payload> { @@ -67,6 +68,7 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload> SignableMessage<'a, T, Pay } => beacon_block_root.signing_root(domain), SignableMessage::SignedContributionAndProof(c) => c.signing_root(domain), SignableMessage::ValidatorRegistration(v) => v.signing_root(domain), + SignableMessage::VoluntaryExit(exit) => exit.signing_root(domain), } } } @@ -203,6 +205,7 @@ impl SigningMethod { SignableMessage::ValidatorRegistration(v) => { Web3SignerObject::ValidatorRegistration(v) } + SignableMessage::VoluntaryExit(e) => Web3SignerObject::VoluntaryExit(e), }; // Determine the Web3Signer message type. diff --git a/validator_client/src/signing_method/web3signer.rs b/validator_client/src/signing_method/web3signer.rs index 17e780304e1..e907126faf4 100644 --- a/validator_client/src/signing_method/web3signer.rs +++ b/validator_client/src/signing_method/web3signer.rs @@ -62,7 +62,6 @@ pub enum Web3SignerObject<'a, T: EthSpec, Payload: AbstractExecPayload> { RandaoReveal { epoch: Epoch, }, - #[allow(dead_code)] VoluntaryExit(&'a VoluntaryExit), SyncCommitteeMessage { beacon_block_root: Hash256, diff --git a/validator_client/src/validator_store.rs b/validator_client/src/validator_store.rs index 36a0d057342..d5654050a84 100644 --- a/validator_client/src/validator_store.rs +++ b/validator_client/src/validator_store.rs @@ -22,8 +22,9 @@ use types::{ AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, Graffiti, Hash256, Keypair, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedRoot, - SignedValidatorRegistrationData, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, - SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, + SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + ValidatorRegistrationData, VoluntaryExit, }; use validator_dir::ValidatorDir; @@ -616,6 +617,32 @@ impl ValidatorStore { } } + pub async fn sign_voluntary_exit( + &self, + validator_pubkey: PublicKeyBytes, + voluntary_exit: VoluntaryExit, + ) -> Result { + let signing_epoch = voluntary_exit.epoch; + let signing_context = self.signing_context(Domain::VoluntaryExit, signing_epoch); + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + + let signature = signing_method + .get_signature::>( + SignableMessage::VoluntaryExit(&voluntary_exit), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + metrics::inc_counter_vec(&metrics::SIGNED_VOLUNTARY_EXITS_TOTAL, &[metrics::SUCCESS]); + + Ok(SignedVoluntaryExit { + message: voluntary_exit, + signature, + }) + } + pub async fn sign_validator_registration_data( &self, validator_registration_data: ValidatorRegistrationData, From 9ad2519c231e5d82abac0771fbbe06ee976848ba Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 22 Mar 2023 22:39:28 +1100 Subject: [PATCH 02/16] Update exit validator endpoint to return SignedVoluntaryExit instead of submitting to BN --- .../src/http_api/exit_validator.rs | 57 +++---------------- validator_client/src/http_api/mod.rs | 29 +++++----- validator_client/src/http_api/tests.rs | 16 +++--- validator_client/src/lib.rs | 6 +- 4 files changed, 32 insertions(+), 76 deletions(-) diff --git a/validator_client/src/http_api/exit_validator.rs b/validator_client/src/http_api/exit_validator.rs index c7421d319d5..e22edbbb25e 100644 --- a/validator_client/src/http_api/exit_validator.rs +++ b/validator_client/src/http_api/exit_validator.rs @@ -1,25 +1,19 @@ -use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; use crate::validator_store::ValidatorStore; use bls::PublicKeyBytes; -use eth2::types::GenesisData; use slog::{info, Logger}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; use std::time::Duration; -use types::{ChainSpec, Epoch, EthSpec, VoluntaryExit}; +use types::{ChainSpec, Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; -pub async fn publish_voluntary_exit( +pub async fn sign_voluntary_exit( pubkey: PublicKeyBytes, validator_store: Arc>, - beacon_nodes: Arc>, spec: Arc, + genesis_time: u64, log: Logger, -) -> Result<(), warp::Rejection> { - let genesis_data = get_genesis_data(&beacon_nodes).await?; - - // TODO: (jimmy) Verify that the beacon node and validator being exited are on the same network. - - let epoch = get_current_epoch::(genesis_data.genesis_time, spec).ok_or_else(|| { +) -> Result { + let epoch = get_current_epoch::(genesis_time, spec).ok_or_else(|| { warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) })?; @@ -35,6 +29,8 @@ pub async fn publish_voluntary_exit( validator_index, }; + info!(log, "Signing voluntary exit"; "validator" => pubkey.as_hex_string()); + let signed_voluntary_exit = validator_store .sign_voluntary_exit(pubkey, voluntary_exit) .await @@ -45,44 +41,7 @@ pub async fn publish_voluntary_exit( )) })?; - info!(log, "Publishing voluntary exit"; "validator" => pubkey.as_hex_string()); - - beacon_nodes - .first_success(RequireSynced::Yes, OfflineOnFailure::No, |client| async { - client - .post_beacon_pool_voluntary_exits(&signed_voluntary_exit) - .await - }) - .await - .map_err(|e| { - warp_utils::reject::custom_server_error(format!( - "Failed to publish voluntary exit: {}", - e - )) - })?; - - // TODO: (jimmy) Do we want to wait until the exit to be accepted into the beacon chain? - // i.e. `validator_status == ActiveExiting`, and return `(current_epoch, exit_epoch, withdrawal_epoch)` to the user? - - Ok(()) -} - -/// Get genesis data by querying the beacon node client. -async fn get_genesis_data( - beacon_nodes: &Arc>, -) -> Result { - let genesis_data = beacon_nodes - .first_success( - RequireSynced::No, - OfflineOnFailure::Yes, - |client| async move { client.get_beacon_genesis().await }, - ) - .await - .map_err(|e| { - warp_utils::reject::custom_server_error(format!("Failed to get beacon genesis: {}", e)) - })? - .data; - Ok(genesis_data) + Ok(signed_voluntary_exit) } /// Calculates the current epoch from the genesis time and current time. diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 9f627aa106d..4385e21b9b2 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -5,8 +5,7 @@ mod keystores; mod remotekeys; mod tests; -use crate::beacon_node_fallback::BeaconNodeFallback; -use crate::http_api::exit_validator::publish_voluntary_exit; +use crate::http_api::exit_validator::sign_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, @@ -71,10 +70,10 @@ pub struct Context { pub validator_dir: Option, pub graffiti_file: Option, pub graffiti_flag: Option, - pub beacon_nodes: Arc>, pub spec: ChainSpec, pub config: Config, pub log: Logger, + pub genesis_time: u64, pub _phantom: PhantomData, } @@ -190,12 +189,12 @@ pub fn serve( let inner_graffiti_flag = ctx.graffiti_flag; let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag); - let inner_beacon_nodes = ctx.beacon_nodes.clone(); - let beacon_nodes_filter = warp::any().map(move || inner_beacon_nodes.clone()); - let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); + let inner_genesis_time = ctx.genesis_time; + let genesis_time_filter = warp::any().map(move || inner_genesis_time); + let inner_spec = Arc::new(ctx.spec.clone()); let spec_filter = warp::any().map(move || inner_spec.clone()); @@ -603,36 +602,36 @@ pub fn serve( }, ); - // POST lighthouse/validators/exit - let post_validators_exit = warp::path("lighthouse") + // POST lighthouse/validators/voluntary_exits + let post_validators_voluntary_exits = warp::path("lighthouse") .and(warp::path("validators")) - .and(warp::path("exit")) + .and(warp::path("voluntary_exits")) .and(warp::path::end()) .and(warp::body::json()) .and(validator_store_filter.clone()) - .and(beacon_nodes_filter) .and(spec_filter) + .and(genesis_time_filter) .and(log_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( |body: api_types::ValidatorExitRequest, validator_store: Arc>, - beacon_nodes: Arc>, spec: Arc, + genesis_time: u64, log, signer, task_executor: TaskExecutor| { blocking_signed_json_task(signer, move || { if let Some(handle) = task_executor.handle() { - handle.block_on(publish_voluntary_exit( + let signed_voluntary_exit = handle.block_on(sign_voluntary_exit( body.pubkey, validator_store, - beacon_nodes, spec, + genesis_time, log, ))?; - Ok(()) + Ok(signed_voluntary_exit) } else { Err(warp_utils::reject::custom_server_error( "Lighthouse shutting down".into(), @@ -1047,7 +1046,7 @@ pub fn serve( .or(post_validators_keystore) .or(post_validators_mnemonic) .or(post_validators_web3signer) - .or(post_validators_exit) + .or(post_validators_voluntary_exits) .or(post_fee_recipient) .or(post_gas_limit) .or(post_std_keystores) diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index 2e8330adfd8..7b2f66df507 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -3,7 +3,6 @@ mod keystores; -use crate::beacon_node_fallback::BeaconNodeFallback; use crate::doppelganger_service::DoppelgangerService; use crate::{ http_api::{ApiSecret, Config as HttpConfig, Context}, @@ -91,8 +90,12 @@ impl ApiTester { let slashing_db_path = config.validator_dir.join(SLASHING_PROTECTION_FILENAME); let slashing_protection = SlashingDatabase::open_or_create(&slashing_db_path).unwrap(); - let slot_clock = - TestingSlotClock::new(Slot::new(0), Duration::from_secs(0), Duration::from_secs(1)); + let genesis_time: u64 = 0; + let slot_clock = TestingSlotClock::new( + Slot::new(0), + Duration::from_secs(genesis_time), + Duration::from_secs(1), + ); let (runtime_shutdown, exit) = exit_future::signal(); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); @@ -131,12 +134,7 @@ impl ApiTester { allow_origin: None, }, log: log.clone(), - beacon_nodes: Arc::new(BeaconNodeFallback::new( - vec![], // FIXME: (jimmy) add a beacon node - false, - spec.clone(), - log.clone(), - )), + genesis_time, _phantom: PhantomData, }); let ctx = context.clone(); diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 25eecaf4275..47ccb6459b7 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,7 +94,7 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, - beacon_nodes: Arc>, + genesis_time: u64, http_api_listen_addr: Option, config: Config, } @@ -483,7 +483,7 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, - beacon_nodes: beacon_nodes.clone(), + genesis_time, http_api_listen_addr: None, }) } @@ -546,7 +546,7 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), - beacon_nodes: self.beacon_nodes.clone(), + genesis_time: self.genesis_time, log: log.clone(), _phantom: PhantomData, }); From 53529a283eada4a8d0538f534b61f64a1e79ec5f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 22 Mar 2023 23:09:09 +1100 Subject: [PATCH 03/16] Add http test for validator voluntary exit api --- common/eth2/src/lighthouse_vc/http_client.rs | 16 +++++++++ common/eth2/src/lighthouse_vc/types.rs | 2 +- validator_client/src/http_api/mod.rs | 2 +- validator_client/src/http_api/tests.rs | 37 ++++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/common/eth2/src/lighthouse_vc/http_client.rs b/common/eth2/src/lighthouse_vc/http_client.rs index 88b5b684019..d98596dcff2 100644 --- a/common/eth2/src/lighthouse_vc/http_client.rs +++ b/common/eth2/src/lighthouse_vc/http_client.rs @@ -460,6 +460,22 @@ impl ValidatorClientHttpClient { self.post(path, &request).await } + /// `POST lighthouse/validators/voluntary_exits` + pub async fn post_lighthouse_validators_voluntary_exits( + &self, + request: &VoluntaryExitRequest, + ) -> Result { + let mut path = self.server.full.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("lighthouse") + .push("validators") + .push("voluntary_exits"); + + self.post(path, &request).await + } + /// `PATCH lighthouse/validators/{validator_pubkey}` pub async fn patch_lighthouse_validators( &self, diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index 37f665421ab..33e63c4413a 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -146,6 +146,6 @@ pub struct UpdateGasLimitRequest { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct ValidatorExitRequest { +pub struct VoluntaryExitRequest { pub pubkey: PublicKeyBytes, } diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 4385e21b9b2..bff063f18c7 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -615,7 +615,7 @@ pub fn serve( .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( - |body: api_types::ValidatorExitRequest, + |body: api_types::VoluntaryExitRequest, validator_store: Arc>, spec: Arc, genesis_time: u64, diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index 7b2f66df507..e21bd16decc 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -499,6 +499,22 @@ impl ApiTester { self } + pub async fn test_sign_voluntary_exits(self, index: usize) -> Self { + let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index]; + let request = VoluntaryExitRequest { + pubkey: validator.voting_pubkey, + }; + + let resp = self + .client + .post_lighthouse_validators_voluntary_exits(&request) + .await; + + assert!(resp.is_ok()); + + self + } + pub async fn set_validator_enabled(self, index: usize, enabled: bool) -> Self { let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index]; @@ -783,6 +799,27 @@ fn hd_validator_creation() { }); } +#[test] +fn validator_exit() { + let runtime = build_runtime(); + let weak_runtime = Arc::downgrade(&runtime); + runtime.block_on(async { + ApiTester::new(weak_runtime) + .await + .create_hd_validators(HdValidatorScenario { + count: 2, + specify_mnemonic: false, + key_derivation_path_offset: 0, + disabled: vec![], + }) + .await + .assert_enabled_validators_count(2) + .assert_validators_count(2) + .test_sign_voluntary_exits(0) + .await; + }); +} + #[test] fn validator_enabling() { let runtime = build_runtime(); From 3a6fe18f39d134a72bc1be83e2fc88959d6b108f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 22 Mar 2023 23:40:34 +1100 Subject: [PATCH 04/16] Rename file and fix test --- validator_client/src/http_api/mod.rs | 4 ++-- .../http_api/{exit_validator.rs => sign_voluntary_exit.rs} | 0 validator_client/src/http_api/tests.rs | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) rename validator_client/src/http_api/{exit_validator.rs => sign_voluntary_exit.rs} (100%) diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index bff063f18c7..c3f5b6141a3 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -1,11 +1,11 @@ mod api_secret; mod create_validator; -mod exit_validator; mod keystores; mod remotekeys; +mod sign_voluntary_exit; mod tests; -use crate::http_api::exit_validator::sign_voluntary_exit; +use crate::http_api::sign_voluntary_exit::sign_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, diff --git a/validator_client/src/http_api/exit_validator.rs b/validator_client/src/http_api/sign_voluntary_exit.rs similarity index 100% rename from validator_client/src/http_api/exit_validator.rs rename to validator_client/src/http_api/sign_voluntary_exit.rs diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index e21bd16decc..8f001808593 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -501,6 +501,11 @@ impl ApiTester { pub async fn test_sign_voluntary_exits(self, index: usize) -> Self { let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index]; + // manually setting validator index in `ValidatorStore` + self.initialized_validators + .write() + .set_index(&validator.voting_pubkey, 0); + let request = VoluntaryExitRequest { pubkey: validator.voting_pubkey, }; From f053edd74cb4f45607b78e31f0c3271930c96922 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Thu, 23 Mar 2023 15:55:01 +1100 Subject: [PATCH 05/16] Add VC voluntary exit endpoint to Lighthouse book. --- book/src/api-vc-endpoints.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/book/src/api-vc-endpoints.md b/book/src/api-vc-endpoints.md index 80a14ae7710..6e3c45e0a00 100644 --- a/book/src/api-vc-endpoints.md +++ b/book/src/api-vc-endpoints.md @@ -16,6 +16,7 @@ HTTP Path | Description | [`POST /lighthouse/validators/keystore`](#post-lighthousevalidatorskeystore) | Import a keystore. [`POST /lighthouse/validators/mnemonic`](#post-lighthousevalidatorsmnemonic) | Create a new validator from an existing mnemonic. [`POST /lighthouse/validators/web3signer`](#post-lighthousevalidatorsweb3signer) | Add web3signer validators. +[`POST /lighthouse/validators/voluntary_exits](#post-lighthousevalidatorsvoluntaryexits) | Create a signed voluntary exit message. In addition to the above endpoints Lighthouse also supports all of the [standard keymanager APIs](https://ethereum.github.io/keymanager-APIs/). @@ -578,3 +579,36 @@ The following fields may be omitted or nullified to obtain default values: ### Example Response Body *No data is included in the response body.* + +## `POST /lighthouse/validators/voluntary_exits` + +Generate and sign a voluntary exit message for the given validator. + +### HTTP Specification + +| Property | Specification | +|-------------------|--------------------------------------------| +| Path | `/lighthouse/validators/voluntary_exits` | +| Method | POST | +| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Typical Responses | 200, 400 | + +### Example Request Body + +```json +{ + "pubkey": "0x81054bd51ce57a8415f0c8e0f2fbf94f5a8464552baa33263c20a4da062e5ed994a4d32c171106d2008cd063f48f6fe2" +} +``` + +### Example Response Body + +```json +{ + "message": { + "epoch": "3", + "validator_index": "35" + }, + "signature": "0xa3217699ecbecde7452ad4b43fbc60ba751770a00af4ba1af3f779d18cd2f535e29060ea10effd5f87ec9d7e27c65dcb13f4e6e2cde97f4487544d2206f795f4ded25916b2f170e084ae98f89c0bbff62d4c1a1c31015098f325620a50564d2f" +} +``` \ No newline at end of file From b204dea3ad29544ff60538007966ddfb909430a3 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 24 Mar 2023 15:58:11 +1100 Subject: [PATCH 06/16] Revert back to submitting exit to Beacon API --- validator_client/src/http_api/mod.rs | 20 ++++++++----- ...ntary_exit.rs => submit_voluntary_exit.rs} | 28 +++++++++++++++---- validator_client/src/lib.rs | 3 ++ 3 files changed, 38 insertions(+), 13 deletions(-) rename validator_client/src/http_api/{sign_voluntary_exit.rs => submit_voluntary_exit.rs} (65%) diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index c3f5b6141a3..668f493689a 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -2,10 +2,11 @@ mod api_secret; mod create_validator; mod keystores; mod remotekeys; -mod sign_voluntary_exit; +mod submit_voluntary_exit; mod tests; -use crate::http_api::sign_voluntary_exit::sign_voluntary_exit; +use crate::beacon_node_fallback::BeaconNodeFallback; +use crate::http_api::submit_voluntary_exit::submit_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, @@ -74,6 +75,7 @@ pub struct Context { pub config: Config, pub log: Logger, pub genesis_time: u64, + pub beacon_nodes: Arc>, pub _phantom: PhantomData, } @@ -192,6 +194,9 @@ pub fn serve( let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); + let inner_beacon_nodes = ctx.beacon_nodes.clone(); + let beacon_nodes_filter = warp::any().map(move || inner_beacon_nodes.clone()); + let inner_genesis_time = ctx.genesis_time; let genesis_time_filter = warp::any().map(move || inner_genesis_time); @@ -607,31 +612,32 @@ pub fn serve( .and(warp::path("validators")) .and(warp::path("voluntary_exits")) .and(warp::path::end()) - .and(warp::body::json()) .and(validator_store_filter.clone()) .and(spec_filter) + .and(beacon_nodes_filter) .and(genesis_time_filter) .and(log_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( - |body: api_types::VoluntaryExitRequest, - validator_store: Arc>, + |validator_store: Arc>, spec: Arc, + beacon_nodes: Arc>, genesis_time: u64, log, signer, task_executor: TaskExecutor| { blocking_signed_json_task(signer, move || { if let Some(handle) = task_executor.handle() { - let signed_voluntary_exit = handle.block_on(sign_voluntary_exit( + handle.block_on(submit_voluntary_exit( body.pubkey, validator_store, spec, + beacon_nodes, genesis_time, log, ))?; - Ok(signed_voluntary_exit) + Ok(()) } else { Err(warp_utils::reject::custom_server_error( "Lighthouse shutting down".into(), diff --git a/validator_client/src/http_api/sign_voluntary_exit.rs b/validator_client/src/http_api/submit_voluntary_exit.rs similarity index 65% rename from validator_client/src/http_api/sign_voluntary_exit.rs rename to validator_client/src/http_api/submit_voluntary_exit.rs index e22edbbb25e..5a76d8ba23d 100644 --- a/validator_client/src/http_api/sign_voluntary_exit.rs +++ b/validator_client/src/http_api/submit_voluntary_exit.rs @@ -1,18 +1,20 @@ +use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; use crate::validator_store::ValidatorStore; use bls::PublicKeyBytes; use slog::{info, Logger}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; use std::time::Duration; -use types::{ChainSpec, Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; +use types::{ChainSpec, Epoch, EthSpec, VoluntaryExit}; -pub async fn sign_voluntary_exit( +pub async fn submit_voluntary_exit( pubkey: PublicKeyBytes, validator_store: Arc>, spec: Arc, + beacon_nodes: Arc>, genesis_time: u64, log: Logger, -) -> Result { +) -> Result<(), warp::Rejection> { let epoch = get_current_epoch::(genesis_time, spec).ok_or_else(|| { warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) })?; @@ -29,8 +31,6 @@ pub async fn sign_voluntary_exit( validator_index, }; - info!(log, "Signing voluntary exit"; "validator" => pubkey.as_hex_string()); - let signed_voluntary_exit = validator_store .sign_voluntary_exit(pubkey, voluntary_exit) .await @@ -41,7 +41,23 @@ pub async fn sign_voluntary_exit( )) })?; - Ok(signed_voluntary_exit) + info!(log, "Publishing voluntary exit"; "validator" => pubkey.as_hex_string()); + + beacon_nodes + .first_success(RequireSynced::Yes, OfflineOnFailure::No, |client| async { + client + .post_beacon_pool_voluntary_exits(&signed_voluntary_exit) + .await + }) + .await + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "Failed to publish voluntary exit: {}", + e + )) + })?; + + Ok(()) } /// Calculates the current epoch from the genesis time and current time. diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 47ccb6459b7..8a9e8f1616c 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,6 +94,7 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, + beacon_nodes: Arc>, genesis_time: u64, http_api_listen_addr: Option, config: Config, @@ -483,6 +484,7 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, + beacon_nodes: beacon_nodes.clone(), genesis_time, http_api_listen_addr: None, }) @@ -546,6 +548,7 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), + beacon_nodes: self.beacon_nodes.clone(), genesis_time: self.genesis_time, log: log.clone(), _phantom: PhantomData, From ef64b7fc787b3cfa6c8645656ecec80335d36d6b Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 24 Mar 2023 17:36:03 +1100 Subject: [PATCH 07/16] Update exit endpoint to latest proposed spec --- common/eth2/src/lighthouse_vc/http_client.rs | 45 ++++++---- common/eth2/src/lighthouse_vc/types.rs | 30 ++++++- validator_client/src/http_api/mod.rs | 89 ++++++++++--------- .../src/http_api/submit_voluntary_exit.rs | 33 ++++--- validator_client/src/http_api/tests.rs | 14 +-- validator_client/src/validator_store.rs | 2 +- 6 files changed, 135 insertions(+), 78 deletions(-) diff --git a/common/eth2/src/lighthouse_vc/http_client.rs b/common/eth2/src/lighthouse_vc/http_client.rs index d98596dcff2..eafb9304c7b 100644 --- a/common/eth2/src/lighthouse_vc/http_client.rs +++ b/common/eth2/src/lighthouse_vc/http_client.rs @@ -460,22 +460,6 @@ impl ValidatorClientHttpClient { self.post(path, &request).await } - /// `POST lighthouse/validators/voluntary_exits` - pub async fn post_lighthouse_validators_voluntary_exits( - &self, - request: &VoluntaryExitRequest, - ) -> Result { - let mut path = self.server.full.clone(); - - path.path_segments_mut() - .map_err(|()| Error::InvalidUrl(self.server.clone()))? - .push("lighthouse") - .push("validators") - .push("voluntary_exits"); - - self.post(path, &request).await - } - /// `PATCH lighthouse/validators/{validator_pubkey}` pub async fn patch_lighthouse_validators( &self, @@ -658,6 +642,35 @@ impl ValidatorClientHttpClient { let url = self.make_gas_limit_url(pubkey)?; self.delete_with_raw_response(url, &()).await } + + /// `POST /eth/v1/validator/{pubkey}/voluntary_exit` + pub async fn post_validator_voluntary_exit( + &self, + pubkey: &PublicKeyBytes, + confirm: bool, + epoch: Option, + ) -> Result { + let mut path = self.server.full.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("eth") + .push("v1") + .push("validator") + .push(&pubkey.to_string()) + .push("voluntary_exit"); + + if confirm { + path.query_pairs_mut().append_pair("confirm", "yes"); + } + + if let Some(epoch) = epoch { + path.query_pairs_mut() + .append_pair("epoch", &epoch.to_string()); + } + + self.post(path, &()).await + } } /// Returns `Ok(response)` if the response is a `200 OK` response or a diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index 33e63c4413a..e4d50ebf017 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -145,7 +145,31 @@ pub struct UpdateGasLimitRequest { pub gas_limit: u64, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct VoluntaryExitRequest { - pub pubkey: PublicKeyBytes, +#[derive(Deserialize)] +pub struct VoluntaryExitQuery { + pub confirm: ConfirmVoluntaryExit, + pub epoch: Option, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[serde(try_from = "Option")] +pub enum ConfirmVoluntaryExit { + Yes, + #[default] + No, +} + +/// Parse a `confirm` query parameter for voluntary exit. +impl TryFrom> for ConfirmVoluntaryExit { + type Error = String; + + fn try_from(opt: Option) -> Result { + match opt.as_deref() { + None => Ok(ConfirmVoluntaryExit::No), + Some("yes") => Ok(ConfirmVoluntaryExit::Yes), + Some(s) => Err(format!( + "confirm does not take any value other than 'yes', got: {s}" + )), + } + } } diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 668f493689a..b0c89ad07df 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -14,6 +14,7 @@ use account_utils::{ }; pub use api_secret::ApiSecret; use create_validator::{create_validators_mnemonic, create_validators_web3signer}; +use eth2::lighthouse_vc::types::ConfirmVoluntaryExit; use eth2::lighthouse_vc::{ std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse}, types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes}, @@ -607,46 +608,6 @@ pub fn serve( }, ); - // POST lighthouse/validators/voluntary_exits - let post_validators_voluntary_exits = warp::path("lighthouse") - .and(warp::path("validators")) - .and(warp::path("voluntary_exits")) - .and(warp::path::end()) - .and(validator_store_filter.clone()) - .and(spec_filter) - .and(beacon_nodes_filter) - .and(genesis_time_filter) - .and(log_filter.clone()) - .and(signer.clone()) - .and(task_executor_filter.clone()) - .and_then( - |validator_store: Arc>, - spec: Arc, - beacon_nodes: Arc>, - genesis_time: u64, - log, - signer, - task_executor: TaskExecutor| { - blocking_signed_json_task(signer, move || { - if let Some(handle) = task_executor.handle() { - handle.block_on(submit_voluntary_exit( - body.pubkey, - validator_store, - spec, - beacon_nodes, - genesis_time, - log, - ))?; - Ok(()) - } else { - Err(warp_utils::reject::custom_server_error( - "Lighthouse shutting down".into(), - )) - } - }) - }, - ); - // PATCH lighthouse/validators/{validator_pubkey} let patch_validators = warp::path("lighthouse") .and(warp::path("validators")) @@ -955,6 +916,54 @@ pub fn serve( ) .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NO_CONTENT)); + // POST /eth/v1/validator/{pubkey}/voluntary_exit + let post_validators_voluntary_exits = eth_v1 + .and(warp::path("validator")) + .and(warp::path::param::()) + .and(warp::path("voluntary_exit")) + .and(warp::query::()) + .and(warp::path::end()) + .and(validator_store_filter.clone()) + .and(spec_filter) + .and(beacon_nodes_filter) + .and(genesis_time_filter) + .and(log_filter.clone()) + .and(signer.clone()) + .and(task_executor_filter.clone()) + .and_then( + |pubkey: PublicKey, + query: api_types::VoluntaryExitQuery, + validator_store: Arc>, + spec: Arc, + beacon_nodes: Arc>, + genesis_time: u64, + log, + signer, + task_executor: TaskExecutor| { + blocking_signed_json_task(signer, move || { + if let ConfirmVoluntaryExit::No = query.confirm { + return Err(warp_utils::reject::custom_bad_request("please confirm you intend to exit this validator, it cannot be undone once the message is submitted!".to_string())); + } + if let Some(handle) = task_executor.handle() { + handle.block_on(submit_voluntary_exit( + pubkey, + query.epoch, + validator_store, + spec, + beacon_nodes, + genesis_time, + log, + ))?; + Ok(()) + } else { + Err(warp_utils::reject::custom_server_error( + "Lighthouse shutting down".into(), + )) + } + }) + }, + ); + // GET /eth/v1/keystores let get_std_keystores = std_keystores .and(signer.clone()) diff --git a/validator_client/src/http_api/submit_voluntary_exit.rs b/validator_client/src/http_api/submit_voluntary_exit.rs index 5a76d8ba23d..031fe195e30 100644 --- a/validator_client/src/http_api/submit_voluntary_exit.rs +++ b/validator_client/src/http_api/submit_voluntary_exit.rs @@ -1,6 +1,6 @@ use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; use crate::validator_store::ValidatorStore; -use bls::PublicKeyBytes; +use bls::{PublicKey, PublicKeyBytes}; use slog::{info, Logger}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; @@ -8,23 +8,30 @@ use std::time::Duration; use types::{ChainSpec, Epoch, EthSpec, VoluntaryExit}; pub async fn submit_voluntary_exit( - pubkey: PublicKeyBytes, + pubkey: PublicKey, + maybe_epoch: Option, validator_store: Arc>, spec: Arc, beacon_nodes: Arc>, genesis_time: u64, log: Logger, ) -> Result<(), warp::Rejection> { - let epoch = get_current_epoch::(genesis_time, spec).ok_or_else(|| { - warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) - })?; + let epoch = match maybe_epoch { + Some(epoch) => epoch, + None => get_current_epoch::(genesis_time, spec).ok_or_else(|| { + warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) + })?, + }; - let validator_index = validator_store.validator_index(&pubkey).ok_or_else(|| { - warp_utils::reject::custom_server_error(format!( - "Unable to find validator with public key: {}", - pubkey.as_hex_string() - )) - })?; + let pubkey_bytes = PublicKeyBytes::from(pubkey); + let validator_index = validator_store + .validator_index(&pubkey_bytes) + .ok_or_else(|| { + warp_utils::reject::custom_server_error(format!( + "Unable to find validator with public key: {}", + pubkey_bytes.as_hex_string() + )) + })?; let voluntary_exit = VoluntaryExit { epoch, @@ -32,7 +39,7 @@ pub async fn submit_voluntary_exit( }; let signed_voluntary_exit = validator_store - .sign_voluntary_exit(pubkey, voluntary_exit) + .sign_voluntary_exit(pubkey_bytes, voluntary_exit) .await .map_err(|e| { warp_utils::reject::custom_server_error(format!( @@ -41,7 +48,7 @@ pub async fn submit_voluntary_exit( )) })?; - info!(log, "Publishing voluntary exit"; "validator" => pubkey.as_hex_string()); + info!(log, "Publishing voluntary exit"; "validator" => pubkey_bytes.as_hex_string()); beacon_nodes .first_success(RequireSynced::Yes, OfflineOnFailure::No, |client| async { diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index 8f001808593..49c77a96efb 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -3,6 +3,7 @@ mod keystores; +use crate::beacon_node_fallback::BeaconNodeFallback; use crate::doppelganger_service::DoppelgangerService; use crate::{ http_api::{ApiSecret, Config as HttpConfig, Context}, @@ -134,6 +135,12 @@ impl ApiTester { allow_origin: None, }, log: log.clone(), + beacon_nodes: Arc::new(BeaconNodeFallback::new( + vec![], // FIXME: (jimmy) add a beacon node + false, + spec.clone(), + log.clone(), + )), genesis_time, _phantom: PhantomData, }); @@ -506,13 +513,10 @@ impl ApiTester { .write() .set_index(&validator.voting_pubkey, 0); - let request = VoluntaryExitRequest { - pubkey: validator.voting_pubkey, - }; - + // TODO(jimmy): add test cases for different params let resp = self .client - .post_lighthouse_validators_voluntary_exits(&request) + .post_validator_voluntary_exit(&validator.voting_pubkey, true, None) .await; assert!(resp.is_ok()); diff --git a/validator_client/src/validator_store.rs b/validator_client/src/validator_store.rs index d5654050a84..e467b78ce59 100644 --- a/validator_client/src/validator_store.rs +++ b/validator_client/src/validator_store.rs @@ -624,7 +624,7 @@ impl ValidatorStore { ) -> Result { let signing_epoch = voluntary_exit.epoch; let signing_context = self.signing_context(Domain::VoluntaryExit, signing_epoch); - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; let signature = signing_method .get_signature::>( From 63898d3180e4cd15c5ea5d79b33293569d4af74f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 28 Mar 2023 15:51:49 +1100 Subject: [PATCH 08/16] Update VC exit endpoint: remove confirm param, make epoch mandatory and return SignedVoluntaryExit object --- common/eth2/src/lighthouse_vc/http_client.rs | 13 ++--- common/eth2/src/lighthouse_vc/types.rs | 26 +--------- validator_client/src/http_api/mod.rs | 28 ++--------- .../src/http_api/submit_voluntary_exit.rs | 50 +++---------------- validator_client/src/http_api/tests.rs | 12 +---- validator_client/src/lib.rs | 6 --- 6 files changed, 16 insertions(+), 119 deletions(-) diff --git a/common/eth2/src/lighthouse_vc/http_client.rs b/common/eth2/src/lighthouse_vc/http_client.rs index eafb9304c7b..a8cc059641c 100644 --- a/common/eth2/src/lighthouse_vc/http_client.rs +++ b/common/eth2/src/lighthouse_vc/http_client.rs @@ -647,8 +647,7 @@ impl ValidatorClientHttpClient { pub async fn post_validator_voluntary_exit( &self, pubkey: &PublicKeyBytes, - confirm: bool, - epoch: Option, + epoch: Epoch, ) -> Result { let mut path = self.server.full.clone(); @@ -660,14 +659,8 @@ impl ValidatorClientHttpClient { .push(&pubkey.to_string()) .push("voluntary_exit"); - if confirm { - path.query_pairs_mut().append_pair("confirm", "yes"); - } - - if let Some(epoch) = epoch { - path.query_pairs_mut() - .append_pair("epoch", &epoch.to_string()); - } + path.query_pairs_mut() + .append_pair("epoch", &epoch.to_string()); self.post(path, &()).await } diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index e4d50ebf017..52b7c5cc851 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -147,29 +147,5 @@ pub struct UpdateGasLimitRequest { #[derive(Deserialize)] pub struct VoluntaryExitQuery { - pub confirm: ConfirmVoluntaryExit, - pub epoch: Option, -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] -#[serde(try_from = "Option")] -pub enum ConfirmVoluntaryExit { - Yes, - #[default] - No, -} - -/// Parse a `confirm` query parameter for voluntary exit. -impl TryFrom> for ConfirmVoluntaryExit { - type Error = String; - - fn try_from(opt: Option) -> Result { - match opt.as_deref() { - None => Ok(ConfirmVoluntaryExit::No), - Some("yes") => Ok(ConfirmVoluntaryExit::Yes), - Some(s) => Err(format!( - "confirm does not take any value other than 'yes', got: {s}" - )), - } - } + pub epoch: Epoch, } diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index b0c89ad07df..aff693585a6 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -5,7 +5,6 @@ mod remotekeys; mod submit_voluntary_exit; mod tests; -use crate::beacon_node_fallback::BeaconNodeFallback; use crate::http_api::submit_voluntary_exit::submit_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ @@ -14,7 +13,6 @@ use account_utils::{ }; pub use api_secret::ApiSecret; use create_validator::{create_validators_mnemonic, create_validators_web3signer}; -use eth2::lighthouse_vc::types::ConfirmVoluntaryExit; use eth2::lighthouse_vc::{ std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse}, types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes}, @@ -75,8 +73,6 @@ pub struct Context { pub spec: ChainSpec, pub config: Config, pub log: Logger, - pub genesis_time: u64, - pub beacon_nodes: Arc>, pub _phantom: PhantomData, } @@ -195,12 +191,6 @@ pub fn serve( let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); - let inner_beacon_nodes = ctx.beacon_nodes.clone(); - let beacon_nodes_filter = warp::any().map(move || inner_beacon_nodes.clone()); - - let inner_genesis_time = ctx.genesis_time; - let genesis_time_filter = warp::any().map(move || inner_genesis_time); - let inner_spec = Arc::new(ctx.spec.clone()); let spec_filter = warp::any().map(move || inner_spec.clone()); @@ -436,7 +426,7 @@ pub fn serve( .and(warp::body::json()) .and(validator_dir_filter.clone()) .and(validator_store_filter.clone()) - .and(spec_filter.clone()) + .and(spec_filter) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( @@ -924,9 +914,6 @@ pub fn serve( .and(warp::query::()) .and(warp::path::end()) .and(validator_store_filter.clone()) - .and(spec_filter) - .and(beacon_nodes_filter) - .and(genesis_time_filter) .and(log_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) @@ -934,27 +921,18 @@ pub fn serve( |pubkey: PublicKey, query: api_types::VoluntaryExitQuery, validator_store: Arc>, - spec: Arc, - beacon_nodes: Arc>, - genesis_time: u64, log, signer, task_executor: TaskExecutor| { blocking_signed_json_task(signer, move || { - if let ConfirmVoluntaryExit::No = query.confirm { - return Err(warp_utils::reject::custom_bad_request("please confirm you intend to exit this validator, it cannot be undone once the message is submitted!".to_string())); - } if let Some(handle) = task_executor.handle() { - handle.block_on(submit_voluntary_exit( + let signed_voluntary_exit = handle.block_on(submit_voluntary_exit( pubkey, query.epoch, validator_store, - spec, - beacon_nodes, - genesis_time, log, ))?; - Ok(()) + Ok(signed_voluntary_exit) } else { Err(warp_utils::reject::custom_server_error( "Lighthouse shutting down".into(), diff --git a/validator_client/src/http_api/submit_voluntary_exit.rs b/validator_client/src/http_api/submit_voluntary_exit.rs index 031fe195e30..23edc2c435f 100644 --- a/validator_client/src/http_api/submit_voluntary_exit.rs +++ b/validator_client/src/http_api/submit_voluntary_exit.rs @@ -1,28 +1,16 @@ -use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; use crate::validator_store::ValidatorStore; use bls::{PublicKey, PublicKeyBytes}; use slog::{info, Logger}; -use slot_clock::{SlotClock, SystemTimeSlotClock}; +use slot_clock::SlotClock; use std::sync::Arc; -use std::time::Duration; -use types::{ChainSpec, Epoch, EthSpec, VoluntaryExit}; +use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; pub async fn submit_voluntary_exit( pubkey: PublicKey, - maybe_epoch: Option, + epoch: Epoch, validator_store: Arc>, - spec: Arc, - beacon_nodes: Arc>, - genesis_time: u64, log: Logger, -) -> Result<(), warp::Rejection> { - let epoch = match maybe_epoch { - Some(epoch) => epoch, - None => get_current_epoch::(genesis_time, spec).ok_or_else(|| { - warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) - })?, - }; - +) -> Result { let pubkey_bytes = PublicKeyBytes::from(pubkey); let validator_index = validator_store .validator_index(&pubkey_bytes) @@ -38,6 +26,8 @@ pub async fn submit_voluntary_exit( validator_index, }; + info!(log, "Signing voluntary exit"; "validator" => pubkey_bytes.as_hex_string()); + let signed_voluntary_exit = validator_store .sign_voluntary_exit(pubkey_bytes, voluntary_exit) .await @@ -48,31 +38,5 @@ pub async fn submit_voluntary_exit( )) })?; - info!(log, "Publishing voluntary exit"; "validator" => pubkey_bytes.as_hex_string()); - - beacon_nodes - .first_success(RequireSynced::Yes, OfflineOnFailure::No, |client| async { - client - .post_beacon_pool_voluntary_exits(&signed_voluntary_exit) - .await - }) - .await - .map_err(|e| { - warp_utils::reject::custom_server_error(format!( - "Failed to publish voluntary exit: {}", - e - )) - })?; - - Ok(()) -} - -/// Calculates the current epoch from the genesis time and current time. -fn get_current_epoch(genesis_time: u64, spec: Arc) -> Option { - let slot_clock = SystemTimeSlotClock::new( - spec.genesis_slot, - Duration::from_secs(genesis_time), - Duration::from_secs(spec.seconds_per_slot), - ); - slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) + Ok(signed_voluntary_exit) } diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index 49c77a96efb..df90507e3ec 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -3,7 +3,6 @@ mod keystores; -use crate::beacon_node_fallback::BeaconNodeFallback; use crate::doppelganger_service::DoppelgangerService; use crate::{ http_api::{ApiSecret, Config as HttpConfig, Context}, @@ -135,13 +134,6 @@ impl ApiTester { allow_origin: None, }, log: log.clone(), - beacon_nodes: Arc::new(BeaconNodeFallback::new( - vec![], // FIXME: (jimmy) add a beacon node - false, - spec.clone(), - log.clone(), - )), - genesis_time, _phantom: PhantomData, }); let ctx = context.clone(); @@ -513,10 +505,10 @@ impl ApiTester { .write() .set_index(&validator.voting_pubkey, 0); - // TODO(jimmy): add test cases for different params + let test_epoch = Epoch::new(256); let resp = self .client - .post_validator_voluntary_exit(&validator.voting_pubkey, true, None) + .post_validator_voluntary_exit(&validator.voting_pubkey, test_epoch) .await; assert!(resp.is_ok()); diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 8a9e8f1616c..82cacccc605 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,8 +94,6 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, - beacon_nodes: Arc>, - genesis_time: u64, http_api_listen_addr: Option, config: Config, } @@ -484,8 +482,6 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, - beacon_nodes: beacon_nodes.clone(), - genesis_time, http_api_listen_addr: None, }) } @@ -548,8 +544,6 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), - beacon_nodes: self.beacon_nodes.clone(), - genesis_time: self.genesis_time, log: log.clone(), _phantom: PhantomData, }); From f05fa5b59461e03b1fd96e0b1d2b9cb7b2dd740c Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 28 Mar 2023 16:25:51 +1100 Subject: [PATCH 09/16] Remove exit endpoint from LH book --- book/src/api-vc-endpoints.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/book/src/api-vc-endpoints.md b/book/src/api-vc-endpoints.md index 6e3c45e0a00..80a14ae7710 100644 --- a/book/src/api-vc-endpoints.md +++ b/book/src/api-vc-endpoints.md @@ -16,7 +16,6 @@ HTTP Path | Description | [`POST /lighthouse/validators/keystore`](#post-lighthousevalidatorskeystore) | Import a keystore. [`POST /lighthouse/validators/mnemonic`](#post-lighthousevalidatorsmnemonic) | Create a new validator from an existing mnemonic. [`POST /lighthouse/validators/web3signer`](#post-lighthousevalidatorsweb3signer) | Add web3signer validators. -[`POST /lighthouse/validators/voluntary_exits](#post-lighthousevalidatorsvoluntaryexits) | Create a signed voluntary exit message. In addition to the above endpoints Lighthouse also supports all of the [standard keymanager APIs](https://ethereum.github.io/keymanager-APIs/). @@ -579,36 +578,3 @@ The following fields may be omitted or nullified to obtain default values: ### Example Response Body *No data is included in the response body.* - -## `POST /lighthouse/validators/voluntary_exits` - -Generate and sign a voluntary exit message for the given validator. - -### HTTP Specification - -| Property | Specification | -|-------------------|--------------------------------------------| -| Path | `/lighthouse/validators/voluntary_exits` | -| Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | -| Typical Responses | 200, 400 | - -### Example Request Body - -```json -{ - "pubkey": "0x81054bd51ce57a8415f0c8e0f2fbf94f5a8464552baa33263c20a4da062e5ed994a4d32c171106d2008cd063f48f6fe2" -} -``` - -### Example Response Body - -```json -{ - "message": { - "epoch": "3", - "validator_index": "35" - }, - "signature": "0xa3217699ecbecde7452ad4b43fbc60ba751770a00af4ba1af3f779d18cd2f535e29060ea10effd5f87ec9d7e27c65dcb13f4e6e2cde97f4487544d2206f795f4ded25916b2f170e084ae98f89c0bbff62d4c1a1c31015098f325620a50564d2f" -} -``` \ No newline at end of file From e1cddc939fa490f80e38bd9e5b6da43734702bf1 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 28 Mar 2023 22:46:56 +1100 Subject: [PATCH 10/16] Rename file only --- validator_client/src/http_api/mod.rs | 4 ++-- .../{submit_voluntary_exit.rs => sign_voluntary_exit.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename validator_client/src/http_api/{submit_voluntary_exit.rs => sign_voluntary_exit.rs} (100%) diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index aff693585a6..0ed5e032cb4 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -2,10 +2,10 @@ mod api_secret; mod create_validator; mod keystores; mod remotekeys; -mod submit_voluntary_exit; +mod sign_voluntary_exit; mod tests; -use crate::http_api::submit_voluntary_exit::submit_voluntary_exit; +use crate::http_api::sign_voluntary_exit::submit_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, diff --git a/validator_client/src/http_api/submit_voluntary_exit.rs b/validator_client/src/http_api/sign_voluntary_exit.rs similarity index 100% rename from validator_client/src/http_api/submit_voluntary_exit.rs rename to validator_client/src/http_api/sign_voluntary_exit.rs From 37adc18cac08078aaf1b117e34e9866cfc3e19aa Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 28 Mar 2023 23:19:22 +1100 Subject: [PATCH 11/16] Renamed function and file --- ...tary_exit.rs => create_signed_voluntary_exit.rs} | 2 +- validator_client/src/http_api/mod.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) rename validator_client/src/http_api/{sign_voluntary_exit.rs => create_signed_voluntary_exit.rs} (93%) diff --git a/validator_client/src/http_api/sign_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs similarity index 93% rename from validator_client/src/http_api/sign_voluntary_exit.rs rename to validator_client/src/http_api/create_signed_voluntary_exit.rs index 23edc2c435f..67f6cb9accd 100644 --- a/validator_client/src/http_api/sign_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -5,7 +5,7 @@ use slot_clock::SlotClock; use std::sync::Arc; use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; -pub async fn submit_voluntary_exit( +pub async fn create_signed_voluntary_exit( pubkey: PublicKey, epoch: Epoch, validator_store: Arc>, diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 0ed5e032cb4..458409b519f 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -1,11 +1,11 @@ mod api_secret; +mod create_signed_voluntary_exit; mod create_validator; mod keystores; mod remotekeys; -mod sign_voluntary_exit; mod tests; -use crate::http_api::sign_voluntary_exit::submit_voluntary_exit; +use crate::http_api::create_signed_voluntary_exit::create_signed_voluntary_exit; use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, @@ -926,12 +926,9 @@ pub fn serve( task_executor: TaskExecutor| { blocking_signed_json_task(signer, move || { if let Some(handle) = task_executor.handle() { - let signed_voluntary_exit = handle.block_on(submit_voluntary_exit( - pubkey, - query.epoch, - validator_store, - log, - ))?; + let signed_voluntary_exit = handle.block_on( + create_signed_voluntary_exit(pubkey, query.epoch, validator_store, log), + )?; Ok(signed_voluntary_exit) } else { Err(warp_utils::reject::custom_server_error( From f621c3e21d27ae45a8145704a4420e58e38c2817 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 29 Mar 2023 11:44:27 +1100 Subject: [PATCH 12/16] Make Epoch optional again, default to current if not set. --- common/eth2/src/lighthouse_vc/http_client.rs | 8 +++--- common/eth2/src/lighthouse_vc/types.rs | 2 +- .../http_api/create_signed_voluntary_exit.rs | 26 ++++++++++++++++--- validator_client/src/http_api/mod.rs | 22 +++++++++++++--- validator_client/src/http_api/tests.rs | 4 +-- validator_client/src/lib.rs | 3 +++ 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/common/eth2/src/lighthouse_vc/http_client.rs b/common/eth2/src/lighthouse_vc/http_client.rs index a8cc059641c..90c128751d0 100644 --- a/common/eth2/src/lighthouse_vc/http_client.rs +++ b/common/eth2/src/lighthouse_vc/http_client.rs @@ -647,7 +647,7 @@ impl ValidatorClientHttpClient { pub async fn post_validator_voluntary_exit( &self, pubkey: &PublicKeyBytes, - epoch: Epoch, + epoch: Option, ) -> Result { let mut path = self.server.full.clone(); @@ -659,8 +659,10 @@ impl ValidatorClientHttpClient { .push(&pubkey.to_string()) .push("voluntary_exit"); - path.query_pairs_mut() - .append_pair("epoch", &epoch.to_string()); + if let Some(epoch) = epoch { + path.query_pairs_mut() + .append_pair("epoch", &epoch.to_string()); + } self.post(path, &()).await } diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index 52b7c5cc851..fa5d4ae119e 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -147,5 +147,5 @@ pub struct UpdateGasLimitRequest { #[derive(Deserialize)] pub struct VoluntaryExitQuery { - pub epoch: Epoch, + pub epoch: Option, } diff --git a/validator_client/src/http_api/create_signed_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs index 67f6cb9accd..eb4138d1b58 100644 --- a/validator_client/src/http_api/create_signed_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -1,16 +1,26 @@ use crate::validator_store::ValidatorStore; use bls::{PublicKey, PublicKeyBytes}; use slog::{info, Logger}; -use slot_clock::SlotClock; +use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; -use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; +use std::time::Duration; +use types::{ChainSpec, Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; pub async fn create_signed_voluntary_exit( pubkey: PublicKey, - epoch: Epoch, + maybe_epoch: Option, validator_store: Arc>, + spec: Arc, + genesis_time: u64, log: Logger, ) -> Result { + let epoch = match maybe_epoch { + Some(epoch) => epoch, + None => get_current_epoch::(genesis_time, spec).ok_or_else(|| { + warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) + })?, + }; + let pubkey_bytes = PublicKeyBytes::from(pubkey); let validator_index = validator_store .validator_index(&pubkey_bytes) @@ -40,3 +50,13 @@ pub async fn create_signed_voluntary_exit(genesis_time: u64, spec: Arc) -> Option { + let slot_clock = SystemTimeSlotClock::new( + spec.genesis_slot, + Duration::from_secs(genesis_time), + Duration::from_secs(spec.seconds_per_slot), + ); + slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) +} diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 458409b519f..52bf2abace5 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -73,6 +73,7 @@ pub struct Context { pub spec: ChainSpec, pub config: Config, pub log: Logger, + pub genesis_time: u64, pub _phantom: PhantomData, } @@ -191,6 +192,9 @@ pub fn serve( let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); + let inner_genesis_time = ctx.genesis_time; + let genesis_time_filter = warp::any().map(move || inner_genesis_time); + let inner_spec = Arc::new(ctx.spec.clone()); let spec_filter = warp::any().map(move || inner_spec.clone()); @@ -426,7 +430,7 @@ pub fn serve( .and(warp::body::json()) .and(validator_dir_filter.clone()) .and(validator_store_filter.clone()) - .and(spec_filter) + .and(spec_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( @@ -914,6 +918,8 @@ pub fn serve( .and(warp::query::()) .and(warp::path::end()) .and(validator_store_filter.clone()) + .and(spec_filter) + .and(genesis_time_filter) .and(log_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) @@ -921,14 +927,22 @@ pub fn serve( |pubkey: PublicKey, query: api_types::VoluntaryExitQuery, validator_store: Arc>, + spec: Arc, + genesis_time: u64, log, signer, task_executor: TaskExecutor| { blocking_signed_json_task(signer, move || { if let Some(handle) = task_executor.handle() { - let signed_voluntary_exit = handle.block_on( - create_signed_voluntary_exit(pubkey, query.epoch, validator_store, log), - )?; + let signed_voluntary_exit = + handle.block_on(create_signed_voluntary_exit( + pubkey, + query.epoch, + validator_store, + spec, + genesis_time, + log, + ))?; Ok(signed_voluntary_exit) } else { Err(warp_utils::reject::custom_server_error( diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index df90507e3ec..2a23cfb365d 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -134,6 +134,7 @@ impl ApiTester { allow_origin: None, }, log: log.clone(), + genesis_time, _phantom: PhantomData, }); let ctx = context.clone(); @@ -505,10 +506,9 @@ impl ApiTester { .write() .set_index(&validator.voting_pubkey, 0); - let test_epoch = Epoch::new(256); let resp = self .client - .post_validator_voluntary_exit(&validator.voting_pubkey, test_epoch) + .post_validator_voluntary_exit(&validator.voting_pubkey, None) .await; assert!(resp.is_ok()); diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 82cacccc605..47ccb6459b7 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,6 +94,7 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, + genesis_time: u64, http_api_listen_addr: Option, config: Config, } @@ -482,6 +483,7 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, + genesis_time, http_api_listen_addr: None, }) } @@ -544,6 +546,7 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), + genesis_time: self.genesis_time, log: log.clone(), _phantom: PhantomData, }); From abf58bbe680cc3e937ccf269e6ee01b223b29bab Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 29 Mar 2023 15:04:27 +1100 Subject: [PATCH 13/16] Return 404 when validator is not found --- validator_client/src/http_api/create_signed_voluntary_exit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator_client/src/http_api/create_signed_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs index eb4138d1b58..fac49c50e72 100644 --- a/validator_client/src/http_api/create_signed_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -25,7 +25,7 @@ pub async fn create_signed_voluntary_exit Date: Wed, 29 Mar 2023 16:40:51 +1100 Subject: [PATCH 14/16] Improve validator exit test --- .../http_api/create_signed_voluntary_exit.rs | 17 ++++--------- validator_client/src/http_api/mod.rs | 17 ++++++------- validator_client/src/http_api/tests.rs | 24 +++++++++++++++---- validator_client/src/lib.rs | 8 +++---- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/validator_client/src/http_api/create_signed_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs index fac49c50e72..2c45af0375b 100644 --- a/validator_client/src/http_api/create_signed_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -1,22 +1,20 @@ use crate::validator_store::ValidatorStore; use bls::{PublicKey, PublicKeyBytes}; use slog::{info, Logger}; -use slot_clock::{SlotClock, SystemTimeSlotClock}; +use slot_clock::SlotClock; use std::sync::Arc; -use std::time::Duration; -use types::{ChainSpec, Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; +use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; pub async fn create_signed_voluntary_exit( pubkey: PublicKey, maybe_epoch: Option, validator_store: Arc>, - spec: Arc, - genesis_time: u64, + slot_clock: T, log: Logger, ) -> Result { let epoch = match maybe_epoch { Some(epoch) => epoch, - None => get_current_epoch::(genesis_time, spec).ok_or_else(|| { + None => get_current_epoch::(slot_clock).ok_or_else(|| { warp_utils::reject::custom_server_error("Unable to determine current epoch".to_string()) })?, }; @@ -52,11 +50,6 @@ pub async fn create_signed_voluntary_exit(genesis_time: u64, spec: Arc) -> Option { - let slot_clock = SystemTimeSlotClock::new( - spec.genesis_slot, - Duration::from_secs(genesis_time), - Duration::from_secs(spec.seconds_per_slot), - ); +fn get_current_epoch(slot_clock: T) -> Option { slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) } diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index 52bf2abace5..15b3f9fe09e 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -73,7 +73,7 @@ pub struct Context { pub spec: ChainSpec, pub config: Config, pub log: Logger, - pub genesis_time: u64, + pub slot_clock: T, pub _phantom: PhantomData, } @@ -192,8 +192,8 @@ pub fn serve( let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); - let inner_genesis_time = ctx.genesis_time; - let genesis_time_filter = warp::any().map(move || inner_genesis_time); + let inner_slot_clock = ctx.slot_clock.clone(); + let slot_clock_filter = warp::any().map(move || inner_slot_clock.clone()); let inner_spec = Arc::new(ctx.spec.clone()); let spec_filter = warp::any().map(move || inner_spec.clone()); @@ -430,7 +430,7 @@ pub fn serve( .and(warp::body::json()) .and(validator_dir_filter.clone()) .and(validator_store_filter.clone()) - .and(spec_filter.clone()) + .and(spec_filter) .and(signer.clone()) .and(task_executor_filter.clone()) .and_then( @@ -918,8 +918,7 @@ pub fn serve( .and(warp::query::()) .and(warp::path::end()) .and(validator_store_filter.clone()) - .and(spec_filter) - .and(genesis_time_filter) + .and(slot_clock_filter) .and(log_filter.clone()) .and(signer.clone()) .and(task_executor_filter.clone()) @@ -927,8 +926,7 @@ pub fn serve( |pubkey: PublicKey, query: api_types::VoluntaryExitQuery, validator_store: Arc>, - spec: Arc, - genesis_time: u64, + slot_clock: T, log, signer, task_executor: TaskExecutor| { @@ -939,8 +937,7 @@ pub fn serve( pubkey, query.epoch, validator_store, - spec, - genesis_time, + slot_clock, log, ))?; Ok(signed_voluntary_exit) diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index 2a23cfb365d..df0e4804440 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -45,6 +45,7 @@ struct ApiTester { initialized_validators: Arc>, validator_store: Arc>, url: SensitiveUrl, + slot_clock: TestingSlotClock, _server_shutdown: oneshot::Sender<()>, _validator_dir: TempDir, _runtime_shutdown: exit_future::Signal, @@ -107,7 +108,7 @@ impl ApiTester { Hash256::repeat_byte(42), spec.clone(), Some(Arc::new(DoppelgangerService::new(log.clone()))), - slot_clock, + slot_clock.clone(), &config, executor.clone(), log.clone(), @@ -134,7 +135,7 @@ impl ApiTester { allow_origin: None, }, log: log.clone(), - genesis_time, + slot_clock: slot_clock.clone(), _phantom: PhantomData, }); let ctx = context.clone(); @@ -161,6 +162,7 @@ impl ApiTester { initialized_validators, validator_store, url, + slot_clock, _server_shutdown: shutdown_tx, _validator_dir: validator_dir, _runtime_shutdown: runtime_shutdown, @@ -499,23 +501,33 @@ impl ApiTester { self } - pub async fn test_sign_voluntary_exits(self, index: usize) -> Self { + pub async fn test_sign_voluntary_exits(self, index: usize, maybe_epoch: Option) -> Self { let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index]; // manually setting validator index in `ValidatorStore` self.initialized_validators .write() .set_index(&validator.voting_pubkey, 0); + let expected_exit_epoch = maybe_epoch.unwrap_or_else(|| self.get_current_epoch()); + let resp = self .client - .post_validator_voluntary_exit(&validator.voting_pubkey, None) + .post_validator_voluntary_exit(&validator.voting_pubkey, maybe_epoch) .await; assert!(resp.is_ok()); + assert_eq!(resp.unwrap().message.epoch, expected_exit_epoch); self } + fn get_current_epoch(&self) -> Epoch { + self.slot_clock + .now() + .map(|s| s.epoch(E::slots_per_epoch())) + .unwrap() + } + pub async fn set_validator_enabled(self, index: usize, enabled: bool) -> Self { let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index]; @@ -816,7 +828,9 @@ fn validator_exit() { .await .assert_enabled_validators_count(2) .assert_validators_count(2) - .test_sign_voluntary_exits(0) + .test_sign_voluntary_exits(0, None) + .await + .test_sign_voluntary_exits(0, Some(Epoch::new(256))) .await; }); } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 47ccb6459b7..556fdef26b3 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -94,7 +94,7 @@ pub struct ProductionValidatorClient { doppelganger_service: Option>, preparation_service: PreparationService, validator_store: Arc>, - genesis_time: u64, + slot_clock: SystemTimeSlotClock, http_api_listen_addr: Option, config: Config, } @@ -462,7 +462,7 @@ impl ProductionValidatorClient { let sync_committee_service = SyncCommitteeService::new( duties_service.clone(), validator_store.clone(), - slot_clock, + slot_clock.clone(), beacon_nodes.clone(), context.service_context("sync_committee".into()), ); @@ -483,7 +483,7 @@ impl ProductionValidatorClient { preparation_service, validator_store, config, - genesis_time, + slot_clock, http_api_listen_addr: None, }) } @@ -546,7 +546,7 @@ impl ProductionValidatorClient { graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), - genesis_time: self.genesis_time, + slot_clock: self.slot_clock.clone(), log: log.clone(), _phantom: PhantomData, }); From 6edcfe39af88bfe9dcf9576077e493a02b416386 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 30 Mar 2023 18:53:03 +1100 Subject: [PATCH 15/16] Add more detail for 404s --- .../src/http_api/create_signed_voluntary_exit.rs | 11 ++++++++++- validator_client/src/validator_store.rs | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/validator_client/src/http_api/create_signed_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs index 2c45af0375b..5a7780e1072 100644 --- a/validator_client/src/http_api/create_signed_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -20,11 +20,20 @@ pub async fn create_signed_voluntary_exit ValidatorStore { self.validators.clone() } + /// Indicates if the `voting_public_key` exists in self and is enabled. + pub fn has_validator(&self, voting_public_key: &PublicKeyBytes) -> bool { + self.validators + .read() + .validator(voting_public_key) + .is_some() + } + /// Insert a new validator to `self`, where the validator is represented by an EIP-2335 /// keystore on the filesystem. #[allow(clippy::too_many_arguments)] From 9e8a17d73bea9970a0fe624ff26f3f395a89b545 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 3 Apr 2023 11:48:09 +1000 Subject: [PATCH 16/16] Improve logs when signing voluntary exits Co-authored-by: Paul Hauner --- .../src/http_api/create_signed_voluntary_exit.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/validator_client/src/http_api/create_signed_voluntary_exit.rs b/validator_client/src/http_api/create_signed_voluntary_exit.rs index 5a7780e1072..b777d158064 100644 --- a/validator_client/src/http_api/create_signed_voluntary_exit.rs +++ b/validator_client/src/http_api/create_signed_voluntary_exit.rs @@ -43,7 +43,12 @@ pub async fn create_signed_voluntary_exit pubkey_bytes.as_hex_string()); + info!( + log, + "Signing voluntary exit"; + "validator" => pubkey_bytes.as_hex_string(), + "epoch" => epoch + ); let signed_voluntary_exit = validator_store .sign_voluntary_exit(pubkey_bytes, voluntary_exit)