Skip to content

Commit

Permalink
Add headers endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
paulhauner committed Sep 5, 2020
1 parent 6994cd3 commit 512e53b
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 19 deletions.
34 changes: 28 additions & 6 deletions beacon_node/http_api/src/block_id.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::types::BlockId as CoreBlockId;
use std::str::FromStr;
use types::{Hash256, SignedBeaconBlock};
use types::{Hash256, SignedBeaconBlock, Slot};

#[derive(Debug)]
pub struct BlockId(pub CoreBlockId);

impl BlockId {
pub fn from_slot(slot: Slot) -> Self {
Self(CoreBlockId::Slot(slot))
}

pub fn from_root(root: Hash256) -> Self {
Self(CoreBlockId::Root(root))
}

pub fn root<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
Expand All @@ -28,7 +36,11 @@ impl BlockId {
CoreBlockId::Slot(slot) => chain
.block_root_at_slot(*slot)
.map_err(crate::reject::beacon_chain_error)
.and_then(|root_opt| root_opt.ok_or_else(|| warp::reject::not_found())),
.and_then(|root_opt| {
root_opt.ok_or_else(|| {
crate::reject::custom_not_found(format!("beacon block at slot {}", slot))
})
}),
CoreBlockId::Root(root) => Ok(*root),
}
}
Expand All @@ -41,10 +53,20 @@ impl BlockId {
CoreBlockId::Head => chain
.head_beacon_block()
.map_err(crate::reject::beacon_chain_error),
_ => chain
.get_block(&self.root(chain)?)
.map_err(crate::reject::beacon_chain_error)
.and_then(|root_opt| root_opt.ok_or_else(|| warp::reject::not_found())),
_ => {
let root = self.root(chain)?;
chain
.get_block(&root)
.map_err(crate::reject::beacon_chain_error)
.and_then(|root_opt| {
root_opt.ok_or_else(|| {
crate::reject::custom_not_found(format!(
"beacon block with root {}",
root
))
})
})
}
}
}
}
Expand Down
92 changes: 80 additions & 12 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ mod state_id;
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
use block_id::BlockId;
use eth2::types::{self as api_types, ValidatorId};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use state_id::StateId;
use std::borrow::Cow;
use std::future::Future;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::oneshot;
use types::{CommitteeCache, Epoch, EthSpec, RelativeEpoch, Slot};
use types::{CommitteeCache, Epoch, EthSpec, RelativeEpoch};
use warp::Filter;

const API_PREFIX: &str = "eth";
Expand Down Expand Up @@ -194,24 +194,18 @@ pub fn serve<T: BeaconChainTypes>(
},
);

#[derive(Serialize, Deserialize)]
struct CommitteesQuery {
slot: Option<Slot>,
index: Option<u64>,
}

