Skip to content

Commit d1bbf1a

Browse files
committed
Add /v2/ API endpoints
1 parent b9d134e commit d1bbf1a

File tree

8 files changed

+348
-138
lines changed

8 files changed

+348
-138
lines changed

beacon_node/http_api/src/lib.rs

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod proposer_duties;
1212
mod state_id;
1313
mod sync_committees;
1414
mod validator_inclusion;
15+
mod version;
1516

1617
use beacon_chain::{
1718
attestation_verification::SignatureVerifiedAttestation,
@@ -21,7 +22,7 @@ use beacon_chain::{
2122
WhenSlotSkipped,
2223
};
2324
use block_id::BlockId;
24-
use eth2::types::{self as api_types, ValidatorId};
25+
use eth2::types::{self as api_types, EndpointVersion, ValidatorId};
2526
use eth2_libp2p::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
2627
use lighthouse_version::version_with_platform;
2728
use network::NetworkMessage;
@@ -43,14 +44,14 @@ use types::{
4344
SignedContributionAndProof, SignedVoluntaryExit, Slot, SyncCommitteeMessage,
4445
SyncContributionData,
4546
};
47+
use version::{fork_versioned_response, unsupported_version_rejection, V1};
4648
use warp::http::StatusCode;
4749
use warp::sse::Event;
4850
use warp::Reply;
4951
use warp::{http::Response, Filter};
5052
use warp_utils::task::{blocking_json_task, blocking_task};
5153

5254
const API_PREFIX: &str = "eth";
53-
const API_VERSION: &str = "v1";
5455

5556
/// If the node is within this many epochs from the head, we declare it to be synced regardless of
5657
/// the network sync state.
@@ -154,38 +155,38 @@ pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::lo
154155
// a block hash).
155156
let path = {
156157
let equals = |s: &'static str| -> Option<&'static str> {
157-
if info.path() == format!("/{}/{}/{}", API_PREFIX, API_VERSION, s) {
158+
if info.path() == format!("/{}/{}", API_PREFIX, s) {
158159
Some(s)
159160
} else {
160161
None
161162
}
162163
};
163164

164165
let starts_with = |s: &'static str| -> Option<&'static str> {
165-
if info
166-
.path()
167-
.starts_with(&format!("/{}/{}/{}", API_PREFIX, API_VERSION, s))
168-
{
166+
if info.path().starts_with(&format!("/{}/{}", API_PREFIX, s)) {
169167
Some(s)
170168
} else {
171169
None
172170
}
173171
};
174172

175-
equals("beacon/blocks")
176-
.or_else(|| starts_with("validator/duties/attester"))
177-
.or_else(|| starts_with("validator/duties/proposer"))
178-
.or_else(|| starts_with("validator/attestation_data"))
179-
.or_else(|| starts_with("validator/blocks"))
180-
.or_else(|| starts_with("validator/aggregate_attestation"))
181-
.or_else(|| starts_with("validator/aggregate_and_proofs"))
182-
.or_else(|| starts_with("validator/beacon_committee_subscriptions"))
183-
.or_else(|| starts_with("beacon/"))
184-
.or_else(|| starts_with("config/"))
185-
.or_else(|| starts_with("debug/"))
186-
.or_else(|| starts_with("events/"))
187-
.or_else(|| starts_with("node/"))
188-
.or_else(|| starts_with("validator/"))
173+
// First line covers `POST /v1/beacon/blocks` only
174+
equals("v1/beacon/blocks")
175+
.or_else(|| starts_with("v1/validator/duties/attester"))
176+
.or_else(|| starts_with("v1/validator/duties/proposer"))
177+
.or_else(|| starts_with("v1/validator/attestation_data"))
178+
.or_else(|| starts_with("v1/validator/blocks"))
179+
.or_else(|| starts_with("v2/validator/blocks"))
180+
.or_else(|| starts_with("v1/validator/aggregate_attestation"))
181+
.or_else(|| starts_with("v1/validator/aggregate_and_proofs"))
182+
.or_else(|| starts_with("v1/validator/beacon_committee_subscriptions"))
183+
.or_else(|| starts_with("v1/beacon/"))
184+
.or_else(|| starts_with("v2/beacon/"))
185+
.or_else(|| starts_with("v1/config/"))
186+
.or_else(|| starts_with("v1/debug/"))
187+
.or_else(|| starts_with("v1/events/"))
188+
.or_else(|| starts_with("v1/node/"))
189+
.or_else(|| starts_with("v1/validator/"))
189190
.unwrap_or("other")
190191
};
191192

@@ -241,7 +242,30 @@ pub fn serve<T: BeaconChainTypes>(
241242
));
242243
}
243244

244-
let eth1_v1 = warp::path(API_PREFIX).and(warp::path(API_VERSION));
245+
// Create a filter that extracts the endpoint version.
246+
let any_version = warp::path(API_PREFIX).and(warp::path::param::<EndpointVersion>().or_else(
247+
|_| async move {
248+
Err(warp_utils::reject::custom_bad_request(
249+
"Invalid version identifier".to_string(),
250+
))
251+
},
252+
));
253+
254+
// Filter that enforces a single endpoint version and then discards the `EndpointVersion`.
255+
let single_version = |reqd: EndpointVersion| {
256+
any_version
257+
.clone()
258+
.and_then(move |version| async move {
259+
if version == reqd {
260+
Ok(())
261+
} else {
262+
Err(unsupported_version_rejection(version))
263+
}
264+
})
265+
.untuple_one()
266+
};
267+
268+
let eth1_v1 = single_version(V1);
245269

246270
// Create a `warp` filter that provides access to the network globals.
247271
let inner_network_globals = ctx.network_globals.clone();
@@ -877,23 +901,32 @@ pub fn serve<T: BeaconChainTypes>(
877901
},
878902
);
879903

