From 501a2559d74317732521bcd229e06ac5d3e892eb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Nov 2019 11:51:28 +1100 Subject: [PATCH] Add working tests for publish beacon block --- beacon_node/rest_api/src/beacon.rs | 4 +- beacon_node/rest_api/tests/test.rs | 69 ++++++++--- eth2/utils/remote_beacon_node/src/lib.rs | 142 +++++++++++++++-------- 3 files changed, 149 insertions(+), 66 deletions(-) diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 908fc2a573..1857336601 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -3,13 +3,13 @@ use crate::response_builder::ResponseBuilder; use crate::{ApiError, ApiResult, UrlQuery}; use beacon_chain::{BeaconChain, BeaconChainTypes}; use hyper::{Body, Request}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use ssz_derive::Encode; use std::sync::Arc; use store::Store; use types::{BeaconBlock, BeaconState, Epoch, EthSpec, Hash256, Slot, Validator}; -#[derive(Serialize, Encode)] +#[derive(Serialize, Deserialize, Encode)] pub struct HeadResponse { pub slot: Slot, pub block_root: Hash256, diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index c833827543..a0b96c2e38 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -1,16 +1,16 @@ #![cfg(test)] use beacon_chain::{BeaconChain, BeaconChainTypes}; -use futures::Future; use node_test_rig::{ environment::{Environment, EnvironmentBuilder}, LocalBeaconNode, }; +use remote_beacon_node::BeaconBlockPublishStatus; use std::sync::Arc; -use tree_hash::TreeHash; +use tree_hash::{SignedRoot, TreeHash}; use types::{ - test_utils::generate_deterministic_keypair, ChainSpec, Domain, EthSpec, MinimalEthSpec, - Signature, Slot, + test_utils::generate_deterministic_keypair, BeaconBlock, ChainSpec, Domain, EthSpec, + MinimalEthSpec, Signature, Slot, }; type E = MinimalEthSpec; @@ -43,7 +43,23 @@ fn get_randao_reveal( Signature::new(&message, domain, &keypair.sk) } -/* +/// Signs the given block (assuming the given `beacon_chain` uses deterministic keypairs). +fn sign_block( + beacon_chain: Arc>, + block: &mut BeaconBlock, + spec: &ChainSpec, +) { + let fork = beacon_chain.head().beacon_state.fork.clone(); + let proposer_index = beacon_chain + .block_proposer(block.slot) + .expect("should get proposer index"); + let keypair = generate_deterministic_keypair(proposer_index); + let epoch = block.slot.epoch(E::slots_per_epoch()); + let message = block.signed_root(); + let domain = spec.get_domain(epoch, Domain::BeaconProposer, &fork); + block.signature = Signature::new(&message, domain, &keypair.sk); +} + #[test] fn validator_block_post() { let mut env = build_env(); @@ -61,7 +77,7 @@ fn validator_block_post() { let slot = Slot::new(1); let randao_reveal = get_randao_reveal(beacon_chain.clone(), slot, spec); - let block = env + let mut block = env .runtime() .block_on( remote_node @@ -71,18 +87,39 @@ fn validator_block_post() { ) .expect("should fetch block from http api"); - assert!(env + // Try publishing the block without a signature, ensure it is flagged as invalid. + let publish_status = env .runtime() - .block_on( - remote_node - .http - .validator() - .publish_block(block) - .and_then(|_| Ok(())) - ) - .is_ok()); + .block_on(remote_node.http.validator().publish_block(block.clone())) + .expect("should publish block"); + assert!( + !publish_status.is_valid(), + "the unsigned published block should not be valid" + ); + + sign_block(beacon_chain.clone(), &mut block, spec); + let block_root = block.canonical_root(); + + let publish_status = env + .runtime() + .block_on(remote_node.http.validator().publish_block(block.clone())) + .expect("should publish block"); + assert_eq!( + publish_status, + BeaconBlockPublishStatus::Valid, + "the signed published block should be valid" + ); + + let head = env + .runtime() + .block_on(remote_node.http.beacon().get_head()) + .expect("should get head"); + + assert_eq!( + head.block_root, block_root, + "the published block should become the head block" + ); } -*/ #[test] fn validator_block_get() { diff --git a/eth2/utils/remote_beacon_node/src/lib.rs b/eth2/utils/remote_beacon_node/src/lib.rs index cc21899cf3..6559f30f49 100644 --- a/eth2/utils/remote_beacon_node/src/lib.rs +++ b/eth2/utils/remote_beacon_node/src/lib.rs @@ -4,8 +4,11 @@ //! Presently, this is only used for testing but it _could_ become a user-facing library. use futures::{Future, IntoFuture}; -use reqwest::r#async::{Client, ClientBuilder, RequestBuilder}; -use serde::Deserialize; +use reqwest::{ + r#async::{Client, ClientBuilder, Response}, + StatusCode, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use ssz::Encode; use std::marker::PhantomData; use std::net::SocketAddr; @@ -14,6 +17,8 @@ use types::{BeaconBlock, BeaconState, EthSpec, Signature}; use types::{Hash256, Slot}; use url::Url; +pub use rest_api::HeadResponse; + pub const REQUEST_TIMEOUT_SECONDS: u64 = 5; /// Connects to a remote Lighthouse (or compatible) node via HTTP. @@ -72,16 +77,51 @@ impl HttpClient { self.url.join(path).map_err(|e| e.into()) } - pub fn get(&self, path: &str) -> Result { - // TODO: add timeout - self.url(path) - .map(|url| Client::new().get(&url.to_string())) + pub fn json_post( + &self, + url: Url, + body: T, + ) -> impl Future { + self.client + .post(&url.to_string()) + .json(&body) + .send() + .map_err(Error::from) } - pub fn post(&self, path: &str) -> Result { - // TODO: add timeout - self.url(path) - .map(|url| Client::new().post(&url.to_string())) + pub fn json_get( + &self, + mut url: Url, + query_pairs: Vec<(String, String)>, + ) -> impl Future { + query_pairs.into_iter().for_each(|(key, param)| { + url.query_pairs_mut().append_pair(&key, ¶m); + }); + + self.client + .get(&url.to_string()) + .send() + .map_err(Error::from) + .and_then(|response| response.error_for_status().map_err(Error::from)) + .and_then(|mut success| success.json::().map_err(Error::from)) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum BeaconBlockPublishStatus { + /// The block was valid and has been published to the network. + Valid, + /// The block was not valid and may or may not have been published to the network. + Invalid(String), + /// The server responsed with an unknown status code. The block may or may not have been + /// published to the network. + Unknown, +} + +impl BeaconBlockPublishStatus { + /// Returns `true` if `*self == BeaconBlockPublishStatus::Valid`. + pub fn is_valid(&self) -> bool { + *self == BeaconBlockPublishStatus::Valid } } @@ -98,18 +138,28 @@ impl Validator { } /// Posts a block to the beacon node, expecting it to verify it and publish it to the network. - pub fn publish_block(&self, block: BeaconBlock) -> impl Future { + pub fn publish_block( + &self, + block: BeaconBlock, + ) -> impl Future { let client = self.0.clone(); self.url("block") .into_future() - .and_then(move |url| client.post(&url.to_string())) - .and_then(move |builder| { - builder - .json(&block) - .send() - .map_err(|e| Error::ReqwestError(e)) + .and_then(move |url| client.json_post::<_>(url, block)) + .and_then(|mut response| { + response + .text() + .map(|text| (response, text)) + .map_err(Error::from) + }) + .and_then(|(response, text)| match response.status() { + StatusCode::OK => Ok(BeaconBlockPublishStatus::Valid), + StatusCode::ACCEPTED => Ok(BeaconBlockPublishStatus::Invalid(text)), + _ => response + .error_for_status() + .map_err(Error::from) + .map(|_| BeaconBlockPublishStatus::Unknown), }) - .map(|_response| ()) } /// Requests a new (unsigned) block from the beacon node. @@ -119,18 +169,15 @@ impl Validator { randao_reveal: Signature, ) -> impl Future, Error = Error> { let client = self.0.clone(); - self.url("block") - .into_future() - .and_then(move |mut url| { - url.query_pairs_mut() - .append_pair("slot", &format!("{}", slot.as_u64())); - url.query_pairs_mut() - .append_pair("randao_reveal", &signature_as_string(&randao_reveal)); - client.get(&url.to_string()) - }) - .and_then(|builder| builder.send().map_err(Error::from)) - .and_then(|response| response.error_for_status().map_err(Error::from)) - .and_then(|mut success| success.json::>().map_err(Error::from)) + self.url("block").into_future().and_then(move |url| { + client.json_get::>( + url, + vec![ + ("slot".into(), format!("{}", slot.as_u64())), + ("randao_reveal".into(), signature_as_string(&randao_reveal)), + ], + ) + }) } } @@ -146,12 +193,19 @@ impl Beacon { .map_err(Into::into) } + pub fn get_head(&self) -> impl Future { + let client = self.0.clone(); + self.url("head") + .into_future() + .and_then(move |url| client.json_get::(url, vec![])) + } + /// Returns the block and block root at the given slot. pub fn get_block_by_slot( &self, slot: Slot, ) -> impl Future, Hash256), Error = Error> { - self.get_block("slot", format!("{}", slot.as_u64())) + self.get_block("slot".to_string(), format!("{}", slot.as_u64())) } /// Returns the block and block root at the given root. @@ -159,25 +213,21 @@ impl Beacon { &self, root: Hash256, ) -> impl Future, Hash256), Error = Error> { - self.get_block("root", root_as_string(root)) + self.get_block("root".to_string(), root_as_string(root)) } /// Returns the block and block root at the given slot. fn get_block( &self, - query_key: &'static str, + query_key: String, query_param: String, ) -> impl Future, Hash256), Error = Error> { let client = self.0.clone(); self.url("block") .into_future() - .and_then(move |mut url| { - url.query_pairs_mut().append_pair(query_key, &query_param); - client.get(&url.to_string()) + .and_then(move |url| { + client.json_get::>(url, vec![(query_key, query_param)]) }) - .and_then(|builder| builder.send().map_err(Error::from)) - .and_then(|response| response.error_for_status().map_err(Error::from)) - .and_then(|mut success| success.json::>().map_err(Error::from)) .map(|response| (response.beacon_block, response.root)) } @@ -186,7 +236,7 @@ impl Beacon { &self, slot: Slot, ) -> impl Future, Hash256), Error = Error> { - self.get_state("slot", format!("{}", slot.as_u64())) + self.get_state("slot".to_string(), format!("{}", slot.as_u64())) } /// Returns the state and state root at the given root. @@ -194,25 +244,21 @@ impl Beacon { &self, root: Hash256, ) -> impl Future, Hash256), Error = Error> { - self.get_state("root", root_as_string(root)) + self.get_state("root".to_string(), root_as_string(root)) } /// Returns the state and state root at the given slot. fn get_state( &self, - query_key: &'static str, + query_key: String, query_param: String, ) -> impl Future, Hash256), Error = Error> { let client = self.0.clone(); self.url("state") .into_future() - .and_then(move |mut url| { - url.query_pairs_mut().append_pair(query_key, &query_param); - client.get(&url.to_string()) + .and_then(move |url| { + client.json_get::>(url, vec![(query_key, query_param)]) }) - .and_then(|builder| builder.send().map_err(Error::from)) - .and_then(|response| response.error_for_status().map_err(Error::from)) - .and_then(|mut success| success.json::>().map_err(Error::from)) .map(|response| (response.beacon_state, response.root)) } }