Add initial (failing) REST api tests

This commit is contained in:
Paul Hauner
2019-11-12 18:11:05 +11:00
parent 41805611d0
commit 7b5a868f4a
8 changed files with 304 additions and 38 deletions

View File

@@ -175,7 +175,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
) -> Result<Vec<BeaconBlockBody<T::EthSpec>>, Error> {
let bodies: Result<Vec<_>, _> = roots
.iter()
.map(|root| match self.get_block(root)? {
.map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.body),
None => Err(Error::DBInconsistent(format!("Missing block: {}", root))),
})
@@ -190,7 +190,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn get_block_headers(&self, roots: &[Hash256]) -> Result<Vec<BeaconBlockHeader>, Error> {
let headers: Result<Vec<BeaconBlockHeader>, _> = roots
.iter()
.map(|root| match self.get_block(root)? {
.map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.block_header()),
None => Err(Error::DBInconsistent("Missing block".into())),
})
@@ -241,11 +241,29 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ## Errors
///
/// May return a database error.
pub fn get_block(
pub fn block_at_root(
&self,
block_root: &Hash256,
block_root: Hash256,
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
Ok(self.store.get(block_root)?)
Ok(self.store.get(&block_root)?)
}
/// Returns the block at the given slot, if any. Only returns blocks in the canonical chain.
///
/// ## Errors
///
/// May return a database error.
pub fn block_at_slot(&self, slot: Slot) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
let root = self
.rev_iter_block_roots()
.find(|(_, this_slot)| *this_slot == slot)
.map(|(root, _)| root);
if let Some(block_root) = root {
Ok(self.store.get(&block_root)?)
} else {
Ok(None)
}
}
/// Returns a `Checkpoint` representing the head block and state. Contains the "best block";

View File

@@ -24,7 +24,6 @@ state_processing = { path = "../../eth2/state_processing" }
types = { path = "../../eth2/types" }
clap = "2.32.0"
http = "^0.1.17"
prometheus = { version = "^0.6", features = ["process"] }
hyper = "0.12.35"
exit-future = "0.1.3"
tokio = "0.1.17"
@@ -36,3 +35,8 @@ slot_clock = { path = "../../eth2/utils/slot_clock" }
hex = "0.3.2"
parking_lot = "0.9"
futures = "0.1.25"
[dev-dependencies]
remote_beacon_node = { path = "../../eth2/utils/remote_beacon_node" }
node_test_rig = { path = "../../tests/node_test_rig" }
tree_hash = { path = "../../eth2/utils/tree_hash" }

View File