// beacon/states/{state_id}/committees/{epoch}
let beacon_state_committees = beacon_states_path
.clone()
.and(warp::path("committees"))
.and(warp::path::param::<Epoch>())
.and(warp::query::<CommitteesQuery>())
.and(warp::query::<api_types::CommitteesQuery>())
.and(warp::path::end())
.and_then(
|state_id: StateId,
chain: Arc<BeaconChain<T>>,
epoch: Epoch,
query: CommitteesQuery| {
query: api_types::CommitteesQuery| {
blocking_json_task(move || {
state_id.map_state(&chain, |state| {
let relative_epoch =
Expand Down Expand Up @@ -284,8 +278,81 @@ pub fn serve<T: BeaconChainTypes>(
},
);

// beacon/headers
//
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
// there's a `canonical` flag on the response, I assume it should also return non-canonical
// things. Returning non-canonical things is hard for us since we don't already have a
// mechanism for arbitrary forwards block iteration, we only support iterating forwards along
// the canonical chain.
let beacon_headers = base_path
.and(warp::path("beacon"))
.and(warp::path("headers"))
.and(warp::query::<api_types::HeadersQuery>())
.and(chain_filter.clone())
.and_then(
|query: api_types::HeadersQuery, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || {
let (root, block) = match (query.slot, query.parent_root) {
(None, None) => chain
.head_beacon_block()
.map_err(crate::reject::beacon_chain_error)
.map(|block| (block.canonical_root(), block))?,
(None, Some(parent_root)) => {
let parent = BlockId::from_root(parent_root).block(&chain)?;
let root = chain
.forwards_iter_block_roots(parent.slot())
.map_err(crate::reject::beacon_chain_error)?
.next()
.transpose()
.map_err(crate::reject::beacon_chain_error)?
.map(|(root, _)| root)
.ok_or_else(|| {
crate::reject::custom_not_found(format!(
"child of block with root {}",
parent_root
))
})?;

BlockId::from_root(root)
.block(&chain)
.map(|block| (root, block))?
}
(Some(slot), parent_root_opt) => {
let root = BlockId::from_slot(slot).root(&chain)?;
let block = BlockId::from_root(root).block(&chain)?;

// If the parent root was supplied, check that it matches the block
// obtained via a slot lookup.
if let Some(parent_root) = parent_root_opt {
if block.parent_root() != parent_root {
return Err(crate::reject::custom_not_found(format!(
"no canonical block at slot {} with parent root {}",
slot, parent_root
)));
}
}

(root, block)
}
};

let data = api_types::BlockHeaderData {
root,
canonical: true,
header: api_types::BlockHeaderAndSignature {
message: block.message.block_header(),
signature: block.signature.into(),
},
};

Ok(api_types::GenericResponse::from(vec![data]))
})
},
);

/*
* beacon/blocks
* beacon/blocks/{block_id}
*/

let beacon_blocks_path = base_path
Expand Down Expand Up @@ -314,8 +381,9 @@ pub fn serve<T: BeaconChainTypes>(
.or(beacon_state_finality_checkpoints)
.or(beacon_state_validators)
.or(beacon_state_validators_id)
.or(beacon_block_root)
.or(beacon_state_committees)
.or(beacon_headers)
.or(beacon_block_root)
.recover(crate::reject::handle_rejection);

let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
Expand Down
40 changes: 40 additions & 0 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,41 @@ impl ApiTester {
}
}

pub async fn test_beacon_headers_all_slots(self) -> Self {
for slot in 0..CHAIN_LENGTH {
let slot = Slot::from(slot);

let result = self
.client
.beacon_headers(Some(slot), None)
.await
.unwrap()
.map(|res| res.data);

let root = self.chain.block_root_at_slot(slot).unwrap();

if root.is_none() && result.is_none() {
continue;
}

let root = root.unwrap();
let block = self.chain.block_at_slot(slot).unwrap().unwrap();
let header = BlockHeaderData {
root,
canonical: true,
header: BlockHeaderAndSignature {
message: block.message.block_header(),
signature: block.signature.into(),
},
};
let expected = vec![header];

assert_eq!(result.unwrap(), expected, "slot {:?}", slot);
}

self
}

pub async fn test_beacon_blocks_root(self) -> Self {
for block_id in self.interesting_block_ids() {
let result = self
Expand Down Expand Up @@ -504,6 +539,11 @@ async fn beacon_states_validator_id() {
ApiTester::new().test_beacon_states_validator_id().await;
}

#[tokio::test(core_threads = 2)]
async fn beacon_headers() {
ApiTester::new().test_beacon_headers_all_slots().await;
}

#[tokio::test(core_threads = 2)]
async fn beacon_blocks_root() {
ApiTester::new().test_beacon_blocks_root().await;
Expand Down
28 changes: 28 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,34 @@ impl BeaconNodeClient {
self.get_opt(path).await
}

/// `GET beacon/headers?slot,parent_root`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn beacon_headers(
&self,
slot: Option<Slot>,
parent_root: Option<u64>,
) -> Result<Option<GenericResponse<Vec<BlockHeaderData>>>, Error> {
let mut path = self.server.clone();

path.path_segments_mut()
.expect("path is base")
.push("beacon")
.push("headers");

if let Some(slot) = slot {
path.query_pairs_mut()
.append_pair("slot", &slot.to_string());
}

if let Some(root) = parent_root {
path.query_pairs_mut()
.append_pair("parent_root", &root.to_string());
}

self.get_opt(path).await
}

/// `GET beacon/blocks/{block_id}/root`
///
/// Returns `Ok(None)` on a 404 error.
Expand Down
30 changes: 29 additions & 1 deletion common/eth2/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use std::fmt;
use std::str::FromStr;
use types::serde_utils;

pub use types::{Checkpoint, Epoch, Fork, Hash256, PublicKeyBytes, Slot, Validator};
pub use types::{
BeaconBlockHeader, Checkpoint, Epoch, Fork, Hash256, PublicKeyBytes, SignatureBytes, Slot,
Validator,
};

/// The number of epochs between when a validator is eligible for activation and when they
/// *usually* enter the activation queue.
Expand Down Expand Up @@ -238,6 +241,12 @@ impl ValidatorStatus {
}
}

#[derive(Serialize, Deserialize)]
pub struct CommitteesQuery {
pub slot: Option<Slot>,
pub index: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CommitteeData {
#[serde(with = "serde_utils::quoted")]
Expand All @@ -246,3 +255,22 @@ pub struct CommitteeData {
#[serde(with = "serde_utils::quoted_u64_vec")]
pub validators: Vec<u64>,
}

#[derive(Serialize, Deserialize)]
pub struct HeadersQuery {
pub slot: Option<Slot>,
pub parent_root: Option<Hash256>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockHeaderAndSignature {
pub message: BeaconBlockHeader,
pub signature: SignatureBytes,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockHeaderData {
pub root: Hash256,
pub canonical: bool,
pub header: BlockHeaderAndSignature,
}
1 change: 1 addition & 0 deletions consensus/types/src/beacon_block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tree_hash_derive::TreeHash;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct BeaconBlockHeader {
pub slot: Slot,
#[serde(with = "crate::serde_utils::quoted")]
pub proposer_index: u64,
pub parent_root: Hash256,
pub state_root: Hash256,
Expand Down

0 comments on commit 512e53b

Please sign in to comment.