diff --git a/Cargo.lock b/Cargo.lock index c78fc2bdc07..3d7220ab592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2303,6 +2303,7 @@ name = "http_api" version = "0.1.0" dependencies = [ "beacon_chain", + "discv5", "environment", "eth1", "eth2", diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index c36dbe1ab9a..cb5886c0345 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -26,3 +26,4 @@ state_processing = { path = "../../consensus/state_processing" } store = { path = "../store" } environment = { path = "../../lighthouse/environment" } tree_hash = { path = "../../consensus/tree_hash" } +discv5 = { version = "0.1.0-alpha.10", features = ["libp2p"] } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index ea8621884fe..a779600a071 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -9,7 +9,7 @@ use beacon_chain::{ use beacon_proposer_cache::BeaconProposerCache; use block_id::BlockId; use eth2::types::{self as api_types, ValidatorId}; -use eth2_libp2p::PubsubMessage; +use eth2_libp2p::{NetworkGlobals, PubsubMessage}; use network::NetworkMessage; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -34,6 +34,7 @@ pub struct Context { pub config: Config, pub chain: Option>>, pub network_tx: Option>>, + pub network_globals: Option>>, pub log: Logger, } @@ -110,6 +111,20 @@ pub fn serve( }) }; + let inner_network_globals = ctx.network_globals.clone(); + let network_globals = || { + warp::any() + .map(move || inner_network_globals.clone()) + .and_then(|network_globals| async move { + match network_globals { + Some(globals) => Ok(globals), + None => Err(crate::reject::custom_not_found( + "network globals are not initialized.".to_string(), + )), + } + }) + }; + let inner_ctx = ctx.clone(); let chain_filter = warp::any() @@ -894,6 +909,40 @@ pub fn serve( }) }); + /* + * node + */ + + // GET node/syncing + let get_node_syncing = eth1_v1 + .and(warp::path("node")) + .and(warp::path("syncing")) + .and(warp::path::end()) + .and(network_globals()) + .and(chain_filter.clone()) + .and_then( + |network_globals: Arc>, chain: Arc>| { + blocking_json_task(move || { + let head_slot = chain + .head_info() + .map(|info| info.slot) + .map_err(crate::reject::beacon_chain_error)?; + let current_slot = chain.slot().map_err(crate::reject::beacon_chain_error)?; + + // Taking advantage of saturating subtraction on slot. + let sync_distance = current_slot - head_slot; + + let syncing_data = api_types::SyncingData { + is_syncing: network_globals.sync_state.read().is_syncing(), + head_slot, + sync_distance, + }; + + Ok(api_types::GenericResponse::from(syncing_data)) + }) + }, + ); + /* * validator */ @@ -1159,6 +1208,7 @@ pub fn serve( .or(get_config_deposit_contract.boxed()) .or(get_debug_beacon_states.boxed()) .or(get_debug_beacon_heads.boxed()) + .or(get_node_syncing.boxed()) .or(get_validator_duties_attester.boxed()) .or(get_validator_duties_proposer.boxed()) .or(get_validator_blocks.boxed()) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 30f0be04c4d..bad9d5adbc5 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -5,8 +5,10 @@ use beacon_chain::{ }, BeaconChain, StateSkipConfig, }; +use discv5::enr::{CombinedKey, EnrBuilder}; use environment::null_logger; use eth2::{types::*, BeaconNodeClient, Url}; +use eth2_libp2p::NetworkGlobals; use http_api::{Config, Context}; use network::NetworkMessage; use std::convert::TryInto; @@ -128,6 +130,12 @@ impl ApiTester { let (network_tx, network_rx) = mpsc::unbounded_channel(); + let log = null_logger().unwrap(); + + let enr_key = CombinedKey::generate_secp256k1(); + let enr = EnrBuilder::new("v4").build(&enr_key).unwrap(); + let network_globals = NetworkGlobals::new(enr, 42, 42, &log); + let context = Arc::new(Context { config: Config { enabled: true, @@ -136,7 +144,8 @@ impl ApiTester { }, chain: Some(chain.clone()), network_tx: Some(network_tx), - log: null_logger().unwrap(), + network_globals: Some(Arc::new(network_globals)), + log, }); let ctx = context.clone(); let (shutdown_tx, shutdown_rx) = oneshot::channel(); @@ -952,6 +961,22 @@ impl ApiTester { self } + pub async fn test_get_node_syncing(self) -> Self { + let result = self.client.get_node_syncing().await.unwrap().data; + let head_slot = self.chain.head_info().unwrap().slot; + let sync_distance = self.chain.slot().unwrap() - head_slot; + + let expected = SyncingData { + is_syncing: false, + head_slot, + sync_distance, + }; + + assert_eq!(result, expected); + + self + } + pub async fn test_get_debug_beacon_states(self) -> Self { for state_id in self.interesting_state_ids() { let result = self @@ -1554,6 +1579,11 @@ async fn debug_get() { .await; } +#[tokio::test(core_threads = 2)] +async fn node_get() { + ApiTester::new().test_get_node_syncing().await; +} + #[tokio::test(core_threads = 2)] async fn get_validator_duties_attester() { ApiTester::new().test_get_validator_duties_attester().await; diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 06f61dfaffd..a8433b8d292 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -512,6 +512,18 @@ impl BeaconNodeClient { self.get(path).await } + /// `GET node/syncing` + pub async fn get_node_syncing(&self) -> Result, Error> { + let mut path = self.server.clone(); + + path.path_segments_mut() + .expect("path is base") + .push("node") + .push("syncing"); + + self.get(path).await + } + /// `GET debug/beacon/states/{state_id}` pub async fn get_debug_beacon_states( &self, diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 9c1b45872bd..3d1121842d9 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -321,6 +321,13 @@ pub struct ChainHeadData { pub root: Hash256, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SyncingData { + pub is_syncing: bool, + pub head_slot: Slot, + pub sync_distance: Slot, +} + #[derive(Clone, PartialEq, Debug, Deserialize)] #[serde(try_from = "String", bound = "T: FromStr")] pub struct QueryVec(pub Vec);