880-
let beacon_blocks_path = eth1_v1
904+
let block_id_or_err = warp::path::param::<BlockId>().or_else(|_| async {
905+
Err(warp_utils::reject::custom_bad_request(
906+
"Invalid block ID".to_string(),
907+
))
908+
});
909+
910+
let beacon_blocks_path_v1 = eth1_v1
881911
.and(warp::path("beacon"))
882912
.and(warp::path("blocks"))
883-
.and(warp::path::param::<BlockId>().or_else(|_| async {
884-
Err(warp_utils::reject::custom_bad_request(
885-
"Invalid block ID".to_string(),
886-
))
887-
}))
913+
.and(block_id_or_err.clone())
914+
.and(chain_filter.clone());
915+
916+
let beacon_blocks_path_any = any_version
917+
.and(warp::path("beacon"))
918+
.and(warp::path("blocks"))
919+
.and(block_id_or_err)
888920
.and(chain_filter.clone());
889921

890922
// GET beacon/blocks/{block_id}
891-
let get_beacon_block = beacon_blocks_path
923+
let get_beacon_block = beacon_blocks_path_any
892924
.clone()
893925
.and(warp::path::end())
894926
.and(warp::header::optional::<api_types::Accept>("accept"))
895927
.and_then(
896-
|block_id: BlockId,
928+
|endpoint_version: EndpointVersion,
929+
block_id: BlockId,
897930
chain: Arc<BeaconChain<T>>,
898931
accept_header: Option<api_types::Accept>| {
899932
blocking_task(move || {
@@ -909,17 +942,18 @@ pub fn serve<T: BeaconChainTypes>(
909942
e
910943
))
911944
}),
912-
_ => Ok(
913-
warp::reply::json(&api_types::GenericResponseRef::from(&block))
914-
.into_response(),
915-
),
945+
_ => {
946+
let fork_name = block.fork_name(&chain.spec).ok();
947+
fork_versioned_response(endpoint_version, fork_name, block)
948+
.map(|res| warp::reply::json(&res).into_response())
949+
}
916950
}
917951
})
918952
},
919953
);
920954

921955
// GET beacon/blocks/{block_id}/root
922-
let get_beacon_block_root = beacon_blocks_path
956+
let get_beacon_block_root = beacon_blocks_path_v1
923957
.clone()
924958
.and(warp::path("root"))
925959
.and(warp::path::end())
@@ -933,7 +967,7 @@ pub fn serve<T: BeaconChainTypes>(
933967
});
934968