@@ -27,7 +27,8 @@ use hyper::rt::Future;
use hyper::service::Service;
use hyper::{Body, Method, Request, Response, Server};
use parking_lot::RwLock;
use slog::{info, o, warn};
use slog::{info, warn};
use std::net::SocketAddr;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
@@ -37,7 +38,7 @@ use url_query::UrlQuery;
pub use crate::helpers::parse_pubkey;
pub use beacon::{BlockResponse, HeadResponse, StateResponse};
pub use config::Config as ApiConfig;
pub use config::Config;
pub use validator::ValidatorDuty;
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
@@ -137,19 +138,17 @@ impl<T: BeaconChainTypes> Service for ApiService<T> {
}
// Methods for Validator
(&Method::GET, "/beacon/validator/duties") => {
(&Method::GET, "/validator/duties") => {
into_boxfut(validator::get_validator_duties::<T>(req))
}
(&Method::GET, "/beacon/validator/block") => {
(&Method::GET, "/validator/block") => {
into_boxfut(validator::get_new_beacon_block::<T>(req))
}
(&Method::POST, "/beacon/validator/block") => validator::publish_beacon_block::<T>(req),
(&Method::GET, "/beacon/validator/attestation") => {
(&Method::POST, "/validator/block") => validator::publish_beacon_block::<T>(req),
(&Method::GET, "/validator/attestation") => {
into_boxfut(validator::get_new_attestation::<T>(req))
}
(&Method::POST, "/beacon/validator/attestation") => {
validator::publish_attestation::<T>(req)
}
(&Method::POST, "/validator/attestation") => validator::publish_attestation::<T>(req),
(&Method::GET, "/beacon/state") => into_boxfut(beacon::get_state::<T>(req)),
(&Method::GET, "/beacon/state_root") => into_boxfut(beacon::get_state_root::<T>(req)),
@@ -199,16 +198,14 @@ impl<T: BeaconChainTypes> Service for ApiService<T> {
}
pub fn start_server<T: BeaconChainTypes>(
config: &ApiConfig,
config: &Config,
executor: &TaskExecutor,
beacon_chain: Arc<BeaconChain<T>>,
network_info: NetworkInfo<T>,
db_path: PathBuf,
eth2_config: Eth2Config,
log: &slog::Logger,
) -> Result<exit_future::Signal, hyper::Error> {
let log = log.new(o!("Service" => "Api"));
log: slog::Logger,
) -> Result<(exit_future::Signal, SocketAddr), hyper::Error> {
// build a channel to kill the HTTP server
let (exit_signal, exit) = exit_future::signal();
@@ -240,8 +237,11 @@ pub fn start_server<T: BeaconChainTypes>(
};
let log_clone = log.clone();
let server = Server::bind(&bind_addr)
.serve(service)
let server = Server::bind(&bind_addr).serve(service);
let actual_listen_addr = server.local_addr();
let server_future = server
.with_graceful_shutdown(server_exit)
.map_err(move |e| {
warn!(
@@ -251,15 +251,15 @@ pub fn start_server<T: BeaconChainTypes>(
});
info!(
log,
"REST API started";
"address" => format!("{}", config.listen_address),
"port" => config.port,
log,
"REST API started";
"address" => format!("{}", actual_listen_addr.ip()),
"port" => actual_listen_addr.port(),
);
executor.spawn(server);
executor.spawn(server_future);
Ok(exit_signal)
Ok((exit_signal, actual_listen_addr))
}
#[derive(Clone)]

View File

@@ -3,7 +3,7 @@ use crate::response_builder::ResponseBuilder;
use crate::{ApiError, ApiResult, DBPath};
use beacon_chain::BeaconChainTypes;
use hyper::{Body, Request};
use prometheus::{Encoder, TextEncoder};
use lighthouse_metrics::{Encoder, TextEncoder};
pub use lighthouse_metrics::*;
@@ -58,7 +58,7 @@ pub fn get_prometheus<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
beacon_chain::scrape_for_metrics(&beacon_chain);
encoder
.encode(&lighthouse_metrics::gather(), &mut buffer)
.encode(&lighthouse_metrics::gather()[..], &mut buffer)
.unwrap();
String::from_utf8(buffer)

View File

@@ -0,0 +1,161 @@
#![cfg(test)]
use node_test_rig::{
environment::{Environment, EnvironmentBuilder},
LocalBeaconNode,
};
use tree_hash::TreeHash;
use types::{
test_utils::generate_deterministic_keypair, Domain, EthSpec, MinimalEthSpec, Signature, Slot,
};
type E = MinimalEthSpec;
fn build_env() -> Environment<E> {
EnvironmentBuilder::minimal()
.null_logger()
.expect("should build env logger")
.single_thread_tokio_runtime()
.expect("should start tokio runtime")
.build()
.expect("environment should build")
}
#[test]
fn validator_block() {
let mut env = build_env();
let spec = E::default_spec();
let node = LocalBeaconNode::production(env.core_context());
let remote_node = node.remote_node().expect("should produce remote node");
let beacon_chain = node
.client
.beacon_chain()
.expect("client should have beacon chain");
let fork = beacon_chain.head().beacon_state.fork.clone();
let slot = Slot::new(1);
let randao_reveal = {
let proposer_index = beacon_chain
.block_proposer(slot)
.expect("should get proposer index");
let keypair = generate_deterministic_keypair(proposer_index);
let epoch = slot.epoch(E::slots_per_epoch());
let message = epoch.tree_hash_root();
let domain = spec.get_domain(epoch, Domain::Randao, &fork);
Signature::new(&message, domain, &keypair.sk)
};
let block = env
.runtime()
.block_on(
remote_node
.http
.validator()
.block(slot, randao_reveal.clone()),
)
.expect("should fetch block from http api");
let (expected_block, _state) = node
.client
.beacon_chain()
.expect("client should have beacon chain")
.produce_block(randao_reveal, slot)
.expect("should produce block");
assert_eq!(
block, expected_block,
"the block returned from the API should be as expected"
);
}
#[test]
fn beacon_state() {
let mut env = build_env();
let node = LocalBeaconNode::production(env.core_context());
let remote_node = node.remote_node().expect("should produce remote node");
let (state_by_slot, root) = env
.runtime()
.block_on(remote_node.http.beacon().state_by_slot(Slot::new(0)))
.expect("should fetch state from http api");
let (state_by_root, root_2) = env
.runtime()
.block_on(remote_node.http.beacon().state_by_root(root))
.expect("should fetch state from http api");
let mut db_state = node
.client
.beacon_chain()
.expect("client should have beacon chain")
.state_at_slot(Slot::new(0))
.expect("should find state");
db_state.drop_all_caches();
assert_eq!(
root, root_2,
"the two roots returned from the api should be identical"
);
assert_eq!(
root,
db_state.canonical_root(),
"root from database should match that from the API"
);
assert_eq!(
state_by_slot, db_state,
"genesis state by slot from api should match that from the DB"
);
assert_eq!(
state_by_root, db_state,
"genesis state by root from api should match that from the DB"
);
}
#[test]
fn beacon_block() {
let mut env = build_env();
let node = LocalBeaconNode::production(env.core_context());
let remote_node = node.remote_node().expect("should produce remote node");
let (block_by_slot, root) = env
.runtime()
.block_on(remote_node.http.beacon().block_by_slot(Slot::new(0)))
.expect("should fetch block from http api");
let (block_by_root, root_2) = env
.runtime()
.block_on(remote_node.http.beacon().block_by_root(root))
.expect("should fetch block from http api");
let db_block = node
.client
.beacon_chain()
.expect("client should have beacon chain")
.block_at_slot(Slot::new(0))
.expect("should find block")
.expect("block should not be none");
assert_eq!(
root, root_2,
"the two roots returned from the api should be identical"
);
assert_eq!(
root,
db_block.canonical_root(),
"root from database should match that from the API"
);
assert_eq!(
block_by_slot, db_block,
"genesis block by slot from api should match that from the DB"
);
assert_eq!(
block_by_root, db_block,
"genesis block by root from api should match that from the DB"
);
}