diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 00b4452ec1a..1e6d00449da 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -12,6 +12,7 @@ mod proposer_duties; mod state_id; mod sync_committees; mod validator_inclusion; +mod version; use beacon_chain::{ attestation_verification::SignatureVerifiedAttestation, @@ -21,7 +22,7 @@ use beacon_chain::{ WhenSlotSkipped, }; use block_id::BlockId; -use eth2::types::{self as api_types, ValidatorId}; +use eth2::types::{self as api_types, EndpointVersion, ValidatorId}; use eth2_libp2p::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_version::version_with_platform; use network::NetworkMessage; @@ -43,6 +44,7 @@ use types::{ SignedContributionAndProof, SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, }; +use version::{fork_versioned_response, unsupported_version_rejection, V1}; use warp::http::StatusCode; use warp::sse::Event; use warp::Reply; @@ -50,7 +52,6 @@ use warp::{http::Response, Filter}; use warp_utils::task::{blocking_json_task, blocking_task}; const API_PREFIX: &str = "eth"; -const API_VERSION: &str = "v1"; /// If the node is within this many epochs from the head, we declare it to be synced regardless of /// the network sync state. @@ -154,7 +155,7 @@ pub fn prometheus_metrics() -> warp::filters::log::Log Option<&'static str> { - if info.path() == format!("/{}/{}/{}", API_PREFIX, API_VERSION, s) { + if info.path() == format!("/{}/{}", API_PREFIX, s) { Some(s) } else { None @@ -162,30 +163,30 @@ pub fn prometheus_metrics() -> warp::filters::log::Log Option<&'static str> { - if info - .path() - .starts_with(&format!("/{}/{}/{}", API_PREFIX, API_VERSION, s)) - { + if info.path().starts_with(&format!("/{}/{}", API_PREFIX, s)) { Some(s) } else { None } }; - equals("beacon/blocks") - .or_else(|| starts_with("validator/duties/attester")) - .or_else(|| starts_with("validator/duties/proposer")) - .or_else(|| starts_with("validator/attestation_data")) - .or_else(|| starts_with("validator/blocks")) - .or_else(|| starts_with("validator/aggregate_attestation")) - .or_else(|| starts_with("validator/aggregate_and_proofs")) - .or_else(|| starts_with("validator/beacon_committee_subscriptions")) - .or_else(|| starts_with("beacon/")) - .or_else(|| starts_with("config/")) - .or_else(|| starts_with("debug/")) - .or_else(|| starts_with("events/")) - .or_else(|| starts_with("node/")) - .or_else(|| starts_with("validator/")) + // First line covers `POST /v1/beacon/blocks` only + equals("v1/beacon/blocks") + .or_else(|| starts_with("v1/validator/duties/attester")) + .or_else(|| starts_with("v1/validator/duties/proposer")) + .or_else(|| starts_with("v1/validator/attestation_data")) + .or_else(|| starts_with("v1/validator/blocks")) + .or_else(|| starts_with("v2/validator/blocks")) + .or_else(|| starts_with("v1/validator/aggregate_attestation")) + .or_else(|| starts_with("v1/validator/aggregate_and_proofs")) + .or_else(|| starts_with("v1/validator/beacon_committee_subscriptions")) + .or_else(|| starts_with("v1/beacon/")) + .or_else(|| starts_with("v2/beacon/")) + .or_else(|| starts_with("v1/config/")) + .or_else(|| starts_with("v1/debug/")) + .or_else(|| starts_with("v1/events/")) + .or_else(|| starts_with("v1/node/")) + .or_else(|| starts_with("v1/validator/")) .unwrap_or("other") }; @@ -241,7 +242,30 @@ pub fn serve( )); } - let eth1_v1 = warp::path(API_PREFIX).and(warp::path(API_VERSION)); + // Create a filter that extracts the endpoint version. + let any_version = warp::path(API_PREFIX).and(warp::path::param::().or_else( + |_| async move { + Err(warp_utils::reject::custom_bad_request( + "Invalid version identifier".to_string(), + )) + }, + )); + + // Filter that enforces a single endpoint version and then discards the `EndpointVersion`. + let single_version = |reqd: EndpointVersion| { + any_version + .clone() + .and_then(move |version| async move { + if version == reqd { + Ok(()) + } else { + Err(unsupported_version_rejection(version)) + } + }) + .untuple_one() + }; + + let eth1_v1 = single_version(V1); // Create a `warp` filter that provides access to the network globals. let inner_network_globals = ctx.network_globals.clone(); @@ -877,23 +901,32 @@ pub fn serve( }, ); - let beacon_blocks_path = eth1_v1 + let block_id_or_err = warp::path::param::().or_else(|_| async { + Err(warp_utils::reject::custom_bad_request( + "Invalid block ID".to_string(), + )) + }); + + let beacon_blocks_path_v1 = eth1_v1 .and(warp::path("beacon")) .and(warp::path("blocks")) - .and(warp::path::param::().or_else(|_| async { - Err(warp_utils::reject::custom_bad_request( - "Invalid block ID".to_string(), - )) - })) + .and(block_id_or_err.clone()) + .and(chain_filter.clone()); + + let beacon_blocks_path_any = any_version + .and(warp::path("beacon")) + .and(warp::path("blocks")) + .and(block_id_or_err) .and(chain_filter.clone()); // GET beacon/blocks/{block_id} - let get_beacon_block = beacon_blocks_path + let get_beacon_block = beacon_blocks_path_any .clone() .and(warp::path::end()) .and(warp::header::optional::("accept")) .and_then( - |block_id: BlockId, + |endpoint_version: EndpointVersion, + block_id: BlockId, chain: Arc>, accept_header: Option| { blocking_task(move || { @@ -909,17 +942,18 @@ pub fn serve( e )) }), - _ => Ok( - warp::reply::json(&api_types::GenericResponseRef::from(&block)) - .into_response(), - ), + _ => { + let fork_name = block.fork_name(&chain.spec).ok(); + fork_versioned_response(endpoint_version, fork_name, block) + .map(|res| warp::reply::json(&res).into_response()) + } } }) }, ); // GET beacon/blocks/{block_id}/root - let get_beacon_block_root = beacon_blocks_path + let get_beacon_block_root = beacon_blocks_path_v1 .clone() .and(warp::path("root")) .and(warp::path::end()) @@ -933,7 +967,7 @@ pub fn serve( }); // GET beacon/blocks/{block_id}/attestations - let get_beacon_block_attestations = beacon_blocks_path + let get_beacon_block_attestations = beacon_blocks_path_v1 .clone() .and(warp::path("attestations")) .and(warp::path::end()) @@ -1331,7 +1365,8 @@ pub fn serve( */ // GET debug/beacon/states/{state_id} - let get_debug_beacon_states = eth1_v1 + let get_debug_beacon_states = any_version + .clone() .and(warp::path("debug")) .and(warp::path("beacon")) .and(warp::path("states")) @@ -1344,7 +1379,8 @@ pub fn serve( .and(warp::header::optional::("accept")) .and(chain_filter.clone()) .and_then( - |state_id: StateId, + |endpoint_version: EndpointVersion, + state_id: StateId, accept_header: Option, chain: Arc>| { blocking_task(move || match accept_header { @@ -1362,10 +1398,9 @@ pub fn serve( }) } _ => state_id.map_state(&chain, |state| { - Ok( - warp::reply::json(&api_types::GenericResponseRef::from(&state)) - .into_response(), - ) + let fork_name = state.fork_name(&chain.spec).ok(); + let res = fork_versioned_response(endpoint_version, fork_name, &state)?; + Ok(warp::reply::json(&res).into_response()) }), }) }, @@ -1685,7 +1720,8 @@ pub fn serve( }); // GET validator/blocks/{slot} - let get_validator_blocks = eth1_v1 + let get_validator_blocks = any_version + .clone() .and(warp::path("validator")) .and(warp::path("blocks")) .and(warp::path::param::().or_else(|_| async { @@ -1698,7 +1734,10 @@ pub fn serve( .and(warp::query::()) .and(chain_filter.clone()) .and_then( - |slot: Slot, query: api_types::ValidatorBlocksQuery, chain: Arc>| { + |endpoint_version: EndpointVersion, + slot: Slot, + query: api_types::ValidatorBlocksQuery, + chain: Arc>| { blocking_json_task(move || { let randao_reveal = (&query.randao_reveal).try_into().map_err(|e| { warp_utils::reject::custom_bad_request(format!( @@ -1707,11 +1746,11 @@ pub fn serve( )) })?; - chain + let (block, _) = chain .produce_block(randao_reveal, slot, query.graffiti.map(Into::into)) - .map(|block_and_state| block_and_state.0) - .map(api_types::GenericResponse::from) - .map_err(warp_utils::reject::block_production_error) + .map_err(warp_utils::reject::block_production_error)?; + let fork_name = block.to_ref().fork_name(&chain.spec).ok(); + fork_versioned_response(endpoint_version, fork_name, block) }) }, ); diff --git a/beacon_node/http_api/src/version.rs b/beacon_node/http_api/src/version.rs new file mode 100644 index 00000000000..db891727ef7 --- /dev/null +++ b/beacon_node/http_api/src/version.rs @@ -0,0 +1,28 @@ +use crate::api_types::{EndpointVersion, ForkVersionedResponse}; +use serde::Serialize; +use types::ForkName; + +pub const V1: EndpointVersion = EndpointVersion(1); +pub const V2: EndpointVersion = EndpointVersion(2); + +pub fn fork_versioned_response( + endpoint_version: EndpointVersion, + fork_name: Option, + data: T, +) -> Result, warp::reject::Rejection> { + let fork_name = if endpoint_version == V1 { + None + } else if endpoint_version == V2 { + fork_name + } else { + return Err(unsupported_version_rejection(endpoint_version)); + }; + Ok(ForkVersionedResponse { + version: fork_name, + data, + }) +} + +pub fn unsupported_version_rejection(version: EndpointVersion) -> warp::reject::Rejection { + warp_utils::reject::custom_bad_request(format!("Unsupported endpoint version: {}", version)) +} diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 8a952dde59c..361a1cafb61 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -921,13 +921,18 @@ impl ApiTester { } } - let json_result = self - .client - .get_beacon_blocks(block_id) - .await - .unwrap() - .map(|res| res.data); - assert_eq!(json_result, expected, "{:?}", block_id); + let json_result = self.client.get_beacon_blocks(block_id).await.unwrap(); + + if let (Some(json), Some(expected)) = (&json_result, &expected) { + assert_eq!(json.data, *expected, "{:?}", block_id); + assert_eq!( + json.version, + Some(expected.fork_name(&self.chain.spec).unwrap()) + ); + } else { + assert_eq!(json_result, None); + assert_eq!(expected, None); + } let ssz_result = self .client @@ -935,6 +940,16 @@ impl ApiTester { .await .unwrap(); assert_eq!(ssz_result, expected, "{:?}", block_id); + + // Check that the legacy v1 API still works but doesn't return a version field. + let v1_result = self.client.get_beacon_blocks_v1(block_id).await.unwrap(); + if let (Some(v1_result), Some(expected)) = (&v1_result, &expected) { + assert_eq!(v1_result.version, None); + assert_eq!(v1_result.data, *expected); + } else { + assert_eq!(v1_result, None); + assert_eq!(expected, None); + } } self @@ -1353,23 +1368,44 @@ impl ApiTester { pub async fn test_get_debug_beacon_states(self) -> Self { for state_id in self.interesting_state_ids() { + let result_json = self.client.get_debug_beacon_states(state_id).await.unwrap(); + + let mut expected = self.get_state(state_id); + expected.as_mut().map(|state| state.drop_all_caches()); + + if let (Some(json), Some(expected)) = (&result_json, &expected) { + assert_eq!(json.data, *expected, "{:?}", state_id); + assert_eq!( + json.version, + Some(expected.fork_name(&self.chain.spec).unwrap()) + ); + } else { + assert_eq!(result_json, None); + assert_eq!(expected, None); + } + + // Check SSZ API. let result_ssz = self .client .get_debug_beacon_states_ssz(state_id, &self.chain.spec) .await .unwrap(); - let result_json = self + assert_eq!(result_ssz, expected, "{:?}", state_id); + + // Check legacy v1 API. + let result_v1 = self .client - .get_debug_beacon_states(state_id) + .get_debug_beacon_states_v1(state_id) .await - .unwrap() - .map(|res| res.data); - - let mut expected = self.get_state(state_id); - expected.as_mut().map(|state| state.drop_all_caches()); + .unwrap(); - assert_eq!(result_ssz, expected, "{:?}", state_id); - assert_eq!(result_json, expected, "{:?}", state_id); + if let (Some(json), Some(expected)) = (&result_v1, &expected) { + assert_eq!(json.version, None); + assert_eq!(json.data, *expected, "{:?}", state_id); + } else { + assert_eq!(result_v1, None); + assert_eq!(expected, None); + } } self diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index aab891f1510..f3b4033dad2 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -26,6 +26,9 @@ use std::fmt; use std::iter::Iterator; use std::time::Duration; +pub const V1: EndpointVersion = EndpointVersion(1); +pub const V2: EndpointVersion = EndpointVersion(2); + #[derive(Debug)] pub enum Error { /// The `reqwest` client raised an error. @@ -142,14 +145,14 @@ impl BeaconNodeHttpClient { } } - /// Return the path with the standard `/eth1/v1` prefix applied. - fn eth_path(&self) -> Result { + /// Return the path with the standard `/eth/vX` prefix applied. + fn eth_path(&self, version: EndpointVersion) -> Result { let mut path = self.server.full.clone(); path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("eth") - .push("v1"); + .push(&version.to_string()); Ok(path) } @@ -315,7 +318,7 @@ impl BeaconNodeHttpClient { /// /// May return a `404` if beacon chain genesis has not yet occurred. pub async fn get_beacon_genesis(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -332,7 +335,7 @@ impl BeaconNodeHttpClient { &self, state_id: StateId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -351,7 +354,7 @@ impl BeaconNodeHttpClient { &self, state_id: StateId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -370,7 +373,7 @@ impl BeaconNodeHttpClient { &self, state_id: StateId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -390,7 +393,7 @@ impl BeaconNodeHttpClient { state_id: StateId, ids: Option<&[ValidatorId]>, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -420,7 +423,7 @@ impl BeaconNodeHttpClient { ids: Option<&[ValidatorId]>, statuses: Option<&[ValidatorStatus]>, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -460,7 +463,7 @@ impl BeaconNodeHttpClient { index: Option, epoch: Option, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -495,7 +498,7 @@ impl BeaconNodeHttpClient { state_id: StateId, validator_id: &ValidatorId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -516,7 +519,7 @@ impl BeaconNodeHttpClient { slot: Option, parent_root: Option, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -543,7 +546,7 @@ impl BeaconNodeHttpClient { &self, block_id: BlockId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -561,7 +564,7 @@ impl BeaconNodeHttpClient { &self, block: &SignedBeaconBlock, ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -574,14 +577,32 @@ impl BeaconNodeHttpClient { Ok(()) } - /// `GET beacon/blocks` + /// `GET v2/beacon/blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks( &self, block_id: BlockId, - ) -> Result>>, Error> { - let mut path = self.eth_path()?; + ) -> Result>>, Error> { + let mut path = self.eth_path(V2)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("blocks") + .push(&block_id.to_string()); + + self.get_opt(path).await + } + + /// `GET v1/beacon/blocks` (LEGACY) + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_blocks_v1( + &self, + block_id: BlockId, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -600,7 +621,7 @@ impl BeaconNodeHttpClient { block_id: BlockId, spec: &ChainSpec, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -621,7 +642,7 @@ impl BeaconNodeHttpClient { &self, block_id: BlockId, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -640,7 +661,7 @@ impl BeaconNodeHttpClient { &self, block_id: BlockId, ) -> Result>>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -657,7 +678,7 @@ impl BeaconNodeHttpClient { &self, attestations: &[Attestation], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -677,7 +698,7 @@ impl BeaconNodeHttpClient { slot: Option, committee_index: Option, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -703,7 +724,7 @@ impl BeaconNodeHttpClient { &self, slashing: &AttesterSlashing, ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -720,7 +741,7 @@ impl BeaconNodeHttpClient { pub async fn get_beacon_pool_attester_slashings( &self, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -736,7 +757,7 @@ impl BeaconNodeHttpClient { &self, slashing: &ProposerSlashing, ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -753,7 +774,7 @@ impl BeaconNodeHttpClient { pub async fn get_beacon_pool_proposer_slashings( &self, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -769,7 +790,7 @@ impl BeaconNodeHttpClient { &self, exit: &SignedVoluntaryExit, ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -786,7 +807,7 @@ impl BeaconNodeHttpClient { pub async fn get_beacon_pool_voluntary_exits( &self, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -802,7 +823,7 @@ impl BeaconNodeHttpClient { &self, signatures: &[SyncCommitteeMessage], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -820,7 +841,7 @@ impl BeaconNodeHttpClient { &self, signed_contributions: &[SignedContributionAndProof], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -834,7 +855,7 @@ impl BeaconNodeHttpClient { /// `GET config/fork_schedule` pub async fn get_config_fork_schedule(&self) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -846,7 +867,7 @@ impl BeaconNodeHttpClient { /// `GET config/spec` pub async fn get_config_spec(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -860,7 +881,7 @@ impl BeaconNodeHttpClient { pub async fn get_config_deposit_contract( &self, ) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -872,7 +893,7 @@ impl BeaconNodeHttpClient { /// `GET node/version` pub async fn get_node_version(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -884,7 +905,7 @@ impl BeaconNodeHttpClient { /// `GET node/identity` pub async fn get_node_identity(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -896,7 +917,7 @@ impl BeaconNodeHttpClient { /// `GET node/syncing` pub async fn get_node_syncing(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -908,7 +929,7 @@ impl BeaconNodeHttpClient { /// `GET node/health` pub async fn get_node_health(&self) -> Result { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -934,7 +955,7 @@ impl BeaconNodeHttpClient { &self, peer_id: PeerId, ) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -951,7 +972,7 @@ impl BeaconNodeHttpClient { states: Option<&[PeerState]>, directions: Option<&[PeerDirection]>, ) -> Result { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -981,7 +1002,7 @@ impl BeaconNodeHttpClient { /// `GET node/peer_count` pub async fn get_node_peer_count(&self) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -991,12 +1012,29 @@ impl BeaconNodeHttpClient { self.get(path).await } - /// `GET debug/beacon/states/{state_id}` + /// `GET v2/debug/beacon/states/{state_id}` pub async fn get_debug_beacon_states( &self, state_id: StateId, - ) -> Result>>, Error> { - let mut path = self.eth_path()?; + ) -> Result>>, Error> { + let mut path = self.eth_path(V2)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("debug") + .push("beacon") + .push("states") + .push(&state_id.to_string()); + + self.get_opt(path).await + } + + /// `GET v1/debug/beacon/states/{state_id}` (LEGACY) + pub async fn get_debug_beacon_states_v1( + &self, + state_id: StateId, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1015,7 +1053,7 @@ impl BeaconNodeHttpClient { state_id: StateId, spec: &ChainSpec, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1034,7 +1072,7 @@ impl BeaconNodeHttpClient { pub async fn get_debug_beacon_heads( &self, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1050,7 +1088,7 @@ impl BeaconNodeHttpClient { &self, epoch: Epoch, ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1063,14 +1101,14 @@ impl BeaconNodeHttpClient { .await } - /// `GET validator/blocks/{slot}` + /// `GET v2/validator/blocks/{slot}` pub async fn get_validator_blocks( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, - ) -> Result>, Error> { - let mut path = self.eth_path()?; + ) -> Result>, Error> { + let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1095,7 +1133,7 @@ impl BeaconNodeHttpClient { slot: Slot, committee_index: CommitteeIndex, ) -> Result, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1115,7 +1153,7 @@ impl BeaconNodeHttpClient { slot: Slot, attestation_data_root: Hash256, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1138,7 +1176,7 @@ impl BeaconNodeHttpClient { &self, sync_committee_data: &SyncContributionData, ) -> Result>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1165,7 +1203,7 @@ impl BeaconNodeHttpClient { epoch: Epoch, indices: &[u64], ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1183,7 +1221,7 @@ impl BeaconNodeHttpClient { &self, aggregates: &[SignedAggregateAndProof], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1201,7 +1239,7 @@ impl BeaconNodeHttpClient { &self, subscriptions: &[BeaconCommitteeSubscription], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1218,7 +1256,7 @@ impl BeaconNodeHttpClient { &self, subscriptions: &[SyncCommitteeSubscription], ) -> Result<(), Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? @@ -1235,7 +1273,7 @@ impl BeaconNodeHttpClient { &self, topic: &[EventTopic], ) -> Result, Error>>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("events"); @@ -1266,7 +1304,7 @@ impl BeaconNodeHttpClient { epoch: Epoch, indices: &[u64], ) -> Result>, Error> { - let mut path = self.eth_path()?; + let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 139fbcbe0d3..68af259cf22 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -51,6 +51,30 @@ impl Failure { } } +/// The version of a single API endpoint, e.g. the `v1` in `/eth/v1/beacon/blocks`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct EndpointVersion(pub u64); + +impl FromStr for EndpointVersion { + type Err = (); + + fn from_str(s: &str) -> Result { + if let Some(version_str) = s.strip_prefix("v") { + u64::from_str(version_str) + .map(EndpointVersion) + .map_err(|_| ()) + } else { + Err(()) + } + } +} + +impl std::fmt::Display for EndpointVersion { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(fmt, "v{}", self.0) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct GenesisData { #[serde(with = "serde_utils::quoted_u64")] @@ -187,6 +211,14 @@ impl<'a, T: Serialize> From<&'a T> for GenericResponseRef<'a, T> { } } +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +// #[serde(bound = "T: Serialize + serde::de::DeserializeOwned")] +pub struct ForkVersionedResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + pub data: T, +} + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct RootData { pub root: Hash256, diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 0de0683f2d2..27bed6f4973 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -149,6 +149,27 @@ impl BeaconBlock { } impl<'a, T: EthSpec> BeaconBlockRef<'a, T> { + /// Returns the name of the fork pertaining to `self`. + /// + /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork + /// dictated by `self.slot()`. + pub fn fork_name(&self, spec: &ChainSpec) -> Result { + let fork_at_slot = spec.fork_name_at_slot::(self.slot()); + let object_fork = match self { + BeaconBlockRef::Base { .. } => ForkName::Base, + BeaconBlockRef::Altair { .. } => ForkName::Altair, + }; + + if fork_at_slot == object_fork { + Ok(object_fork) + } else { + Err(InconsistentFork { + fork_at_slot, + object_fork, + }) + } + } + /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. pub fn body(&self) -> BeaconBlockBodyRef<'a, T> { match self { diff --git a/consensus/types/src/fork_name.rs b/consensus/types/src/fork_name.rs index 4941073b67b..a464a0ed9ad 100644 --- a/consensus/types/src/fork_name.rs +++ b/consensus/types/src/fork_name.rs @@ -1,6 +1,12 @@ use crate::{ChainSpec, Epoch}; +use serde_derive::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(try_from = "String")] +#[serde(into = "String")] pub enum ForkName { Base, Altair, @@ -48,7 +54,7 @@ impl ForkName { } } -impl std::str::FromStr for ForkName { +impl FromStr for ForkName { type Err = (); fn from_str(fork_name: &str) -> Result { @@ -60,6 +66,29 @@ impl std::str::FromStr for ForkName { } } +impl Display for ForkName { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ForkName::Base => "phase0".fmt(f), + ForkName::Altair => "altair".fmt(f), + } + } +} + +impl From for String { + fn from(fork: ForkName) -> String { + fork.to_string() + } +} + +impl TryFrom for ForkName { + type Error = String; + + fn try_from(s: String) -> Result { + Self::from_str(&s).map_err(|()| format!("Invalid fork name: {}", s)) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct InconsistentFork { pub fork_at_slot: ForkName, diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 1a9e93b8e2d..14c1ffb8cfe 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -71,20 +71,7 @@ impl SignedBeaconBlock { /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork /// dictated by `self.slot()`. pub fn fork_name(&self, spec: &ChainSpec) -> Result { - let fork_at_slot = spec.fork_name_at_slot::(self.slot()); - let object_fork = match self { - SignedBeaconBlock::Base { .. } => ForkName::Base, - SignedBeaconBlock::Altair { .. } => ForkName::Altair, - }; - - if fork_at_slot == object_fork { - Ok(object_fork) - } else { - Err(InconsistentFork { - fork_at_slot, - object_fork, - }) - } + self.message().fork_name(spec) } /// SSZ decode.