935969
// GET beacon/blocks/{block_id}/attestations
936-
let get_beacon_block_attestations = beacon_blocks_path
970+
let get_beacon_block_attestations = beacon_blocks_path_v1
937971
.clone()
938972
.and(warp::path("attestations"))
939973
.and(warp::path::end())
@@ -1331,7 +1365,8 @@ pub fn serve<T: BeaconChainTypes>(
13311365
*/
13321366

13331367
// GET debug/beacon/states/{state_id}
1334-
let get_debug_beacon_states = eth1_v1
1368+
let get_debug_beacon_states = any_version
1369+
.clone()
13351370
.and(warp::path("debug"))
13361371
.and(warp::path("beacon"))
13371372
.and(warp::path("states"))
@@ -1344,7 +1379,8 @@ pub fn serve<T: BeaconChainTypes>(
13441379
.and(warp::header::optional::<api_types::Accept>("accept"))
13451380
.and(chain_filter.clone())
13461381
.and_then(
1347-
|state_id: StateId,
1382+
|endpoint_version: EndpointVersion,
1383+
state_id: StateId,
13481384
accept_header: Option<api_types::Accept>,
13491385
chain: Arc<BeaconChain<T>>| {
13501386
blocking_task(move || match accept_header {
@@ -1362,10 +1398,9 @@ pub fn serve<T: BeaconChainTypes>(
13621398
})
13631399
}
13641400
_ => state_id.map_state(&chain, |state| {
1365-
Ok(
1366-
warp::reply::json(&api_types::GenericResponseRef::from(&state))
1367-
.into_response(),
1368-
)
1401+
let fork_name = state.fork_name(&chain.spec).ok();
1402+
let res = fork_versioned_response(endpoint_version, fork_name, &state)?;
1403+
Ok(warp::reply::json(&res).into_response())
13691404
}),
13701405
})
13711406
},
@@ -1685,7 +1720,8 @@ pub fn serve<T: BeaconChainTypes>(
16851720
});
16861721

