diff --git a/Cargo.lock b/Cargo.lock index 26f0e18c40..6ffef90840 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3352,6 +3352,7 @@ dependencies = [ "eth2_ssz 0.1.2", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proto_array_fork_choice 0.1.0", "reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", "rest_api 0.1.0", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/beacon_node/beacon_chain/src/fork_choice.rs b/beacon_node/beacon_chain/src/fork_choice.rs index 688111df80..a41628d72a 100644 --- a/beacon_node/beacon_chain/src/fork_choice.rs +++ b/beacon_node/beacon_chain/src/fork_choice.rs @@ -29,6 +29,7 @@ pub enum Error { UnknownBlockSlot(Hash256), UnknownJustifiedBlock(Hash256), UnknownJustifiedState(Hash256), + UnableToJsonEncode(String), } pub struct ForkChoice { @@ -230,6 +231,10 @@ impl ForkChoice { self.backend.maybe_prune(finalized_root).map_err(Into::into) } + pub fn as_json(&self) -> Result { + self.backend.as_json().map_err(Error::UnableToJsonEncode) + } + /// Returns a `SszForkChoice` which contains the current state of `Self`. pub fn as_ssz_container(&self) -> SszForkChoice { SszForkChoice { diff --git a/beacon_node/rest_api/src/advanced.rs b/beacon_node/rest_api/src/advanced.rs new file mode 100644 index 0000000000..6761f707b9 --- /dev/null +++ b/beacon_node/rest_api/src/advanced.rs @@ -0,0 +1,18 @@ +use crate::response_builder::ResponseBuilder; +use crate::{ApiError, ApiResult}; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use hyper::{Body, Request}; +use std::sync::Arc; + +/// Returns the `proto_array` fork choice struct, encoded as JSON. +/// +/// Useful for debugging or advanced inspection of the chain. +pub fn get_fork_choice( + req: Request, + beacon_chain: Arc>, +) -> ApiResult { + let json = beacon_chain.fork_choice.as_json().map_err(|e| { + ApiError::ServerError(format!("Unable to encode fork choice as JSON: {:?}", e)) + })?; + ResponseBuilder::new(&req)?.body_no_ssz(&json) +} diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 221d96e251..f4f13babc8 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -4,6 +4,7 @@ mod macros; extern crate lazy_static; extern crate network as client_network; +mod advanced; mod beacon; pub mod config; mod consensus; diff --git a/beacon_node/rest_api/src/router.rs b/beacon_node/rest_api/src/router.rs index d9fd63ce55..02c4678c95 100644 --- a/beacon_node/rest_api/src/router.rs +++ b/beacon_node/rest_api/src/router.rs @@ -1,6 +1,6 @@ use crate::{ - beacon, consensus, error::ApiError, helpers, metrics, network, node, spec, validator, BoxFut, - NetworkChannel, + advanced, beacon, consensus, error::ApiError, helpers, metrics, network, node, spec, validator, + BoxFut, NetworkChannel, }; use beacon_chain::{BeaconChain, BeaconChainTypes}; use client_network::Service as NetworkService; @@ -147,6 +147,11 @@ pub fn route( into_boxfut(spec::get_eth2_config::(req, eth2_config)) } + // Methods for advanced parameters + (&Method::GET, "/advanced/fork_choice") => { + into_boxfut(advanced::get_fork_choice::(req, beacon_chain)) + } + (&Method::GET, "/metrics") => into_boxfut(metrics::get_prometheus::( req, beacon_chain, diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index 1d438253a5..d8d370dc09 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -792,6 +792,24 @@ fn get_committees() { assert_eq!(result, expected, "result should be as expected"); } +#[test] +fn get_fork_choice() { + let mut env = build_env(); + + let node = build_node(&mut env, testing_client_config()); + let remote_node = node.remote_node().expect("should produce remote node"); + + // Ideally we would check that the returned fork choice is the same as the one in the + // `beacon_chain`, however that would involve exposing (making public) the core fork choice + // struct that is a bit messy. + // + // Given that serializing the fork choice is just vanilla serde, I think it's fair to assume it + // works. + env.runtime() + .block_on(remote_node.http.advanced().get_fork_choice()) + .expect("should not error when getting fork choice"); +} + fn compare_validator_response( state: &BeaconState, response: &ValidatorResponse, diff --git a/eth2/proto_array_fork_choice/src/lib.rs b/eth2/proto_array_fork_choice/src/lib.rs index 8cd40fedcf..85ac466f43 100644 --- a/eth2/proto_array_fork_choice/src/lib.rs +++ b/eth2/proto_array_fork_choice/src/lib.rs @@ -5,3 +5,7 @@ mod ssz_container; pub use crate::proto_array_fork_choice::ProtoArrayForkChoice; pub use error::Error; + +pub mod core { + pub use super::proto_array::ProtoArray; +} diff --git a/eth2/utils/remote_beacon_node/Cargo.toml b/eth2/utils/remote_beacon_node/Cargo.toml index 68a5e5df0d..f17109d8d9 100644 --- a/eth2/utils/remote_beacon_node/Cargo.toml +++ b/eth2/utils/remote_beacon_node/Cargo.toml @@ -17,3 +17,4 @@ hex = "0.3" eth2_ssz = { path = "../../../eth2/utils/ssz" } serde_json = "^1.0" eth2_config = { path = "../../../eth2/utils/eth2_config" } +proto_array_fork_choice = { path = "../../../eth2/proto_array_fork_choice" } diff --git a/eth2/utils/remote_beacon_node/src/lib.rs b/eth2/utils/remote_beacon_node/src/lib.rs index 55d6d9cbf1..f01c9f1aa0 100644 --- a/eth2/utils/remote_beacon_node/src/lib.rs +++ b/eth2/utils/remote_beacon_node/src/lib.rs @@ -5,6 +5,7 @@ use eth2_config::Eth2Config; use futures::{future, Future, IntoFuture}; +use proto_array_fork_choice::core::ProtoArray; use reqwest::{ r#async::{Client, ClientBuilder, Response}, StatusCode, @@ -101,6 +102,10 @@ impl HttpClient { Node(self.clone()) } + pub fn advanced(&self) -> Advanced { + Advanced(self.clone()) + } + fn url(&self, path: &str) -> Result { self.url.join(path).map_err(|e| e.into()) } @@ -536,6 +541,27 @@ impl Node { } } +/// Provides the functions on the `/advanced` endpoint of the node. +#[derive(Clone)] +pub struct Advanced(HttpClient); + +impl Advanced { + fn url(&self, path: &str) -> Result { + self.0 + .url("advanced/") + .and_then(move |url| url.join(path).map_err(Error::from)) + .map_err(Into::into) + } + + /// Gets the core `ProtoArray` struct from the node. + pub fn get_fork_choice(&self) -> impl Future { + let client = self.0.clone(); + self.url("fork_choice") + .into_future() + .and_then(move |url| client.json_get(url, vec![])) + } +} + #[derive(Deserialize)] #[serde(bound = "T: EthSpec")] pub struct BlockResponse {