From 59b2247ab898b076ef84ee8d993af7e8f1b867ff Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 23 Nov 2020 01:00:22 +0000 Subject: [PATCH] Improve UX whilst VC is waiting for genesis (#1915) ## Issue Addressed - Resolves #1424 ## Proposed Changes Add a `GET lighthouse/staking` that returns 200 if the node is ready to stake (i.e., `--eth1` flag is present) or a 404 otherwise. Whilst the VC is waiting for the genesis time to start (i.e., when the genesis state is known), check the `lighthouse/staking` endpoint and log an error if the node isn't configured for staking. ## Additional Info NA --- beacon_node/http_api/src/lib.rs | 24 ++++++++++- beacon_node/http_api/tests/tests.rs | 10 +++++ common/eth2/src/lighthouse.rs | 12 ++++++ validator_client/src/lib.rs | 63 ++++++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 22e6ae2d0..356835397 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2147,7 +2147,7 @@ pub fn serve( .and(warp::path::param::()) .and(warp::path("ssz")) .and(warp::path::end()) - .and(chain_filter) + .and(chain_filter.clone()) .and_then(|state_id: StateId, chain: Arc>| { blocking_task(move || { let state = state_id.state(&chain)?; @@ -2164,6 +2164,25 @@ pub fn serve( }) }); + // GET lighthouse/staking + let get_lighthouse_staking = warp::path("lighthouse") + .and(warp::path("staking")) + .and(warp::path::end()) + .and(chain_filter) + .and_then(|chain: Arc>| { + blocking_json_task(move || { + if chain.eth1_chain.is_some() { + Ok(()) + } else { + Err(warp_utils::reject::custom_not_found( + "staking is not enabled, \ + see the --staking CLI flag" + .to_string(), + )) + } + }) + }); + // Define the ultimate set of routes that will be provided to the server. let routes = warp::get() .and( @@ -2211,7 +2230,8 @@ pub fn serve( .or(get_lighthouse_eth1_syncing.boxed()) .or(get_lighthouse_eth1_block_cache.boxed()) .or(get_lighthouse_eth1_deposit_cache.boxed()) - .or(get_lighthouse_beacon_states_ssz.boxed()), + .or(get_lighthouse_beacon_states_ssz.boxed()) + .or(get_lighthouse_staking.boxed()), ) .or(warp::post().and( post_beacon_blocks diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index b257a20d5..987b659e2 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1815,6 +1815,14 @@ impl ApiTester { self } + + pub async fn test_get_lighthouse_staking(self) -> Self { + let result = self.client.get_lighthouse_staking().await.unwrap(); + + assert_eq!(result, self.chain.eth1_chain.is_some()); + + self + } } #[tokio::test(core_threads = 2)] @@ -2087,5 +2095,7 @@ async fn lighthouse_endpoints() { .test_get_lighthouse_eth1_deposit_cache() .await .test_get_lighthouse_beacon_states_ssz() + .await + .test_get_lighthouse_staking() .await; } diff --git a/common/eth2/src/lighthouse.rs b/common/eth2/src/lighthouse.rs index 39f52da5e..a879b7c8d 100644 --- a/common/eth2/src/lighthouse.rs +++ b/common/eth2/src/lighthouse.rs @@ -356,4 +356,16 @@ impl BeaconNodeHttpClient { .map(|bytes| BeaconState::from_ssz_bytes(&bytes).map_err(Error::InvalidSsz)) .transpose() } + + /// `GET lighthouse/staking` + pub async fn get_lighthouse_staking(&self) -> Result { + let mut path = self.server.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("lighthouse") + .push("staking"); + + self.get_opt::<(), _>(path).await.map(|opt| opt.is_some()) + } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 768dbc6cb..2d7d47b84 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -43,6 +43,9 @@ use validator_store::ValidatorStore; /// The interval between attempts to contact the beacon node during startup. const RETRY_DELAY: Duration = Duration::from_secs(2); +/// The time between polls when waiting for genesis. +const WAITING_FOR_GENESIS_POLL_TIME: Duration = Duration::from_secs(12); + /// The global timeout for HTTP requests to the beacon node. const HTTP_TIMEOUT: Duration = Duration::from_secs(12); @@ -378,7 +381,18 @@ async fn init_from_beacon_node( "seconds_to_wait" => (genesis_time - now).as_secs() ); - delay_for(genesis_time - now).await; + // Start polling the node for pre-genesis information, cancelling the polling as soon as the + // timer runs out. + tokio::select! { + result = poll_whilst_waiting_for_genesis(beacon_node, genesis_time, context.log()) => result?, + () = delay_for(genesis_time - now) => () + }; + + info!( + context.log(), + "Genesis has occurred"; + "ms_since_genesis" => (genesis_time - now).as_millis() + ); } else { info!( context.log(), @@ -427,3 +441,50 @@ async fn wait_for_node(beacon_node: &BeaconNodeHttpClient, log: &Logger) -> Resu } } } + +/// Request the version from the node, looping back and trying again on failure. Exit once the node +/// has been contacted. +async fn poll_whilst_waiting_for_genesis( + beacon_node: &BeaconNodeHttpClient, + genesis_time: Duration, + log: &Logger, +) -> Result<(), String> { + loop { + match beacon_node.get_lighthouse_staking().await { + Ok(is_staking) => { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| format!("Unable to read system time: {:?}", e))?; + + if !is_staking { + error!( + log, + "Staking is disabled for beacon node"; + "msg" => "this will caused missed duties", + "info" => "see the --staking CLI flag on the beacon node" + ); + } + + if now < genesis_time { + info!( + log, + "Waiting for genesis"; + "bn_staking_enabled" => is_staking, + "seconds_to_wait" => (genesis_time - now).as_secs() + ); + } else { + break Ok(()); + } + } + Err(e) => { + error!( + log, + "Error polling beacon node"; + "error" => format!("{:?}", e) + ); + } + } + + delay_for(WAITING_FOR_GENESIS_POLL_TIME).await; + } +}