16871722
// GET validator/blocks/{slot}
1688-
let get_validator_blocks = eth1_v1
1723+
let get_validator_blocks = any_version
1724+
.clone()
16891725
.and(warp::path("validator"))
16901726
.and(warp::path("blocks"))
16911727
.and(warp::path::param::<Slot>().or_else(|_| async {
@@ -1698,7 +1734,10 @@ pub fn serve<T: BeaconChainTypes>(
16981734
.and(warp::query::<api_types::ValidatorBlocksQuery>())
16991735
.and(chain_filter.clone())
17001736
.and_then(
1701-
|slot: Slot, query: api_types::ValidatorBlocksQuery, chain: Arc<BeaconChain<T>>| {
1737+
|endpoint_version: EndpointVersion,
1738+
slot: Slot,
1739+
query: api_types::ValidatorBlocksQuery,
1740+
chain: Arc<BeaconChain<T>>| {
17021741
blocking_json_task(move || {
17031742
let randao_reveal = (&query.randao_reveal).try_into().map_err(|e| {
17041743
warp_utils::reject::custom_bad_request(format!(
@@ -1707,11 +1746,11 @@ pub fn serve<T: BeaconChainTypes>(
17071746
))
17081747
})?;
17091748

1710-
chain
1749+
let (block, _) = chain
17111750
.produce_block(randao_reveal, slot, query.graffiti.map(Into::into))
1712-
.map(|block_and_state| block_and_state.0)
1713-
.map(api_types::GenericResponse::from)
1714-
.map_err(warp_utils::reject::block_production_error)
1751+
.map_err(warp_utils::reject::block_production_error)?;
1752+
let fork_name = block.to_ref().fork_name(&chain.spec).ok();
1753+
fork_versioned_response(endpoint_version, fork_name, block)
17151754
})
17161755
},
17171756
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::api_types::{EndpointVersion, ForkVersionedResponse};
2+
use serde::Serialize;
3+
use types::ForkName;
4+
5+
pub const V1: EndpointVersion = EndpointVersion(1);
6+
pub const V2: EndpointVersion = EndpointVersion(2);
7+
8+
pub fn fork_versioned_response<T: Serialize>(
9+
endpoint_version: EndpointVersion,
10+
fork_name: Option<ForkName>,
11+
data: T,
12+
) -> Result<ForkVersionedResponse<T>, warp::reject::Rejection> {
13+
let fork_name = if endpoint_version == V1 {
14+
None
15+
} else if endpoint_version == V2 {
16+
fork_name
17+
} else {
18+
return Err(unsupported_version_rejection(endpoint_version));
19+
};
20+
Ok(ForkVersionedResponse {
21+
version: fork_name,
22+
data,
23+
})
24+
}
25+
26+
pub fn unsupported_version_rejection(version: EndpointVersion) -> warp::reject::Rejection {
27+
warp_utils::reject::custom_bad_request(format!("Unsupported endpoint version: {}", version))
28+
}

beacon_node/http_api/tests/tests.rs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -921,20 +921,35 @@ impl ApiTester {
921921
}
922922
}
923923

924-
let json_result = self
925-
.client
926-
.get_beacon_blocks(block_id)
927-
.await
928-
.unwrap()
929-
.map(|res| res.data);
930-
assert_eq!(json_result, expected, "{:?}", block_id);
924+
let json_result = self.client.get_beacon_blocks(block_id).await.unwrap();
925+
926+
if let (Some(json), Some(expected)) = (&json_result, &expected) {
927+
assert_eq!(json.data, *expected, "{:?}", block_id);
928+
assert_eq!(
929+
json.version,
930+
Some(expected.fork_name(&self.chain.spec).unwrap())
931+
);
932+
} else {
933+
assert_eq!(json_result, None);
934+
assert_eq!(expected, None);
935+
}
931936

932937
let ssz_result = self
933938
.client
934939
.get_beacon_blocks_ssz(block_id, &self.chain.spec)
935940
.await
936941
.unwrap();
937942
assert_eq!(ssz_result, expected, "{:?}", block_id);
943+
944+
// Check that the legacy v1 API still works but doesn't return a version field.
945+
let v1_result = self.client.get_beacon_blocks_v1(block_id).await.unwrap();
946+
if let (Some(v1_result), Some(expected)) = (&v1_result, &expected) {
947+
assert_eq!(v1_result.version, None);
948+
assert_eq!(v1_result.data, *expected);
949+
} else {
950+
assert_eq!(v1_result, None);
951+
assert_eq!(expected, None);
952+
}
938953
}
939954

940955
self
@@ -1353,23 +1368,44 @@ impl ApiTester {
13531368

13541369
pub async fn test_get_debug_beacon_states(self) -> Self {
13551370
for state_id in self.interesting_state_ids() {
1371+
let result_json = self.client.get_debug_beacon_states(state_id).await.unwrap();
1372+
1373+
let mut expected = self.get_state(state_id);
1374+
expected.as_mut().map(|state| state.drop_all_caches());
1375+
1376+
if let (Some(json), Some(expected)) = (&result_json, &expected) {
1377+
assert_eq!(json.data, *expected, "{:?}", state_id);
1378+
assert_eq!(
1379+
json.version,
1380+
Some(expected.fork_name(&self.chain.spec).unwrap())
1381+
);
1382+
} else {
1383+
assert_eq!(result_json, None);
1384+
assert_eq!(expected, None);
1385+
}
1386+
1387+
// Check SSZ API.
13561388
let result_ssz = self
13571389
.client
13581390
.get_debug_beacon_states_ssz(state_id, &self.chain.spec)
13591391
.await
13601392
.unwrap();
1361-
let result_json = self
1393+
assert_eq!(result_ssz, expected, "{:?}", state_id);
1394+
1395+
// Check legacy v1 API.
1396+
let result_v1 = self
13621397
.client
1363-
.get_debug_beacon_states(state_id)
1398+
.get_debug_beacon_states_v1(state_id)
13641399
.await
1365-
.unwrap()
1366-
.map(|res| res.data);
1367-
1368-
let mut expected = self.get_state(state_id);
1369-
expected.as_mut().map(|state| state.drop_all_caches());
1400+
.unwrap();
13701401

1371-
assert_eq!(result_ssz, expected, "{:?}", state_id);
1372-
assert_eq!(result_json, expected, "{:?}", state_id);
1402+
if let (Some(json), Some(expected)) = (&result_v1, &expected) {
1403+
assert_eq!(json.version, None);
1404+
assert_eq!(json.data, *expected, "{:?}", state_id);
1405+
} else {
1406+
assert_eq!(result_v1, None);
1407+
assert_eq!(expected, None);
1408+
}
13731409
}
13741410

13751411
self

0 commit comments

Comments
 (0)