mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-29 10:43:42 +00:00
Validator client refactor (#618)
* Update to spec v0.9.0 * Update to v0.9.1 * Bump spec tags for v0.9.1 * Formatting, fix CI failures * Resolve accidental KeyPair merge conflict * Document new BeaconState functions * Add `validator` changes from `validator-to-rest` * Add initial (failing) REST api tests * Fix signature parsing * Add more tests * Refactor http router * Add working tests for publish beacon block * Add validator duties tests * Move account_manager under `lighthouse` binary * Unify logfile handling in `environment` crate. * Fix incorrect cache drops in `advance_caches` * Update fork choice for v0.9.1 * Add `deposit_contract` crate * Add progress on validator onboarding * Add unfinished attesation code * Update account manager CLI * Write eth1 data file as hex string * Integrate ValidatorDirectory with validator_client * Move ValidatorDirectory into validator_client * Clean up some FIXMEs * Add beacon_chain_sim * Fix a few docs/logs * Expand `beacon_chain_sim` * Fix spec for `beacon_chain_sim * More testing for api * Start work on attestation endpoint * Reject empty attestations * Allow attestations to genesis block * Add working tests for `rest_api` validator endpoint * Remove grpc from beacon_node * Start heavy refactor of validator client - Block production is working * Prune old validator client files * Start works on attestation service * Add attestation service to validator client * Use full pubkey for validator directories * Add validator duties post endpoint * Use par_iter for keypair generation * Use bulk duties request in validator client * Add version http endpoint tests * Add interop keys and startup wait * Ensure a prompt exit * Add duties pruning * Fix compile error in beacon node tests * Add github workflow * Modify rust.yaml * Modify gitlab actions * Add to CI file * Add sudo to CI npm install * Move cargo fmt to own job in tests * Fix cargo fmt in CI * Add rustup update before cargo fmt * Change name of CI job * Make other CI jobs require cargo fmt * Add CI badge * Remove gitlab and travis files * Add different http timeout for debug * Update docker file, use makefile in CI * Use make in the dockerfile, skip the test * Use the makefile for debug GI test * Update book * Tidy grpc and misc things * Apply discv5 fixes * Address other minor issues * Fix warnings * Attempt fix for addr parsing * Tidy validator config, CLIs * Tidy comments * Tidy signing, reduce ForkService duplication * Fail if skipping too many slots * Set default recent genesis time to 0 * Add custom http timeout to validator * Fix compile bug in node_test_rig * Remove old bootstrap flag from val CLI * Update docs * Tidy val client * Change val client log levels * Add comments, more validity checks * Fix compile error, add comments * Undo changes to eth2-libp2p/src * Reduce duplication of keypair generation * Add more logging for validator duties * Fix beacon_chain_sim, nitpicks * Fix compile error, minor nits * Address Michael's comments
This commit is contained in:
@@ -28,6 +28,7 @@ sloggers = "0.3.4"
|
||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||
eth2_hashing = "0.1.0"
|
||||
eth2_ssz = "0.1.2"
|
||||
eth2_ssz_types = { path = "../../eth2/utils/ssz_types" }
|
||||
eth2_ssz_derive = "0.1.0"
|
||||
state_processing = { path = "../../eth2/state_processing" }
|
||||
tree_hash = "0.1.0"
|
||||
|
||||
@@ -10,7 +10,7 @@ use lmd_ghost::LmdGhost;
|
||||
use operation_pool::DepositInsertStatus;
|
||||
use operation_pool::{OperationPool, PersistedOperationPool};
|
||||
use parking_lot::RwLock;
|
||||
use slog::{debug, error, info, trace, warn, Logger};
|
||||
use slog::{crit, debug, error, info, trace, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::Encode;
|
||||
use state_processing::per_block_processing::{
|
||||
@@ -44,6 +44,7 @@ pub const GRAFFITI: &str = "sigp/lighthouse-0.0.0-prerelease";
|
||||
const WRITE_BLOCK_PROCESSING_SSZ: bool = cfg!(feature = "write_ssz_files");
|
||||
|
||||
const BLOCK_SKIPPING_LOGGING_THRESHOLD: u64 = 3;
|
||||
const BLOCK_SKIPPING_FAILURE_THRESHOLD: u64 = 128;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProcessingOutcome {
|
||||
@@ -74,6 +75,7 @@ pub enum BlockProcessingOutcome {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AttestationProcessingOutcome {
|
||||
Processed,
|
||||
EmptyAggregationBitfield,
|
||||
UnknownHeadBlock {
|
||||
beacon_block_root: Hash256,
|
||||
},
|
||||
@@ -175,7 +177,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 +192,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())),
|
||||
})
|
||||
@@ -274,6 +276,36 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
ReverseStateRootIterator::new((head.beacon_state_root, slot), iter)
|
||||
}
|
||||
|
||||
/// Returns the block at the given root, if any.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// May return a database error.
|
||||
pub fn block_at_root(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
|
||||
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 the block at the given root, if any.
|
||||
///
|
||||
/// ## Errors
|
||||
@@ -318,7 +350,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
if slot == head_state.slot {
|
||||
Ok(head_state)
|
||||
} else if slot > head_state.slot {
|
||||
if slot > head_state.slot + BLOCK_SKIPPING_LOGGING_THRESHOLD {
|
||||
// It is presently very resource intensive (lots of hashing) to skip slots.
|
||||
//
|
||||
// We log warnings or simply fail if there are too many skip slots. This is a
|
||||
// protection against DoS attacks.
|
||||
if slot > head_state.slot + BLOCK_SKIPPING_FAILURE_THRESHOLD {
|
||||
crit!(
|
||||
self.log,
|
||||
"Refusing to skip more than {} blocks", BLOCK_SKIPPING_LOGGING_THRESHOLD;
|
||||
"head_slot" => head_state.slot,
|
||||
"request_slot" => slot
|
||||
);
|
||||
|
||||
return Err(Error::StateSkipTooLarge {
|
||||
head_slot: head_state.slot,
|
||||
requested_slot: slot,
|
||||
});
|
||||
} else if slot > head_state.slot + BLOCK_SKIPPING_LOGGING_THRESHOLD {
|
||||
warn!(
|
||||
self.log,
|
||||
"Skipping more than {} blocks", BLOCK_SKIPPING_LOGGING_THRESHOLD;
|
||||
@@ -326,6 +374,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
"request_slot" => slot
|
||||
)
|
||||
}
|
||||
|
||||
let head_state_slot = head_state.slot;
|
||||
let mut state = head_state;
|
||||
while state.slot < slot {
|
||||
@@ -476,6 +525,33 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce an `Attestation` that is valid for the given `slot` and `index`.
|
||||
///
|
||||
/// Always attests to the canonical chain.
|
||||
pub fn produce_attestation(
|
||||
&self,
|
||||
slot: Slot,
|
||||
index: CommitteeIndex,
|
||||
) -> Result<Attestation<T::EthSpec>, Error> {
|
||||
let state = self.state_at_slot(slot)?;
|
||||
let head = self.head();
|
||||
|
||||
let data = self.produce_attestation_data_for_block(
|
||||
index,
|
||||
head.beacon_block_root,
|
||||
head.beacon_block.slot,
|
||||
&state,
|
||||
)?;
|
||||
|
||||
let committee_len = state.get_beacon_committee(slot, index)?.committee.len();
|
||||
|
||||
Ok(Attestation {
|
||||
aggregation_bits: BitList::with_capacity(committee_len)?,
|
||||
data,
|
||||
signature: AggregateSignature::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Produce an `AttestationData` that is valid for the given `slot`, `index`.
|
||||
///
|
||||
/// Always attests to the canonical chain.
|
||||
@@ -635,6 +711,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_REQUESTS);
|
||||
let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_TIMES);
|
||||
|
||||
if attestation.aggregation_bits.num_set_bits() == 0 {
|
||||
return Ok(AttestationProcessingOutcome::EmptyAggregationBitfield);
|
||||
}
|
||||
|
||||
// From the store, load the attestation's "head block".
|
||||
//
|
||||
// An honest validator would have set this block to be the head of the chain (i.e., the
|
||||
@@ -790,13 +870,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
result
|
||||
};
|
||||
|
||||
if block.slot <= finalized_epoch.start_slot(T::EthSpec::slots_per_epoch()) {
|
||||
if block.slot > 0 && block.slot <= finalized_epoch.start_slot(T::EthSpec::slots_per_epoch())
|
||||
{
|
||||
// Ignore any attestation where the slot of `data.beacon_block_root` is equal to or
|
||||
// prior to the finalized epoch.
|
||||
//
|
||||
// For any valid attestation if the `beacon_block_root` is prior to finalization, then
|
||||
// all other parameters (source, target, etc) must all be prior to finalization and
|
||||
// therefore no longer interesting.
|
||||
//
|
||||
// We allow the case where the block is the genesis block. Without this, all
|
||||
// attestations prior to the first block being produced would be invalid.
|
||||
Ok(AttestationProcessingOutcome::FinalizedSlot {
|
||||
attestation: block.slot.epoch(T::EthSpec::slots_per_epoch()),
|
||||
finalized: finalized_epoch,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::eth1_chain::Error as Eth1ChainError;
|
||||
use crate::fork_choice::Error as ForkChoiceError;
|
||||
use ssz_types::Error as SszTypesError;
|
||||
use state_processing::per_block_processing::errors::AttestationValidationError;
|
||||
use state_processing::BlockProcessingError;
|
||||
use state_processing::SlotProcessingError;
|
||||
@@ -38,12 +39,18 @@ pub enum BeaconChainError {
|
||||
beacon_block_root: Hash256,
|
||||
},
|
||||
AttestationValidationError(AttestationValidationError),
|
||||
StateSkipTooLarge {
|
||||
head_slot: Slot,
|
||||
requested_slot: Slot,
|
||||
},
|
||||
/// Returned when an internal check fails, indicating corrupt data.
|
||||
InvariantViolated(String),
|
||||
SszTypesError(SszTypesError),
|
||||
}
|
||||
|
||||
easy_from_to!(SlotProcessingError, BeaconChainError);
|
||||
easy_from_to!(AttestationValidationError, BeaconChainError);
|
||||
easy_from_to!(SszTypesError, BeaconChainError);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProductionError {
|
||||
|
||||
@@ -13,7 +13,6 @@ beacon_chain = { path = "../beacon_chain" }
|
||||
store = { path = "../store" }
|
||||
network = { path = "../network" }
|
||||
eth2-libp2p = { path = "../eth2-libp2p" }
|
||||
rpc = { path = "../rpc" }
|
||||
rest_api = { path = "../rest_api" }
|
||||
websocket_server = { path = "../websocket_server" }
|
||||
prometheus = "0.7.0"
|
||||
|
||||
@@ -19,7 +19,6 @@ use genesis::{
|
||||
use lighthouse_bootstrap::Bootstrapper;
|
||||
use lmd_ghost::LmdGhost;
|
||||
use network::{NetworkConfig, NetworkMessage, Service as NetworkService};
|
||||
use rpc::Config as RpcConfig;
|
||||
use slog::{debug, error, info, warn};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
@@ -267,35 +266,6 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Immediately starts the gRPC server (gRPC is soon to be deprecated).
|
||||
pub fn grpc_server(mut self, config: &RpcConfig) -> Result<Self, String> {
|
||||
let beacon_chain = self
|
||||
.beacon_chain
|
||||
.clone()
|
||||
.ok_or_else(|| "grpc_server requires a beacon chain")?;
|
||||
let context = self
|
||||
.runtime_context
|
||||
.as_ref()
|
||||
.ok_or_else(|| "grpc_server requires a runtime_context")?
|
||||
.service_context("grpc");
|
||||
let network_send = self
|
||||
.libp2p_network_send
|
||||
.clone()
|
||||
.ok_or_else(|| "grpc_server requires a libp2p network")?;
|
||||
|
||||
let exit_signal = rpc::start_server(
|
||||
config,
|
||||
&context.executor,
|
||||
network_send,
|
||||
beacon_chain,
|
||||
context.log,
|
||||
);
|
||||
|
||||
self.exit_signals.push(exit_signal);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Immediately starts the beacon node REST API http server.
|
||||
pub fn http_server(
|
||||
mut self,
|
||||
@@ -305,7 +275,7 @@ where
|
||||
let beacon_chain = self
|
||||
.beacon_chain
|
||||
.clone()
|
||||
.ok_or_else(|| "grpc_server requires a beacon chain")?;
|
||||
.ok_or_else(|| "http_server requires a beacon chain")?;
|
||||
let context = self
|
||||
.runtime_context
|
||||
.as_ref()
|
||||
@@ -314,11 +284,11 @@ where
|
||||
let network = self
|
||||
.libp2p_network
|
||||
.clone()
|
||||
.ok_or_else(|| "grpc_server requires a libp2p network")?;
|
||||
.ok_or_else(|| "http_server requires a libp2p network")?;
|
||||
let network_send = self
|
||||
.libp2p_network_send
|
||||
.clone()
|
||||
.ok_or_else(|| "grpc_server requires a libp2p network sender")?;
|
||||
.ok_or_else(|| "http_server requires a libp2p network sender")?;
|
||||
|
||||
let network_info = rest_api::NetworkInfo {
|
||||
network_service: network.clone(),
|
||||
|
||||
@@ -51,7 +51,6 @@ pub struct Config {
|
||||
/// via the CLI at runtime, instead of from a configuration file saved to disk.
|
||||
pub genesis: ClientGenesis,
|
||||
pub network: network::NetworkConfig,
|
||||
pub rpc: rpc::Config,
|
||||
pub rest_api: rest_api::Config,
|
||||
pub websocket_server: websocket_server::Config,
|
||||
pub eth1: eth1::Config,
|
||||
@@ -66,7 +65,6 @@ impl Default for Config {
|
||||
db_name: "chain_db".to_string(),
|
||||
genesis: <_>::default(),
|
||||
network: NetworkConfig::new(),
|
||||
rpc: <_>::default(),
|
||||
rest_api: <_>::default(),
|
||||
websocket_server: <_>::default(),
|
||||
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
||||
@@ -107,7 +105,6 @@ impl Config {
|
||||
};
|
||||
|
||||
self.network.apply_cli_args(args)?;
|
||||
self.rpc.apply_cli_args(args)?;
|
||||
self.rest_api.apply_cli_args(args)?;
|
||||
self.websocket_server.apply_cli_args(args)?;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod builder;
|
||||
pub mod error;
|
||||
|
||||
use beacon_chain::BeaconChain;
|
||||
use eth2_libp2p::{Enr, Multiaddr};
|
||||
use exit_future::Signal;
|
||||
use network::Service as NetworkService;
|
||||
use std::net::SocketAddr;
|
||||
@@ -48,6 +49,16 @@ impl<T: BeaconChainTypes> Client<T> {
|
||||
pub fn libp2p_listen_port(&self) -> Option<u16> {
|
||||
self.libp2p_network.as_ref().map(|n| n.listen_port())
|
||||
}
|
||||
|
||||
/// Returns the list of libp2p addresses the client is listening to.
|
||||
pub fn libp2p_listen_addresses(&self) -> Option<Vec<Multiaddr>> {
|
||||
self.libp2p_network.as_ref().map(|n| n.listen_multiaddrs())
|
||||
}
|
||||
|
||||
/// Returns the local libp2p ENR of this node, for network discovery.
|
||||
pub fn enr(&self) -> Option<Enr> {
|
||||
self.libp2p_network.as_ref().map(|n| n.local_enr())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> Drop for Client<T> {
|
||||
|
||||
@@ -7,9 +7,10 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
hex = "0.3"
|
||||
#SigP repository
|
||||
libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "2a9ded92db30dab7d3530c597a0a3b3458a7dfb7" }
|
||||
enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "2a9ded92db30dab7d3530c597a0a3b3458a7dfb7", features = ["serde"] }
|
||||
# rust-libp2p is presently being sourced from a Sigma Prime fork of the
|
||||
# `libp2p/rust-libp2p` repository.
|
||||
libp2p = { git = "https://github.com/sigp/rust-libp2p", rev = "2a9ded92db30dab7d3530c597a0a3b3458a7dfb7" }
|
||||
enr = { git = "https://github.com/sigp/rust-libp2p/", rev = "2a9ded92db30dab7d3530c597a0a3b3458a7dfb7", features = ["serde"] }
|
||||
types = { path = "../../eth2/types" }
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rest_api"
|
||||
version = "0.1.0"
|
||||
authors = ["Luke Anderson <luke@lukeanderson.com.au>"]
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>", "Luke Anderson <luke@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -12,28 +12,31 @@ network = { path = "../network" }
|
||||
eth2-libp2p = { path = "../eth2-libp2p" }
|
||||
store = { path = "../store" }
|
||||
version = { path = "../version" }
|
||||
serde = { version = "1.0.102", features = ["derive"] }
|
||||
serde_json = "1.0.41"
|
||||
serde_yaml = "0.8.11"
|
||||
slog = "2.5.2"
|
||||
slog-term = "2.4.2"
|
||||
slog-async = "2.3.0"
|
||||
eth2_ssz = "0.1.2"
|
||||
eth2_ssz_derive = "0.1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
slog = "2.5"
|
||||
slog-term = "2.4"
|
||||
slog-async = "2.3"
|
||||
eth2_ssz = { path = "../../eth2/utils/ssz" }
|
||||
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
||||
state_processing = { path = "../../eth2/state_processing" }
|
||||
types = { path = "../../eth2/types" }
|
||||
clap = "2.33.0"
|
||||
http = "0.1.19"
|
||||
prometheus = { version = "0.7.0", features = ["process"] }
|
||||
hyper = "0.12.35"
|
||||
clap = "2.33"
|
||||
http = "0.1"
|
||||
hyper = "0.12"
|
||||
exit-future = "0.1.4"
|
||||
tokio = "0.1.22"
|
||||
url = "2.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
url = "2.1"
|
||||
lazy_static = "1.3.0"
|
||||
eth2_config = { path = "../../eth2/utils/eth2_config" }
|
||||
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
|
||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||
hex = "0.3"
|
||||
parking_lot = "0.9.0"
|
||||
parking_lot = "0.9"
|
||||
futures = "0.1.29"
|
||||
|
||||
[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" }
|
||||
|
||||
@@ -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,
|
||||
@@ -23,12 +23,10 @@ pub struct HeadResponse {
|
||||
}
|
||||
|
||||
/// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`.
|
||||
pub fn get_head<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = req
|
||||
.extensions()
|
||||
.get::<Arc<BeaconChain<T>>>()
|
||||
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?;
|
||||
|
||||
pub fn get_head<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let chain_head = beacon_chain.head();
|
||||
|
||||
let head = HeadResponse {
|
||||
@@ -66,12 +64,10 @@ pub struct BlockResponse<T: EthSpec> {
|
||||
}
|
||||
|
||||
/// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`.
|
||||
pub fn get_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = req
|
||||
.extensions()
|
||||
.get::<Arc<BeaconChain<T>>>()
|
||||
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?;
|
||||
|
||||
pub fn get_block<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let query_params = ["root", "slot"];
|
||||
let (key, value) = UrlQuery::from_request(&req)?.first_of(&query_params)?;
|
||||
|
||||
@@ -106,9 +102,10 @@ pub fn get_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
||||
}
|
||||
|
||||
/// HTTP handler to return a `BeaconBlock` root at a given `slot`.
|
||||
pub fn get_block_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
|
||||
pub fn get_block_root<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
|
||||
let target = parse_slot(&slot_string)?;
|
||||
|
||||
@@ -120,8 +117,10 @@ pub fn get_block_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
|
||||
}
|
||||
|
||||
/// HTTP handler to return the `Fork` of the current head.
|
||||
pub fn get_fork<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
pub fn get_fork<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body(&beacon_chain.head().beacon_state.fork)
|
||||
}
|
||||
|
||||
@@ -129,9 +128,10 @@ pub fn get_fork<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
||||
///
|
||||
/// The `Epoch` parameter can be any epoch number. If it is not specified,
|
||||
/// the current epoch is assumed.
|
||||
pub fn get_validators<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
|
||||
pub fn get_validators<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let epoch = match UrlQuery::from_request(&req) {
|
||||
// We have some parameters, so make sure it's the epoch one and parse it
|
||||
Ok(query) => query
|
||||
@@ -168,8 +168,10 @@ pub struct StateResponse<T: EthSpec> {
|
||||
///
|
||||
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||
/// the current head by skipping slots.
|
||||
pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
pub fn get_state<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let head_state = beacon_chain.head().beacon_state;
|
||||
|
||||
let (key, value) = match UrlQuery::from_request(&req) {
|
||||
@@ -214,9 +216,10 @@ pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
||||
///
|
||||
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||
/// the current head by skipping slots.
|
||||
pub fn get_state_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
|
||||
pub fn get_state_root<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
|
||||
let slot = parse_slot(&slot_string)?;
|
||||
|
||||
@@ -226,10 +229,10 @@ pub fn get_state_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
|
||||
}
|
||||
|
||||
/// HTTP handler to return the highest finalized slot.
|
||||
pub fn get_current_finalized_checkpoint<T: BeaconChainTypes + 'static>(
|
||||
pub fn get_current_finalized_checkpoint<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
let head_state = beacon_chain.head().beacon_state;
|
||||
|
||||
let checkpoint = head_state.finalized_checkpoint.clone();
|
||||
@@ -238,10 +241,19 @@ pub fn get_current_finalized_checkpoint<T: BeaconChainTypes + 'static>(
|
||||
}
|
||||
|
||||
/// HTTP handler to return a `BeaconState` at the genesis block.
|
||||
pub fn get_genesis_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
|
||||
pub fn get_genesis_state<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let (_root, state) = state_at_slot(&beacon_chain, Slot::new(0))?;
|
||||
|
||||
ResponseBuilder::new(&req)?.body(&state)
|
||||
}
|
||||
|
||||
/// Read the genesis time from the current beacon chain state.
|
||||
pub fn get_genesis_time<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body(&beacon_chain.head().beacon_state.genesis_time)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,34 @@ use clap::ArgMatches;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
/// Defines the encoding for the API.
|
||||
#[derive(Clone, Serialize, Deserialize, Copy)]
|
||||
pub enum ApiEncodingFormat {
|
||||
JSON,
|
||||
YAML,
|
||||
SSZ,
|
||||
}
|
||||
|
||||
impl ApiEncodingFormat {
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self {
|
||||
ApiEncodingFormat::JSON => "application/json",
|
||||
ApiEncodingFormat::YAML => "application/yaml",
|
||||
ApiEncodingFormat::SSZ => "application/ssz",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ApiEncodingFormat {
|
||||
fn from(f: &str) -> ApiEncodingFormat {
|
||||
match f {
|
||||
"application/yaml" => ApiEncodingFormat::YAML,
|
||||
"application/ssz" => ApiEncodingFormat::SSZ,
|
||||
_ => ApiEncodingFormat::JSON,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP REST API Configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
|
||||
@@ -10,13 +10,16 @@ use http::header;
|
||||
use hyper::{Body, Request};
|
||||
use network::NetworkMessage;
|
||||
use parking_lot::RwLock;
|
||||
use ssz::Encode;
|
||||
use ssz::{Decode, Encode};
|
||||
use std::sync::Arc;
|
||||
use store::{iter::AncestorIter, Store};
|
||||
use tokio::sync::mpsc;
|
||||
use types::{Attestation, BeaconBlock, BeaconState, EthSpec, Hash256, RelativeEpoch, Slot};
|
||||
use types::{
|
||||
Attestation, BeaconBlock, BeaconState, CommitteeIndex, Epoch, EthSpec, Hash256, RelativeEpoch,
|
||||
Signature, Slot,
|
||||
};
|
||||
|
||||
/// Parse a slot from a `0x` preixed string.
|
||||
/// Parse a slot.
|
||||
///
|
||||
/// E.g., `"1234"`
|
||||
pub fn parse_slot(string: &str) -> Result<Slot, ApiError> {
|
||||
@@ -26,6 +29,25 @@ pub fn parse_slot(string: &str) -> Result<Slot, ApiError> {
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse slot: {:?}", e)))
|
||||
}
|
||||
|
||||
/// Parse an epoch.
|
||||
///
|
||||
/// E.g., `"13"`
|
||||
pub fn parse_epoch(string: &str) -> Result<Epoch, ApiError> {
|
||||
string
|
||||
.parse::<u64>()
|
||||
.map(Epoch::from)
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse epoch: {:?}", e)))
|
||||
}
|
||||
|
||||
/// Parse a CommitteeIndex.
|
||||
///
|
||||
/// E.g., `"18"`
|
||||
pub fn parse_committee_index(string: &str) -> Result<CommitteeIndex, ApiError> {
|
||||
string
|
||||
.parse::<CommitteeIndex>()
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse committee index: {:?}", e)))
|
||||
}
|
||||
|
||||
/// Checks the provided request to ensure that the `content-type` header.
|
||||
///
|
||||
/// The content-type header should either be omitted, in which case JSON is assumed, or it should
|
||||
@@ -41,6 +63,23 @@ pub fn check_content_type_for_json(req: &Request<Body>) -> Result<(), ApiError>
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a signature from a `0x` preixed string.
|
||||
pub fn parse_signature(string: &str) -> Result<Signature, ApiError> {
|
||||
const PREFIX: &str = "0x";
|
||||
|
||||
if string.starts_with(PREFIX) {
|
||||
let trimmed = string.trim_start_matches(PREFIX);
|
||||
let bytes = hex::decode(trimmed)
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse signature hex: {:?}", e)))?;
|
||||
Signature::from_ssz_bytes(&bytes)
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse signature bytes: {:?}", e)))
|
||||
} else {
|
||||
Err(ApiError::BadRequest(
|
||||
"Signature must have a 0x prefix".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a root from a `0x` preixed string.
|
||||
///
|
||||
/// E.g., `"0x0000000000000000000000000000000000000000000000000000000000000000"`
|
||||
@@ -54,7 +93,7 @@ pub fn parse_root(string: &str) -> Result<Hash256, ApiError> {
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to parse root: {:?}", e)))
|
||||
} else {
|
||||
Err(ApiError::BadRequest(
|
||||
"Root must have a '0x' prefix".to_string(),
|
||||
"Root must have a 0x prefix".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -71,7 +110,7 @@ pub fn parse_pubkey(string: &str) -> Result<PublicKey, ApiError> {
|
||||
Ok(pubkey)
|
||||
} else {
|
||||
Err(ApiError::BadRequest(
|
||||
"Public key must have a '0x' prefix".to_string(),
|
||||
"Public key must have a 0x prefix".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -194,26 +233,6 @@ pub fn implementation_pending_response(_req: Request<Body>) -> ApiResult {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_beacon_chain_from_request<T: BeaconChainTypes + 'static>(
|
||||
req: &Request<Body>,
|
||||
) -> Result<(Arc<BeaconChain<T>>), ApiError> {
|
||||
// Get beacon state
|
||||
let beacon_chain = req
|
||||
.extensions()
|
||||
.get::<Arc<BeaconChain<T>>>()
|
||||
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".into()))?;
|
||||
|
||||
Ok(beacon_chain.clone())
|
||||
}
|
||||
|
||||
pub fn get_logger_from_request(req: &Request<Body>) -> slog::Logger {
|
||||
let log = req
|
||||
.extensions()
|
||||
.get::<slog::Logger>()
|
||||
.expect("Should always get the logger from the request, since we put it in there.");
|
||||
log.to_owned()
|
||||
}
|
||||
|
||||
pub fn publish_beacon_block_to_network<T: BeaconChainTypes + 'static>(
|
||||
chan: Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>,
|
||||
block: BeaconBlock<T::EthSpec>,
|
||||
|
||||
@@ -5,13 +5,14 @@ extern crate lazy_static;
|
||||
extern crate network as client_network;
|
||||
|
||||
mod beacon;
|
||||
mod config;
|
||||
pub mod config;
|
||||
mod error;
|
||||
mod helpers;
|
||||
mod metrics;
|
||||
mod network;
|
||||
mod node;
|
||||
mod response_builder;
|
||||
mod router;
|
||||
mod spec;
|
||||
mod url_query;
|
||||
mod validator;
|
||||
@@ -19,12 +20,13 @@ mod validator;
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use client_network::NetworkMessage;
|
||||
use client_network::Service as NetworkService;
|
||||
pub use config::ApiEncodingFormat;
|
||||
use error::{ApiError, ApiResult};
|
||||
use eth2_config::Eth2Config;
|
||||
use futures::future::IntoFuture;
|
||||
use hyper::rt::Future;
|
||||
use hyper::service::Service;
|
||||
use hyper::{Body, Method, Request, Response, Server};
|
||||
use hyper::server::conn::AddrStream;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use parking_lot::RwLock;
|
||||
use slog::{info, warn};
|
||||
use std::net::SocketAddr;
|
||||
@@ -35,167 +37,19 @@ use tokio::runtime::TaskExecutor;
|
||||
use tokio::sync::mpsc;
|
||||
use url_query::UrlQuery;
|
||||
|
||||
pub use crate::helpers::parse_pubkey;
|
||||
pub use beacon::{BlockResponse, HeadResponse, StateResponse};
|
||||
pub use config::Config;
|
||||
pub use validator::{BulkValidatorDutiesRequest, ValidatorDuty};
|
||||
|
||||
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
|
||||
|
||||
pub struct ApiService<T: BeaconChainTypes + 'static> {
|
||||
log: slog::Logger,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
db_path: DBPath,
|
||||
network_service: Arc<NetworkService<T>>,
|
||||
network_channel: Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>,
|
||||
eth2_config: Arc<Eth2Config>,
|
||||
}
|
||||
pub type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
|
||||
pub type NetworkChannel = Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>;
|
||||
|
||||
pub struct NetworkInfo<T: BeaconChainTypes> {
|
||||
pub network_service: Arc<NetworkService<T>>,
|
||||
pub network_chan: mpsc::UnboundedSender<NetworkMessage>,
|
||||
}
|
||||
|
||||
fn into_boxfut<F: IntoFuture + 'static>(item: F) -> BoxFut
|
||||
where
|
||||
F: IntoFuture<Item = Response<Body>, Error = ApiError>,
|
||||
F::Future: Send,
|
||||
{
|
||||
Box::new(item.into_future())
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> Service for ApiService<T> {
|
||||
type ReqBody = Body;
|
||||
type ResBody = Body;
|
||||
type Error = ApiError;
|
||||
type Future = BoxFut;
|
||||
|
||||
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
||||
metrics::inc_counter(&metrics::REQUEST_COUNT);
|
||||
let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
|
||||
|
||||
// Add all the useful bits into the request, so that we can pull them out in the individual
|
||||
// functions.
|
||||
req.extensions_mut()
|
||||
.insert::<slog::Logger>(self.log.clone());
|
||||
req.extensions_mut()
|
||||
.insert::<Arc<BeaconChain<T>>>(self.beacon_chain.clone());
|
||||
req.extensions_mut().insert::<DBPath>(self.db_path.clone());
|
||||
req.extensions_mut()
|
||||
.insert::<Arc<NetworkService<T>>>(self.network_service.clone());
|
||||
req.extensions_mut()
|
||||
.insert::<Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>>(
|
||||
self.network_channel.clone(),
|
||||
);
|
||||
req.extensions_mut()
|
||||
.insert::<Arc<Eth2Config>>(self.eth2_config.clone());
|
||||
|
||||
let path = req.uri().path().to_string();
|
||||
|
||||
// Route the request to the correct handler.
|
||||
let result = match (req.method(), path.as_ref()) {
|
||||
// Methods for Client
|
||||
(&Method::GET, "/node/version") => into_boxfut(node::get_version(req)),
|
||||
(&Method::GET, "/node/genesis_time") => into_boxfut(node::get_genesis_time::<T>(req)),
|
||||
(&Method::GET, "/node/syncing") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
|
||||
// Methods for Network
|
||||
(&Method::GET, "/network/enr") => into_boxfut(network::get_enr::<T>(req)),
|
||||
(&Method::GET, "/network/peer_count") => into_boxfut(network::get_peer_count::<T>(req)),
|
||||
(&Method::GET, "/network/peer_id") => into_boxfut(network::get_peer_id::<T>(req)),
|
||||
(&Method::GET, "/network/peers") => into_boxfut(network::get_peer_list::<T>(req)),
|
||||
(&Method::GET, "/network/listen_port") => {
|
||||
into_boxfut(network::get_listen_port::<T>(req))
|
||||
}
|
||||
(&Method::GET, "/network/listen_addresses") => {
|
||||
into_boxfut(network::get_listen_addresses::<T>(req))
|
||||
}
|
||||
|
||||
// Methods for Beacon Node
|
||||
(&Method::GET, "/beacon/head") => into_boxfut(beacon::get_head::<T>(req)),
|
||||
(&Method::GET, "/beacon/block") => into_boxfut(beacon::get_block::<T>(req)),
|
||||
(&Method::GET, "/beacon/block_root") => into_boxfut(beacon::get_block_root::<T>(req)),
|
||||
(&Method::GET, "/beacon/blocks") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/fork") => into_boxfut(beacon::get_fork::<T>(req)),
|
||||
(&Method::GET, "/beacon/attestations") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/attestations/pending") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
|
||||
(&Method::GET, "/beacon/validators") => into_boxfut(beacon::get_validators::<T>(req)),
|
||||
(&Method::GET, "/beacon/validators/indicies") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/validators/pubkeys") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
|
||||
// Methods for Validator
|
||||
(&Method::GET, "/beacon/validator/duties") => {
|
||||
into_boxfut(validator::get_validator_duties::<T>(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/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") => {
|
||||
into_boxfut(validator::get_new_attestation::<T>(req))
|
||||
}
|
||||
(&Method::POST, "/beacon/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)),
|
||||
(&Method::GET, "/beacon/state/current_finalized_checkpoint") => {
|
||||
into_boxfut(beacon::get_current_finalized_checkpoint::<T>(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/state/genesis") => {
|
||||
into_boxfut(beacon::get_genesis_state::<T>(req))
|
||||
}
|
||||
//TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances
|
||||
|
||||
// Methods for bootstrap and checking configuration
|
||||
(&Method::GET, "/spec") => into_boxfut(spec::get_spec::<T>(req)),
|
||||
(&Method::GET, "/spec/slots_per_epoch") => {
|
||||
into_boxfut(spec::get_slots_per_epoch::<T>(req))
|
||||
}
|
||||
(&Method::GET, "/spec/deposit_contract") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/spec/eth2_config") => into_boxfut(spec::get_eth2_config::<T>(req)),
|
||||
|
||||
(&Method::GET, "/metrics") => into_boxfut(metrics::get_prometheus::<T>(req)),
|
||||
|
||||
_ => Box::new(futures::future::err(ApiError::NotFound(
|
||||
"Request path and/or method not found.".to_owned(),
|
||||
))),
|
||||
};
|
||||
|
||||
let response = match result.wait() {
|
||||
// Return the `hyper::Response`.
|
||||
Ok(response) => {
|
||||
metrics::inc_counter(&metrics::SUCCESS_COUNT);
|
||||
slog::debug!(self.log, "Request successful: {:?}", path);
|
||||
response
|
||||
}
|
||||
// Map the `ApiError` into `hyper::Response`.
|
||||
Err(e) => {
|
||||
slog::debug!(self.log, "Request failure: {:?}", path);
|
||||
e.into()
|
||||
}
|
||||
};
|
||||
|
||||
metrics::stop_timer(timer);
|
||||
|
||||
Box::new(futures::future::ok(response))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_server<T: BeaconChainTypes>(
|
||||
config: &Config,
|
||||
executor: &TaskExecutor,
|
||||
@@ -205,46 +59,54 @@ pub fn start_server<T: BeaconChainTypes>(
|
||||
eth2_config: Eth2Config,
|
||||
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();
|
||||
|
||||
let exit_log = log.clone();
|
||||
let server_exit = exit.and_then(move |_| {
|
||||
info!(exit_log, "API service shutdown");
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let db_path = DBPath(db_path);
|
||||
|
||||
// Get the address to bind to
|
||||
let bind_addr = (config.listen_address, config.port).into();
|
||||
|
||||
// Clone our stateful objects, for use in service closure.
|
||||
let server_log = log.clone();
|
||||
let server_bc = beacon_chain.clone();
|
||||
let inner_log = log.clone();
|
||||
let eth2_config = Arc::new(eth2_config);
|
||||
|
||||
let service = move || -> futures::future::FutureResult<ApiService<T>, String> {
|
||||
futures::future::ok(ApiService {
|
||||
log: server_log.clone(),
|
||||
beacon_chain: server_bc.clone(),
|
||||
db_path: db_path.clone(),
|
||||
network_service: network_info.network_service.clone(),
|
||||
network_channel: Arc::new(RwLock::new(network_info.network_chan.clone())),
|
||||
eth2_config: eth2_config.clone(),
|
||||
// Define the function that will build the request handler.
|
||||
let make_service = make_service_fn(move |_socket: &AddrStream| {
|
||||
let beacon_chain = beacon_chain.clone();
|
||||
let log = inner_log.clone();
|
||||
let eth2_config = eth2_config.clone();
|
||||
let network_service = network_info.network_service.clone();
|
||||
let network_channel = Arc::new(RwLock::new(network_info.network_chan.clone()));
|
||||
let db_path = db_path.clone();
|
||||
|
||||
service_fn(move |req: Request<Body>| {
|
||||
router::route(
|
||||
req,
|
||||
beacon_chain.clone(),
|
||||
network_service.clone(),
|
||||
network_channel.clone(),
|
||||
eth2_config.clone(),
|
||||
log.clone(),
|
||||
db_path.clone(),
|
||||
)
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
let log_clone = log.clone();
|
||||
let server = Server::bind(&bind_addr).serve(service);
|
||||
let bind_addr = (config.listen_address, config.port).into();
|
||||
let server = Server::bind(&bind_addr).serve(make_service);
|
||||
|
||||
// Determine the address the server is actually listening on.
|
||||
//
|
||||
// This may be different to `bind_addr` if bind port was 0 (this allows the OS to choose a free
|
||||
// port).
|
||||
let actual_listen_addr = server.local_addr();
|
||||
|
||||
// Build a channel to kill the HTTP server.
|
||||
let (exit_signal, exit) = exit_future::signal();
|
||||
let inner_log = log.clone();
|
||||
let server_exit = exit.and_then(move |_| {
|
||||
info!(inner_log, "API service shutdown");
|
||||
Ok(())
|
||||
});
|
||||
// Configure the `hyper` server to gracefully shutdown when the shutdown channel is triggered.
|
||||
let inner_log = log.clone();
|
||||
let server_future = server
|
||||
.with_graceful_shutdown(server_exit)
|
||||
.map_err(move |e| {
|
||||
warn!(
|
||||
log_clone,
|
||||
inner_log,
|
||||
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::helpers::get_beacon_chain_from_request;
|
||||
use crate::response_builder::ResponseBuilder;
|
||||
use crate::{ApiError, ApiResult, DBPath};
|
||||
use beacon_chain::BeaconChainTypes;
|
||||
use crate::{ApiError, ApiResult};
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use hyper::{Body, Request};
|
||||
use prometheus::{Encoder, TextEncoder};
|
||||
use lighthouse_metrics::{Encoder, TextEncoder};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use lighthouse_metrics::*;
|
||||
|
||||
@@ -27,16 +28,14 @@ lazy_static! {
|
||||
/// # Note
|
||||
///
|
||||
/// This is a HTTP handler method.
|
||||
pub fn get_prometheus<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
pub fn get_prometheus<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
db_path: PathBuf,
|
||||
) -> ApiResult {
|
||||
let mut buffer = vec![];
|
||||
let encoder = TextEncoder::new();
|
||||
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
let db_path = req
|
||||
.extensions()
|
||||
.get::<DBPath>()
|
||||
.ok_or_else(|| ApiError::ServerError("DBPath extension missing".to_string()))?;
|
||||
|
||||
// There are two categories of metrics:
|
||||
//
|
||||
// - Dynamically updated: things like histograms and event counters that are updated on the
|
||||
|
||||
@@ -9,11 +9,10 @@ use std::sync::Arc;
|
||||
/// HTTP handler to return the list of libp2p multiaddr the client is listening on.
|
||||
///
|
||||
/// Returns a list of `Multiaddr`, serialized according to their `serde` impl.
|
||||
pub fn get_listen_addresses<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there");
|
||||
pub fn get_listen_addresses<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
let multiaddresses: Vec<Multiaddr> = network.listen_multiaddrs();
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&multiaddresses)
|
||||
}
|
||||
@@ -21,54 +20,48 @@ pub fn get_listen_addresses<T: BeaconChainTypes>(req: Request<Body>) -> ApiResul
|
||||
/// HTTP handler to return the network port the client is listening on.
|
||||
///
|
||||
/// Returns the TCP port number in its plain form (which is also valid JSON serialization)
|
||||
pub fn get_listen_port<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there")
|
||||
.clone();
|
||||
pub fn get_listen_port<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body(&network.listen_port())
|
||||
}
|
||||
|
||||
/// HTTP handler to return the Discv5 ENR from the client's libp2p service.
|
||||
///
|
||||
/// ENR is encoded as base64 string.
|
||||
pub fn get_enr<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there");
|
||||
pub fn get_enr<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&network.local_enr().to_base64())
|
||||
}
|
||||
|
||||
/// HTTP handler to return the `PeerId` from the client's libp2p service.
|
||||
///
|
||||
/// PeerId is encoded as base58 string.
|
||||
pub fn get_peer_id<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there");
|
||||
pub fn get_peer_id<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&network.local_peer_id().to_base58())
|
||||
}
|
||||
|
||||
/// HTTP handler to return the number of peers connected in the client's libp2p service.
|
||||
pub fn get_peer_count<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there");
|
||||
pub fn get_peer_count<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body(&network.connected_peers())
|
||||
}
|
||||
|
||||
/// HTTP handler to return the list of peers connected to the client's libp2p service.
|
||||
///
|
||||
/// Peers are presented as a list of `PeerId::to_string()`.
|
||||
pub fn get_peer_list<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
let network = req
|
||||
.extensions()
|
||||
.get::<Arc<NetworkService<T>>>()
|
||||
.expect("The network service should always be there, we put it there");
|
||||
pub fn get_peer_list<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
network: Arc<NetworkService<T>>,
|
||||
) -> ApiResult {
|
||||
let connected_peers: Vec<String> = network
|
||||
.connected_peer_set()
|
||||
.iter()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::helpers::get_beacon_chain_from_request;
|
||||
use crate::response_builder::ResponseBuilder;
|
||||
use crate::ApiResult;
|
||||
use beacon_chain::BeaconChainTypes;
|
||||
use hyper::{Body, Request};
|
||||
use version;
|
||||
|
||||
@@ -9,9 +7,3 @@ use version;
|
||||
pub fn get_version(req: Request<Body>) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&version::version())
|
||||
}
|
||||
|
||||
/// Read the genesis time from the current beacon chain state.
|
||||
pub fn get_genesis_time<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
ResponseBuilder::new(&req)?.body(&beacon_chain.head().beacon_state.genesis_time)
|
||||
}
|
||||
|
||||
@@ -1,47 +1,36 @@
|
||||
use super::{ApiError, ApiResult};
|
||||
use crate::config::ApiEncodingFormat;
|
||||
use http::header;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use serde::Serialize;
|
||||
use ssz::Encode;
|
||||
|
||||
pub enum Encoding {
|
||||
JSON,
|
||||
SSZ,
|
||||
YAML,
|
||||
TEXT,
|
||||
}
|
||||
|
||||
pub struct ResponseBuilder {
|
||||
encoding: Encoding,
|
||||
encoding: ApiEncodingFormat,
|
||||
}
|
||||
|
||||
impl ResponseBuilder {
|
||||
pub fn new(req: &Request<Body>) -> Result<Self, ApiError> {
|
||||
let content_header: String = req
|
||||
let accept_header: String = req
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.get(header::ACCEPT)
|
||||
.map_or(Ok(""), |h| h.to_str())
|
||||
.map_err(|e| {
|
||||
ApiError::BadRequest(format!(
|
||||
"The content-type header contains invalid characters: {:?}",
|
||||
"The Accept header contains invalid characters: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
.map(String::from)?;
|
||||
|
||||
// JSON is our default encoding, unless something else is requested.
|
||||
let encoding = match content_header {
|
||||
ref h if h.starts_with("application/ssz") => Encoding::SSZ,
|
||||
ref h if h.starts_with("application/yaml") => Encoding::YAML,
|
||||
ref h if h.starts_with("text/") => Encoding::TEXT,
|
||||
_ => Encoding::JSON,
|
||||
};
|
||||
let encoding = ApiEncodingFormat::from(accept_header.as_str());
|
||||
Ok(Self { encoding })
|
||||
}
|
||||
|
||||
pub fn body<T: Serialize + Encode>(self, item: &T) -> ApiResult {
|
||||
match self.encoding {
|
||||
Encoding::SSZ => Response::builder()
|
||||
ApiEncodingFormat::SSZ => Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("content-type", "application/ssz")
|
||||
.body(Body::from(item.as_ssz_bytes()))
|
||||
@@ -52,7 +41,7 @@ impl ResponseBuilder {
|
||||
|
||||
pub fn body_no_ssz<T: Serialize>(self, item: &T) -> ApiResult {
|
||||
let (body, content_type) = match self.encoding {
|
||||
Encoding::JSON => (
|
||||
ApiEncodingFormat::JSON => (
|
||||
Body::from(serde_json::to_string(&item).map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to serialize response body as JSON: {:?}",
|
||||
@@ -61,12 +50,12 @@ impl ResponseBuilder {
|
||||
})?),
|
||||
"application/json",
|
||||
),
|
||||
Encoding::SSZ => {
|
||||
ApiEncodingFormat::SSZ => {
|
||||
return Err(ApiError::UnsupportedType(
|
||||
"Response cannot be encoded as SSZ.".into(),
|
||||
));
|
||||
}
|
||||
Encoding::YAML => (
|
||||
ApiEncodingFormat::YAML => (
|
||||
Body::from(serde_yaml::to_string(&item).map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to serialize response body as YAML: {:?}",
|
||||
@@ -75,11 +64,6 @@ impl ResponseBuilder {
|
||||
})?),
|
||||
"application/yaml",
|
||||
),
|
||||
Encoding::TEXT => {
|
||||
return Err(ApiError::UnsupportedType(
|
||||
"Response cannot be encoded as plain text.".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
|
||||
172
beacon_node/rest_api/src/router.rs
Normal file
172
beacon_node/rest_api/src/router.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use crate::{
|
||||
beacon, error::ApiError, helpers, metrics, network, node, spec, validator, BoxFut,
|
||||
NetworkChannel,
|
||||
};
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use client_network::Service as NetworkService;
|
||||
use eth2_config::Eth2Config;
|
||||
use futures::{Future, IntoFuture};
|
||||
use hyper::{Body, Error, Method, Request, Response};
|
||||
use slog::debug;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn into_boxfut<F: IntoFuture + 'static>(item: F) -> BoxFut
|
||||
where
|
||||
F: IntoFuture<Item = Response<Body>, Error = ApiError>,
|
||||
F::Future: Send,
|
||||
{
|
||||
Box::new(item.into_future())
|
||||
}
|
||||
|
||||
pub fn route<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
network_service: Arc<NetworkService<T>>,
|
||||
network_channel: NetworkChannel,
|
||||
eth2_config: Arc<Eth2Config>,
|
||||
local_log: slog::Logger,
|
||||
db_path: PathBuf,
|
||||
) -> impl Future<Item = Response<Body>, Error = Error> {
|
||||
metrics::inc_counter(&metrics::REQUEST_COUNT);
|
||||
let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
|
||||
|
||||
let path = req.uri().path().to_string();
|
||||
|
||||
let log = local_log.clone();
|
||||
let request_result: Box<dyn Future<Item = Response<_>, Error = _> + Send> =
|
||||
match (req.method(), path.as_ref()) {
|
||||
// Methods for Client
|
||||
(&Method::GET, "/node/version") => into_boxfut(node::get_version(req)),
|
||||
(&Method::GET, "/node/syncing") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
|
||||
// Methods for Network
|
||||
(&Method::GET, "/network/enr") => {
|
||||
into_boxfut(network::get_enr::<T>(req, network_service))
|
||||
}
|
||||
(&Method::GET, "/network/peer_count") => {
|
||||
into_boxfut(network::get_peer_count::<T>(req, network_service))
|
||||
}
|
||||
(&Method::GET, "/network/peer_id") => {
|
||||
into_boxfut(network::get_peer_id::<T>(req, network_service))
|
||||
}
|
||||
(&Method::GET, "/network/peers") => {
|
||||
into_boxfut(network::get_peer_list::<T>(req, network_service))
|
||||
}
|
||||
(&Method::GET, "/network/listen_port") => {
|
||||
into_boxfut(network::get_listen_port::<T>(req, network_service))
|
||||
}
|
||||
(&Method::GET, "/network/listen_addresses") => {
|
||||
into_boxfut(network::get_listen_addresses::<T>(req, network_service))
|
||||
}
|
||||
|
||||
// Methods for Beacon Node
|
||||
(&Method::GET, "/beacon/head") => into_boxfut(beacon::get_head::<T>(req, beacon_chain)),
|
||||
(&Method::GET, "/beacon/block") => {
|
||||
into_boxfut(beacon::get_block::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::GET, "/beacon/block_root") => {
|
||||
into_boxfut(beacon::get_block_root::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::GET, "/beacon/blocks") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/fork") => into_boxfut(beacon::get_fork::<T>(req, beacon_chain)),
|
||||
(&Method::GET, "/beacon/attestations") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/attestations/pending") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/genesis_time") => {
|
||||
into_boxfut(beacon::get_genesis_time::<T>(req, beacon_chain))
|
||||
}
|
||||
|
||||
(&Method::GET, "/beacon/validators") => {
|
||||
into_boxfut(beacon::get_validators::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::GET, "/beacon/validators/indicies") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/beacon/validators/pubkeys") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
|
||||
// Methods for Validator
|
||||
(&Method::GET, "/validator/duties") => {
|
||||
into_boxfut(validator::get_validator_duties::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::POST, "/validator/duties") => {
|
||||
validator::post_validator_duties::<T>(req, beacon_chain)
|
||||
}
|
||||
(&Method::GET, "/validator/block") => {
|
||||
into_boxfut(validator::get_new_beacon_block::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::POST, "/validator/block") => {
|
||||
validator::publish_beacon_block::<T>(req, beacon_chain, network_channel, log)
|
||||
}
|
||||
(&Method::GET, "/validator/attestation") => {
|
||||
into_boxfut(validator::get_new_attestation::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::POST, "/validator/attestation") => {
|
||||
validator::publish_attestation::<T>(req, beacon_chain, network_channel, log)
|
||||
}
|
||||
|
||||
(&Method::GET, "/beacon/state") => {
|
||||
into_boxfut(beacon::get_state::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::GET, "/beacon/state_root") => {
|
||||
into_boxfut(beacon::get_state_root::<T>(req, beacon_chain))
|
||||
}
|
||||
(&Method::GET, "/beacon/state/current_finalized_checkpoint") => into_boxfut(
|
||||
beacon::get_current_finalized_checkpoint::<T>(req, beacon_chain),
|
||||
),
|
||||
(&Method::GET, "/beacon/state/genesis") => {
|
||||
into_boxfut(beacon::get_genesis_state::<T>(req, beacon_chain))
|
||||
}
|
||||
//TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances
|
||||
|
||||
// Methods for bootstrap and checking configuration
|
||||
(&Method::GET, "/spec") => into_boxfut(spec::get_spec::<T>(req, beacon_chain)),
|
||||
(&Method::GET, "/spec/slots_per_epoch") => {
|
||||
into_boxfut(spec::get_slots_per_epoch::<T>(req))
|
||||
}
|
||||
(&Method::GET, "/spec/deposit_contract") => {
|
||||
into_boxfut(helpers::implementation_pending_response(req))
|
||||
}
|
||||
(&Method::GET, "/spec/eth2_config") => {
|
||||
into_boxfut(spec::get_eth2_config::<T>(req, eth2_config))
|
||||
}
|
||||
|
||||
(&Method::GET, "/metrics") => {
|
||||
into_boxfut(metrics::get_prometheus::<T>(req, beacon_chain, db_path))
|
||||
}
|
||||
|
||||
_ => Box::new(futures::future::err(ApiError::NotFound(
|
||||
"Request path and/or method not found.".to_owned(),
|
||||
))),
|
||||
};
|
||||
|
||||
// Map the Rust-friendly `Result` in to a http-friendly response. In effect, this ensures that
|
||||
// any `Err` returned from our response handlers becomes a valid http response to the client
|
||||
// (e.g., a response with a 404 or 500 status).
|
||||
request_result.then(move |result| match result {
|
||||
Ok(response) => {
|
||||
debug!(local_log, "Request successful: {:?}", path);
|
||||
metrics::inc_counter(&metrics::SUCCESS_COUNT);
|
||||
metrics::stop_timer(timer);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
Err(e) => {
|
||||
let error_response = e.into();
|
||||
|
||||
debug!(local_log, "Request failure: {:?}", path);
|
||||
metrics::stop_timer(timer);
|
||||
|
||||
Ok(error_response)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
use super::ApiResult;
|
||||
use crate::helpers::get_beacon_chain_from_request;
|
||||
use crate::response_builder::ResponseBuilder;
|
||||
use crate::ApiError;
|
||||
use beacon_chain::BeaconChainTypes;
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use eth2_config::Eth2Config;
|
||||
use hyper::{Body, Request};
|
||||
use std::sync::Arc;
|
||||
use types::EthSpec;
|
||||
|
||||
/// HTTP handler to return the full spec object.
|
||||
pub fn get_spec<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
pub fn get_spec<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&beacon_chain.spec)
|
||||
}
|
||||
|
||||
/// HTTP handler to return the full Eth2Config object.
|
||||
pub fn get_eth2_config<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let eth2_config = req
|
||||
.extensions()
|
||||
.get::<Arc<Eth2Config>>()
|
||||
.ok_or_else(|| ApiError::ServerError("Eth2Config extension missing".to_string()))?;
|
||||
|
||||
pub fn get_eth2_config<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
eth2_config: Arc<Eth2Config>,
|
||||
) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(eth2_config.as_ref())
|
||||
}
|
||||
|
||||
/// HTTP handler to return the full spec object.
|
||||
pub fn get_slots_per_epoch<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
pub fn get_slots_per_epoch<T: BeaconChainTypes>(req: Request<Body>) -> ApiResult {
|
||||
ResponseBuilder::new(&req)?.body(&T::EthSpec::slots_per_epoch())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::helpers::{parse_committee_index, parse_epoch, parse_signature, parse_slot};
|
||||
use crate::ApiError;
|
||||
use hyper::Request;
|
||||
use types::{CommitteeIndex, Epoch, Signature, Slot};
|
||||
|
||||
/// Provides handy functions for parsing the query parameters of a URL.
|
||||
|
||||
@@ -77,6 +79,30 @@ impl<'a> UrlQuery<'a> {
|
||||
.collect();
|
||||
Ok(queries)
|
||||
}
|
||||
|
||||
/// Returns the value of the first occurrence of the `epoch` key.
|
||||
pub fn epoch(self) -> Result<Epoch, ApiError> {
|
||||
self.first_of(&["epoch"])
|
||||
.and_then(|(_key, value)| parse_epoch(&value))
|
||||
}
|
||||
|
||||
/// Returns the value of the first occurrence of the `slot` key.
|
||||
pub fn slot(self) -> Result<Slot, ApiError> {
|
||||
self.first_of(&["slot"])
|
||||
.and_then(|(_key, value)| parse_slot(&value))
|
||||
}
|
||||
|
||||
/// Returns the value of the first occurrence of the `committee_index` key.
|
||||
pub fn committee_index(self) -> Result<CommitteeIndex, ApiError> {
|
||||
self.first_of(&["committee_index"])
|
||||
.and_then(|(_key, value)| parse_committee_index(&value))
|
||||
}
|
||||
|
||||
/// Returns the value of the first occurrence of the `randao_reveal` key.
|
||||
pub fn randao_reveal(self) -> Result<Signature, ApiError> {
|
||||
self.first_of(&["randao_reveal"])
|
||||
.and_then(|(_key, value)| parse_signature(&value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,96 +1,127 @@
|
||||
use crate::helpers::{
|
||||
check_content_type_for_json, get_beacon_chain_from_request, get_logger_from_request,
|
||||
parse_pubkey, publish_attestation_to_network, publish_beacon_block_to_network,
|
||||
check_content_type_for_json, parse_pubkey, publish_attestation_to_network,
|
||||
publish_beacon_block_to_network,
|
||||
};
|
||||
use crate::response_builder::ResponseBuilder;
|
||||
use crate::{ApiError, ApiResult, BoxFut, UrlQuery};
|
||||
use beacon_chain::{AttestationProcessingOutcome, BeaconChainTypes, BlockProcessingOutcome};
|
||||
use bls::{AggregateSignature, PublicKey, Signature};
|
||||
use crate::{ApiError, ApiResult, BoxFut, NetworkChannel, UrlQuery};
|
||||
use beacon_chain::{
|
||||
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BlockProcessingOutcome,
|
||||
};
|
||||
use bls::PublicKey;
|
||||
use futures::future::Future;
|
||||
use futures::stream::Stream;
|
||||
use hyper::{Body, Request};
|
||||
use network::NetworkMessage;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slog::{info, trace, warn};
|
||||
use slog::{info, warn, Logger};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::sync::Arc;
|
||||
use tokio;
|
||||
use tokio::sync::mpsc;
|
||||
use types::beacon_state::EthSpec;
|
||||
use types::{Attestation, BeaconBlock, BitList, CommitteeIndex, Epoch, RelativeEpoch, Slot};
|
||||
use types::{Attestation, BeaconBlock, CommitteeIndex, Epoch, RelativeEpoch, Slot};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ValidatorDuty {
|
||||
/// The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._
|
||||
pub validator_pubkey: String,
|
||||
pub validator_pubkey: PublicKey,
|
||||
/// The slot at which the validator must attest.
|
||||
pub attestation_slot: Option<Slot>,
|
||||
/// The index of the committee within `slot` of which the validator is a member.
|
||||
pub attestation_committee_index: Option<CommitteeIndex>,
|
||||
/// The position of the validator in the committee.
|
||||
pub attestation_committee_position: Option<usize>,
|
||||
/// The slot in which a validator must propose a block, or `null` if block production is not required.
|
||||
pub block_proposal_slot: Option<Slot>,
|
||||
}
|
||||
|
||||
impl ValidatorDuty {
|
||||
pub fn new() -> ValidatorDuty {
|
||||
ValidatorDuty {
|
||||
validator_pubkey: "".to_string(),
|
||||
attestation_slot: None,
|
||||
attestation_committee_index: None,
|
||||
block_proposal_slot: None,
|
||||
}
|
||||
}
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
|
||||
pub struct BulkValidatorDutiesRequest {
|
||||
pub epoch: Epoch,
|
||||
pub pubkeys: Vec<PublicKey>,
|
||||
}
|
||||
|
||||
/// HTTP Handler to retrieve a the duties for a set of validators during a particular epoch. This
|
||||
/// method allows for collecting bulk sets of validator duties without risking exceeding the max
|
||||
/// URL length with query pairs.
|
||||
pub fn post_validator_duties<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> BoxFut {
|
||||
let response_builder = ResponseBuilder::new(&req);
|
||||
|
||||
let future = req
|
||||
.into_body()
|
||||
.concat2()
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}", e)))
|
||||
.and_then(|chunks| {
|
||||
serde_json::from_slice::<BulkValidatorDutiesRequest>(&chunks).map_err(|e| {
|
||||
ApiError::BadRequest(format!(
|
||||
"Unable to parse JSON into BulkValidatorDutiesRequest: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
})
|
||||
.and_then(|bulk_request| {
|
||||
return_validator_duties(beacon_chain, bulk_request.epoch, bulk_request.pubkeys)
|
||||
})
|
||||
.and_then(|duties| response_builder?.body_no_ssz(&duties));
|
||||
|
||||
Box::new(future)
|
||||
}
|
||||
|
||||
/// HTTP Handler to retrieve a the duties for a set of validators during a particular epoch
|
||||
pub fn get_validator_duties<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let log = get_logger_from_request(&req);
|
||||
slog::trace!(log, "Validator duties requested of API: {:?}", &req);
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
let mut head_state = beacon_chain.head().beacon_state;
|
||||
|
||||
slog::trace!(log, "Got head state from request.");
|
||||
// Parse and check query parameters
|
||||
///
|
||||
/// The given `epoch` must be within one epoch of the current epoch.
|
||||
pub fn get_validator_duties<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let query = UrlQuery::from_request(&req)?;
|
||||
let current_epoch = head_state.current_epoch();
|
||||
let epoch = match query.first_of(&["epoch"]) {
|
||||
Ok((_, v)) => {
|
||||
slog::trace!(log, "Requested epoch {:?}", v);
|
||||
Epoch::new(v.parse::<u64>().map_err(|e| {
|
||||
slog::info!(log, "Invalid epoch {:?}", e);
|
||||
ApiError::BadRequest(format!("Invalid epoch parameter, must be a u64. {:?}", e))
|
||||
})?)
|
||||
}
|
||||
Err(_) => {
|
||||
// epoch not supplied, use the current epoch
|
||||
slog::info!(log, "Using default epoch {:?}", current_epoch);
|
||||
current_epoch
|
||||
}
|
||||
};
|
||||
let relative_epoch = RelativeEpoch::from_epoch(current_epoch, epoch).map_err(|e| {
|
||||
slog::info!(log, "Requested epoch out of range.");
|
||||
ApiError::BadRequest(format!(
|
||||
"Cannot get RelativeEpoch, epoch out of range: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
let validators: Vec<PublicKey> = query
|
||||
|
||||
let epoch = query.epoch()?;
|
||||
let validator_pubkeys = query
|
||||
.all_of("validator_pubkeys")?
|
||||
.iter()
|
||||
.map(|pk| parse_pubkey(pk))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut duties: Vec<ValidatorDuty> = Vec::new();
|
||||
.map(|validator_pubkey_str| parse_pubkey(validator_pubkey_str))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Build cache for the requested epoch
|
||||
head_state
|
||||
let duties = return_validator_duties(beacon_chain, epoch, validator_pubkeys)?;
|
||||
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&duties)
|
||||
}
|
||||
|
||||
fn return_validator_duties<T: BeaconChainTypes>(
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
epoch: Epoch,
|
||||
validator_pubkeys: Vec<PublicKey>,
|
||||
) -> Result<Vec<ValidatorDuty>, ApiError> {
|
||||
let mut state = beacon_chain
|
||||
.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!("Unable to load state for epoch {}: {:?}", epoch, e))
|
||||
})?;
|
||||
|
||||
let current_epoch = state.current_epoch();
|
||||
let relative_epoch = RelativeEpoch::from_epoch(current_epoch, epoch).map_err(|_| {
|
||||
ApiError::BadRequest(format!(
|
||||
"Epoch must be within one epoch of the current epoch",
|
||||
))
|
||||
})?;
|
||||
|
||||
state
|
||||
.build_committee_cache(relative_epoch, &beacon_chain.spec)
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to build committee cache: {:?}", e)))?;
|
||||
// Get a list of all validators for this epoch
|
||||
let validator_proposers: Vec<usize> = epoch
|
||||
state
|
||||
.update_pubkey_cache()
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to build pubkey cache: {:?}", e)))?;
|
||||
|
||||
// Get a list of all validators for this epoch.
|
||||
//
|
||||
// Used for quickly determining the slot for a proposer.
|
||||
let validator_proposers: Vec<(usize, Slot)> = epoch
|
||||
.slot_iter(T::EthSpec::slots_per_epoch())
|
||||
.map(|slot| {
|
||||
head_state
|
||||
state
|
||||
.get_beacon_proposer_index(slot, &beacon_chain.spec)
|
||||
.map(|i| (i, slot))
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to get proposer index for validator: {:?}",
|
||||
@@ -98,83 +129,59 @@ pub fn get_validator_duties<T: BeaconChainTypes + 'static>(req: Request<Body>) -
|
||||
))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<usize>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Look up duties for each validator
|
||||
for val_pk in validators {
|
||||
let mut duty = ValidatorDuty::new();
|
||||
duty.validator_pubkey = val_pk.as_hex_string();
|
||||
validator_pubkeys
|
||||
.into_iter()
|
||||
.map(|validator_pubkey| {
|
||||
if let Some(validator_index) =
|
||||
state.get_validator_index(&validator_pubkey).map_err(|e| {
|
||||
ApiError::ServerError(format!("Unable to read pubkey cache: {:?}", e))
|
||||
})?
|
||||
{
|
||||
let duties = state
|
||||
.get_attestation_duties(validator_index, relative_epoch)
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to obtain attestation duties: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get the validator index
|
||||
// If it does not exist in the index, just add a null duty and move on.
|
||||
let val_index: usize = match head_state.get_validator_index(&val_pk) {
|
||||
Ok(Some(i)) => i,
|
||||
Ok(None) => {
|
||||
duties.append(&mut vec![duty]);
|
||||
continue;
|
||||
let block_proposal_slot = validator_proposers
|
||||
.iter()
|
||||
.find(|(i, _slot)| validator_index == *i)
|
||||
.map(|(_i, slot)| *slot);
|
||||
|
||||
Ok(ValidatorDuty {
|
||||
validator_pubkey,
|
||||
attestation_slot: duties.map(|d| d.slot),
|
||||
attestation_committee_index: duties.map(|d| d.index),
|
||||
attestation_committee_position: duties.map(|d| d.committee_position),
|
||||
block_proposal_slot,
|
||||
})
|
||||
} else {
|
||||
Ok(ValidatorDuty {
|
||||
validator_pubkey,
|
||||
attestation_slot: None,
|
||||
attestation_committee_index: None,
|
||||
attestation_committee_position: None,
|
||||
block_proposal_slot: None,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ApiError::ServerError(format!(
|
||||
"Unable to read validator index cache. {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
// Set attestation duties
|
||||
match head_state.get_attestation_duties(val_index, relative_epoch) {
|
||||
Ok(Some(d)) => {
|
||||
duty.attestation_slot = Some(d.slot);
|
||||
duty.attestation_committee_index = Some(d.index);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
return Err(ApiError::ServerError(format!(
|
||||
"unable to read cache for attestation duties: {:?}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// If the validator is to propose a block, identify the slot
|
||||
if let Some(slot) = validator_proposers.iter().position(|&v| val_index == v) {
|
||||
duty.block_proposal_slot = Some(Slot::new(
|
||||
relative_epoch
|
||||
.into_epoch(current_epoch)
|
||||
.start_slot(T::EthSpec::slots_per_epoch())
|
||||
.as_u64()
|
||||
+ slot as u64,
|
||||
));
|
||||
}
|
||||
|
||||
duties.append(&mut vec![duty]);
|
||||
}
|
||||
ResponseBuilder::new(&req)?.body_no_ssz(&duties)
|
||||
})
|
||||
.collect::<Result<Vec<_>, ApiError>>()
|
||||
}
|
||||
|
||||
/// HTTP Handler to produce a new BeaconBlock from the current state, ready to be signed by a validator.
|
||||
pub fn get_new_beacon_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
|
||||
pub fn get_new_beacon_block<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let query = UrlQuery::from_request(&req)?;
|
||||
let slot = query
|
||||
.first_of(&["slot"])
|
||||
.map(|(_key, value)| value)?
|
||||
.parse::<u64>()
|
||||
.map(Slot::from)
|
||||
.map_err(|e| {
|
||||
ApiError::BadRequest(format!("Invalid slot parameter, must be a u64. {:?}", e))
|
||||
})?;
|
||||
let randao_bytes = query
|
||||
.first_of(&["randao_reveal"])
|
||||
.map(|(_key, value)| value)
|
||||
.map(hex::decode)?
|
||||
.map_err(|e| {
|
||||
ApiError::BadRequest(format!("Invalid hex string for randao_reveal: {:?}", e))
|
||||
})?;
|
||||
let randao_reveal = Signature::from_bytes(randao_bytes.as_slice()).map_err(|e| {
|
||||
ApiError::BadRequest(format!("randao_reveal is not a valid signature: {:?}", e))
|
||||
})?;
|
||||
|
||||
let slot = query.slot()?;
|
||||
let randao_reveal = query.randao_reveal()?;
|
||||
|
||||
let (new_block, _state) = beacon_chain
|
||||
.produce_block(randao_reveal, slot)
|
||||
@@ -189,35 +196,21 @@ pub fn get_new_beacon_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -
|
||||
}
|
||||
|
||||
/// HTTP Handler to publish a BeaconBlock, which has been signed by a validator.
|
||||
pub fn publish_beacon_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> BoxFut {
|
||||
pub fn publish_beacon_block<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
network_chan: NetworkChannel,
|
||||
log: Logger,
|
||||
) -> BoxFut {
|
||||
try_future!(check_content_type_for_json(&req));
|
||||
let log = get_logger_from_request(&req);
|
||||
let beacon_chain = try_future!(get_beacon_chain_from_request::<T>(&req));
|
||||
// Get the network sending channel from the request, for later transmission
|
||||
let network_chan = req
|
||||
.extensions()
|
||||
.get::<Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>>()
|
||||
.expect("Should always get the network channel from the request, since we put it in there.")
|
||||
.clone();
|
||||
|
||||
let response_builder = ResponseBuilder::new(&req);
|
||||
|
||||
let body = req.into_body();
|
||||
trace!(
|
||||
log,
|
||||
"Got the request body, now going to parse it into a block."
|
||||
);
|
||||
Box::new(body
|
||||
.concat2()
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}",e)))
|
||||
.map(|chunk| chunk.iter().cloned().collect::<Vec<u8>>())
|
||||
.and_then(|chunks| {
|
||||
serde_json::from_slice(&chunks.as_slice()).map_err(|e| {
|
||||
ApiError::BadRequest(format!(
|
||||
"Unable to deserialize JSON into a BeaconBlock: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
serde_json::from_slice(&chunks).map_err(|e| ApiError::BadRequest(format!("Unable to parse JSON into BeaconBlock: {:?}",e)))
|
||||
})
|
||||
.and_then(move |block: BeaconBlock<T::EthSpec>| {
|
||||
let slot = block.slot;
|
||||
@@ -248,131 +241,34 @@ pub fn publish_beacon_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -
|
||||
}
|
||||
|
||||
/// HTTP Handler to produce a new Attestation from the current state, ready to be signed by a validator.
|
||||
pub fn get_new_attestation<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||
let beacon_chain = get_beacon_chain_from_request::<T>(&req)?;
|
||||
let mut head_state = beacon_chain.head().beacon_state;
|
||||
|
||||
pub fn get_new_attestation<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
) -> ApiResult {
|
||||
let query = UrlQuery::from_request(&req)?;
|
||||
let val_pk_str = query
|
||||
.first_of(&["validator_pubkey"])
|
||||
.map(|(_key, value)| value)?;
|
||||
let val_pk = parse_pubkey(val_pk_str.as_str())?;
|
||||
|
||||
head_state
|
||||
.update_pubkey_cache()
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to build pubkey cache: {:?}", e)))?;
|
||||
// Get the validator index from the supplied public key
|
||||
// If it does not exist in the index, we cannot continue.
|
||||
let val_index = head_state
|
||||
.get_validator_index(&val_pk)
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!("Unable to read validator index cache. {:?}", e))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
ApiError::BadRequest(
|
||||
"The provided validator public key does not correspond to a validator index."
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
let slot = query.slot()?;
|
||||
let index = query.committee_index()?;
|
||||
|
||||
// Build cache for the requested epoch
|
||||
head_state
|
||||
.build_committee_cache(RelativeEpoch::Current, &beacon_chain.spec)
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to build committee cache: {:?}", e)))?;
|
||||
// Get the duties of the validator, to make sure they match up.
|
||||
// If they don't have duties this epoch, then return an error
|
||||
let val_duty = head_state
|
||||
.get_attestation_duties(val_index, RelativeEpoch::Current)
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"unable to read cache for attestation duties: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.ok_or_else(|| ApiError::BadRequest("No validator duties could be found for the requested validator. Cannot provide valid attestation.".into()))?;
|
||||
|
||||
// Check that we are requesting an attestation during the slot where it is relevant.
|
||||
let present_slot = beacon_chain.slot().map_err(|e| ApiError::ServerError(
|
||||
format!("Beacon node is unable to determine present slot, either the state isn't generated or the chain hasn't begun. {:?}", e)
|
||||
))?;
|
||||
if val_duty.slot != present_slot {
|
||||
return Err(ApiError::BadRequest(format!("Validator is only able to request an attestation during the slot they are allocated. Current slot: {:?}, allocated slot: {:?}", head_state.slot, val_duty.slot)));
|
||||
}
|
||||
|
||||
// Parse the POC bit and insert it into the aggregation bits
|
||||
let poc_bit = query
|
||||
.first_of(&["poc_bit"])
|
||||
.map(|(_key, value)| value)?
|
||||
.parse::<bool>()
|
||||
.map_err(|e| {
|
||||
ApiError::BadRequest(format!("Invalid slot parameter, must be a u64. {:?}", e))
|
||||
})?;
|
||||
|
||||
let mut aggregation_bits = BitList::with_capacity(val_duty.committee_len)
|
||||
.expect("An empty BitList should always be created, or we have bigger problems.");
|
||||
aggregation_bits
|
||||
.set(val_duty.committee_position, poc_bit)
|
||||
.map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to set aggregation bits for the attestation: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Allow a provided slot parameter to check against the expected slot as a sanity check only.
|
||||
// Presently, we don't support attestations at future or past slots.
|
||||
let requested_slot = query
|
||||
.first_of(&["slot"])
|
||||
.map(|(_key, value)| value)?
|
||||
.parse::<u64>()
|
||||
.map(Slot::from)
|
||||
.map_err(|e| {
|
||||
ApiError::BadRequest(format!("Invalid slot parameter, must be a u64. {:?}", e))
|
||||
})?;
|
||||
let current_slot = beacon_chain.head().beacon_state.slot.as_u64();
|
||||
if requested_slot != current_slot {
|
||||
return Err(ApiError::BadRequest(format!("Attestation data can only be requested for the current slot ({:?}), not your requested slot ({:?})", current_slot, requested_slot)));
|
||||
}
|
||||
|
||||
let index = query
|
||||
.first_of(&["index"])
|
||||
.map(|(_key, value)| value)?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| ApiError::BadRequest(format!("Index is not a valid u64 value: {:?}", e)))?;
|
||||
|
||||
let attestation_data = beacon_chain
|
||||
.produce_attestation_data(current_slot.into(), index)
|
||||
.map_err(|e| ApiError::ServerError(format!("Could not produce an attestation: {:?}", e)))?;
|
||||
|
||||
let attestation: Attestation<T::EthSpec> = Attestation {
|
||||
aggregation_bits,
|
||||
data: attestation_data,
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
let attestation = beacon_chain
|
||||
.produce_attestation(slot, index)
|
||||
.map_err(|e| ApiError::BadRequest(format!("Unable to produce attestation: {:?}", e)))?;
|
||||
|
||||
ResponseBuilder::new(&req)?.body(&attestation)
|
||||
}
|
||||
|
||||
/// HTTP Handler to publish an Attestation, which has been signed by a validator.
|
||||
pub fn publish_attestation<T: BeaconChainTypes + 'static>(req: Request<Body>) -> BoxFut {
|
||||
pub fn publish_attestation<T: BeaconChainTypes>(
|
||||
req: Request<Body>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
network_chan: NetworkChannel,
|
||||
log: Logger,
|
||||
) -> BoxFut {
|
||||
try_future!(check_content_type_for_json(&req));
|
||||
let log = get_logger_from_request(&req);
|
||||
let beacon_chain = try_future!(get_beacon_chain_from_request::<T>(&req));
|
||||
// Get the network sending channel from the request, for later transmission
|
||||
let network_chan = req
|
||||
.extensions()
|
||||
.get::<Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>>()
|
||||
.expect("Should always get the network channel from the request, since we put it in there.")
|
||||
.clone();
|
||||
|
||||
let response_builder = ResponseBuilder::new(&req);
|
||||
|
||||
let body = req.into_body();
|
||||
trace!(
|
||||
log,
|
||||
"Got the request body, now going to parse it into an attesation."
|
||||
);
|
||||
Box::new(body
|
||||
Box::new(req
|
||||
.into_body()
|
||||
.concat2()
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}",e)))
|
||||
.map(|chunk| chunk.iter().cloned().collect::<Vec<u8>>())
|
||||
|
||||
571
beacon_node/rest_api/tests/test.rs
Normal file
571
beacon_node/rest_api/tests/test.rs
Normal file
@@ -0,0 +1,571 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use node_test_rig::{
|
||||
environment::{Environment, EnvironmentBuilder},
|
||||
testing_client_config, ClientGenesis, LocalBeaconNode,
|
||||
};
|
||||
use remote_beacon_node::{PublishStatus, ValidatorDuty};
|
||||
use std::sync::Arc;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypair, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec,
|
||||
MinimalEthSpec, PublicKey, RelativeEpoch, Signature, Slot,
|
||||
};
|
||||
use version;
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/// Returns the randao reveal for the given slot (assuming the given `beacon_chain` uses
|
||||
/// deterministic keypairs).
|
||||
fn get_randao_reveal<T: BeaconChainTypes>(
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Signature {
|
||||
let fork = beacon_chain.head().beacon_state.fork.clone();
|
||||
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)
|
||||
}
|
||||
|
||||
/// Signs the given block (assuming the given `beacon_chain` uses deterministic keypairs).
|
||||
fn sign_block<T: BeaconChainTypes>(
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
block: &mut BeaconBlock<T::EthSpec>,
|
||||
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);
|
||||
block.sign(&keypair.sk, &fork, spec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_produce_attestation() {
|
||||
let mut env = build_env();
|
||||
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
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 state = beacon_chain.head().beacon_state.clone();
|
||||
|
||||
let validator_index = 0;
|
||||
let duties = state
|
||||
.get_attestation_duties(validator_index, RelativeEpoch::Current)
|
||||
.expect("should have attestation duties cache")
|
||||
.expect("should have attestation duties");
|
||||
|
||||
let mut attestation = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.produce_attestation(duties.slot, duties.index),
|
||||
)
|
||||
.expect("should fetch attestation from http api");
|
||||
|
||||
assert_eq!(
|
||||
attestation.data.index, duties.index,
|
||||
"should have same index"
|
||||
);
|
||||
assert_eq!(attestation.data.slot, duties.slot, "should have same slot");
|
||||
assert_eq!(
|
||||
attestation.aggregation_bits.num_set_bits(),
|
||||
0,
|
||||
"should have empty aggregation bits"
|
||||
);
|
||||
|
||||
let keypair = generate_deterministic_keypair(validator_index);
|
||||
|
||||
// Fetch the duties again, but via HTTP for authenticity.
|
||||
let duties = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.validator().get_duties(
|
||||
attestation.data.slot.epoch(E::slots_per_epoch()),
|
||||
&[keypair.pk.clone()],
|
||||
))
|
||||
.expect("should fetch duties from http api");
|
||||
let duties = &duties[0];
|
||||
|
||||
// Try publishing the attestation without a signature, ensure it is flagged as invalid.
|
||||
let publish_status = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.publish_attestation(attestation.clone()),
|
||||
)
|
||||
.expect("should publish attestation");
|
||||
assert!(
|
||||
!publish_status.is_valid(),
|
||||
"the unsigned published attestation should not be valid"
|
||||
);
|
||||
|
||||
attestation
|
||||
.sign(
|
||||
&keypair.sk,
|
||||
duties
|
||||
.attestation_committee_position
|
||||
.expect("should have committee position"),
|
||||
&state.fork,
|
||||
spec,
|
||||
)
|
||||
.expect("should sign attestation");
|
||||
|
||||
// Try publishing the valid attestation.
|
||||
let publish_status = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.publish_attestation(attestation.clone()),
|
||||
)
|
||||
.expect("should publish attestation");
|
||||
assert!(
|
||||
publish_status.is_valid(),
|
||||
"the signed published attestation should be valid"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_duties_bulk() {
|
||||
let mut env = build_env();
|
||||
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
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 epoch = Epoch::new(0);
|
||||
|
||||
let validators = beacon_chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| v.pubkey.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let duties = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.get_duties_bulk(epoch, &validators),
|
||||
)
|
||||
.expect("should fetch duties from http api");
|
||||
|
||||
check_duties(duties, epoch, validators, beacon_chain, spec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_duties() {
|
||||
let mut env = build_env();
|
||||
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
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 epoch = Epoch::new(0);
|
||||
|
||||
let validators = beacon_chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| v.pubkey.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let duties = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.validator().get_duties(epoch, &validators))
|
||||
.expect("should fetch duties from http api");
|
||||
|
||||
check_duties(duties, epoch, validators, beacon_chain, spec);
|
||||
}
|
||||
|
||||
fn check_duties<T: BeaconChainTypes>(
|
||||
duties: Vec<ValidatorDuty>,
|
||||
epoch: Epoch,
|
||||
validators: Vec<PublicKey>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
assert_eq!(
|
||||
validators.len(),
|
||||
duties.len(),
|
||||
"there should be a duty for each validator"
|
||||
);
|
||||
|
||||
let state = beacon_chain.head().beacon_state.clone();
|
||||
|
||||
validators
|
||||
.iter()
|
||||
.zip(duties.iter())
|
||||
.for_each(|(validator, duty)| {
|
||||
assert_eq!(*validator, duty.validator_pubkey, "pubkey should match");
|
||||
|
||||
let validator_index = state
|
||||
.get_validator_index(validator)
|
||||
.expect("should have pubkey cache")
|
||||
.expect("pubkey should exist");
|
||||
|
||||
let attestation_duty = state
|
||||
.get_attestation_duties(validator_index, RelativeEpoch::Current)
|
||||
.expect("should have attestation duties cache")
|
||||
.expect("should have attestation duties");
|
||||
|
||||
assert_eq!(
|
||||
Some(attestation_duty.slot),
|
||||
duty.attestation_slot,
|
||||
"attestation slot should match"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(attestation_duty.index),
|
||||
duty.attestation_committee_index,
|
||||
"attestation index should match"
|
||||
);
|
||||
|
||||
if let Some(slot) = duty.block_proposal_slot {
|
||||
let expected_proposer = state
|
||||
.get_beacon_proposer_index(slot, spec)
|
||||
.expect("should know proposer");
|
||||
assert_eq!(
|
||||
expected_proposer, validator_index,
|
||||
"should get correct proposal slot"
|
||||
);
|
||||
} else {
|
||||
epoch.slot_iter(E::slots_per_epoch()).for_each(|slot| {
|
||||
let slot_proposer = state
|
||||
.get_beacon_proposer_index(slot, spec)
|
||||
.expect("should know proposer");
|
||||
assert!(
|
||||
slot_proposer != validator_index,
|
||||
"validator should not have proposal slot in this epoch"
|
||||
)
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_block_post() {
|
||||
let mut env = build_env();
|
||||
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let mut config = testing_client_config();
|
||||
config.genesis = ClientGenesis::Interop {
|
||||
validator_count: 8,
|
||||
genesis_time: 13_371_337,
|
||||
};
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), config);
|
||||
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 slot = Slot::new(1);
|
||||
let randao_reveal = get_randao_reveal(beacon_chain.clone(), slot, spec);
|
||||
|
||||
let mut block = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.produce_block(slot, randao_reveal.clone()),
|
||||
)
|
||||
.expect("should fetch block from http api");
|
||||
|
||||
// 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.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,
|
||||
PublishStatus::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() {
|
||||
let mut env = build_env();
|
||||
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
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 slot = Slot::new(1);
|
||||
let randao_reveal = get_randao_reveal(beacon_chain.clone(), slot, spec);
|
||||
|
||||
let block = env
|
||||
.runtime()
|
||||
.block_on(
|
||||
remote_node
|
||||
.http
|
||||
.validator()
|
||||
.produce_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(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let (state_by_slot, root) = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.beacon().get_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().get_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(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let (block_by_slot, root) = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.beacon().get_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().get_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"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genesis_time() {
|
||||
let mut env = build_env();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let genesis_time = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.beacon().get_genesis_time())
|
||||
.expect("should fetch genesis time from http api");
|
||||
|
||||
assert_eq!(
|
||||
node.client
|
||||
.beacon_chain()
|
||||
.expect("should have beacon chain")
|
||||
.head()
|
||||
.beacon_state
|
||||
.genesis_time,
|
||||
genesis_time,
|
||||
"should match genesis time from head state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fork() {
|
||||
let mut env = build_env();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let fork = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.beacon().get_fork())
|
||||
.expect("should fetch from http api");
|
||||
|
||||
assert_eq!(
|
||||
node.client
|
||||
.beacon_chain()
|
||||
.expect("should have beacon chain")
|
||||
.head()
|
||||
.beacon_state
|
||||
.fork,
|
||||
fork,
|
||||
"should match head state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eth2_config() {
|
||||
let mut env = build_env();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let eth2_config = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.spec().get_eth2_config())
|
||||
.expect("should fetch eth2 config from http api");
|
||||
|
||||
// TODO: check the entire eth2_config, not just the spec.
|
||||
|
||||
assert_eq!(
|
||||
node.client
|
||||
.beacon_chain()
|
||||
.expect("should have beacon chain")
|
||||
.spec,
|
||||
eth2_config.spec,
|
||||
"should match genesis time from head state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_version() {
|
||||
let mut env = build_env();
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let version = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.node().get_version())
|
||||
.expect("should fetch eth2 config from http api");
|
||||
|
||||
assert_eq!(version::version(), version, "result should be as expected");
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "rpc"
|
||||
version = "0.1.0"
|
||||
authors = ["Age Manning <Age@AgeManning.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../../eth2/utils/bls" }
|
||||
beacon_chain = { path = "../beacon_chain" }
|
||||
network = { path = "../network" }
|
||||
eth2-libp2p = { path = "../eth2-libp2p" }
|
||||
version = { path = "../version" }
|
||||
types = { path = "../../eth2/types" }
|
||||
eth2_ssz = "0.1.2"
|
||||
protos = { path = "../../protos" }
|
||||
grpcio = { version = "0.4.6", default-features = false, features = ["protobuf-codec"] }
|
||||
clap = "2.33.0"
|
||||
futures = "0.1.29"
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||
tokio = "0.1.22"
|
||||
exit-future = "0.1.4"
|
||||
@@ -1,177 +0,0 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use eth2_libp2p::PubsubMessage;
|
||||
use eth2_libp2p::Topic;
|
||||
use eth2_libp2p::{BEACON_ATTESTATION_TOPIC, TOPIC_ENCODING_POSTFIX, TOPIC_PREFIX};
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||
use network::NetworkMessage;
|
||||
use protos::services::{
|
||||
AttestationData as AttestationDataProto, ProduceAttestationDataRequest,
|
||||
ProduceAttestationDataResponse, PublishAttestationRequest, PublishAttestationResponse,
|
||||
};
|
||||
use protos::services_grpc::AttestationService;
|
||||
use slog::{error, info, trace, warn};
|
||||
use ssz::{ssz_encode, Decode, Encode};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use types::{Attestation, Slot};
|
||||
|
||||
pub struct AttestationServiceInstance<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub network_chan: mpsc::UnboundedSender<NetworkMessage>,
|
||||
pub log: slog::Logger,
|
||||
}
|
||||
|
||||
// NOTE: Deriving Clone puts bogus bounds on T, so we implement it manually.
|
||||
impl<T: BeaconChainTypes> Clone for AttestationServiceInstance<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
chain: self.chain.clone(),
|
||||
network_chan: self.network_chan.clone(),
|
||||
log: self.log.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> AttestationService for AttestationServiceInstance<T> {
|
||||
/// Produce the `AttestationData` for signing by a validator.
|
||||
fn produce_attestation_data(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: ProduceAttestationDataRequest,
|
||||
sink: UnarySink<ProduceAttestationDataResponse>,
|
||||
) {
|
||||
trace!(
|
||||
&self.log,
|
||||
"Attempting to produce attestation at slot {}",
|
||||
req.get_slot()
|
||||
);
|
||||
|
||||
// Then get the AttestationData from the beacon chain
|
||||
// NOTE(v0.9): shard is incorrectly named, all this should be deleted
|
||||
let shard = req.get_shard();
|
||||
let slot_requested = req.get_slot();
|
||||
let attestation_data = match self
|
||||
.chain
|
||||
.produce_attestation_data(Slot::from(slot_requested), shard)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
// Could not produce an attestation
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::Unknown,
|
||||
Some(format!("Could not produce an attestation: {:?}", e)),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
let mut attestation_data_proto = AttestationDataProto::new();
|
||||
attestation_data_proto.set_ssz(ssz_encode(&attestation_data));
|
||||
|
||||
let mut resp = ProduceAttestationDataResponse::new();
|
||||
resp.set_attestation_data(attestation_data_proto);
|
||||
|
||||
let error_log = self.log.clone();
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| error!(error_log, "Failed to reply with success {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
|
||||
/// Accept some fully-formed `FreeAttestation` from the validator,
|
||||
/// store it, and aggregate it into an `Attestation`.
|
||||
fn publish_attestation(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: PublishAttestationRequest,
|
||||
sink: UnarySink<PublishAttestationResponse>,
|
||||
) {
|
||||
trace!(self.log, "Publishing attestation");
|
||||
|
||||
let mut resp = PublishAttestationResponse::new();
|
||||
let ssz_serialized_attestation = req.get_attestation().get_ssz();
|
||||
|
||||
let attestation = match Attestation::from_ssz_bytes(ssz_serialized_attestation) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::InvalidArgument,
|
||||
Some("Invalid attestation".to_string()),
|
||||
))
|
||||
.map_err(move |_| warn!(log_clone, "failed to reply {:?}", req));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
match self.chain.process_attestation(attestation.clone()) {
|
||||
Ok(_) => {
|
||||
// Attestation was successfully processed.
|
||||
info!(
|
||||
self.log,
|
||||
"Valid attestation from RPC";
|
||||
"target_epoch" => attestation.data.target.epoch,
|
||||
"index" => attestation.data.index,
|
||||
);
|
||||
|
||||
// valid attestation, propagate to the network
|
||||
let topic_string = format!(
|
||||
"/{}/{}/{}",
|
||||
TOPIC_PREFIX, BEACON_ATTESTATION_TOPIC, TOPIC_ENCODING_POSTFIX
|
||||
);
|
||||
let topic = Topic::new(topic_string);
|
||||
let message = PubsubMessage::Attestation(attestation.as_ssz_bytes());
|
||||
|
||||
self.network_chan
|
||||
.try_send(NetworkMessage::Publish {
|
||||
topics: vec![topic],
|
||||
message,
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
error!(
|
||||
self.log,
|
||||
"Failed to gossip attestation";
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
});
|
||||
|
||||
resp.set_success(true);
|
||||
}
|
||||
Err(BeaconChainError::AttestationValidationError(e)) => {
|
||||
// Attestation was invalid
|
||||
warn!(
|
||||
self.log,
|
||||
"Invalid attestation from RPC";
|
||||
"error" => format!("{:?}", e),
|
||||
);
|
||||
resp.set_success(false);
|
||||
resp.set_msg(format!("InvalidAttestation: {:?}", e).as_bytes().to_vec());
|
||||
}
|
||||
Err(e) => {
|
||||
// Some other error
|
||||
warn!(
|
||||
self.log,
|
||||
"Failed to process attestation from RPC";
|
||||
"error" => format!("{:?}", e),
|
||||
);
|
||||
resp.set_success(false);
|
||||
resp.set_msg(
|
||||
format!("There was a beacon chain error: {:?}", e)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let error_log = self.log.clone();
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| error!(error_log, "failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
||||
use eth2_libp2p::{PubsubMessage, Topic};
|
||||
use eth2_libp2p::{BEACON_BLOCK_TOPIC, TOPIC_ENCODING_POSTFIX, TOPIC_PREFIX};
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||
use network::NetworkMessage;
|
||||
use protos::services::{
|
||||
BeaconBlock as BeaconBlockProto, ProduceBeaconBlockRequest, ProduceBeaconBlockResponse,
|
||||
PublishBeaconBlockRequest, PublishBeaconBlockResponse,
|
||||
};
|
||||
use protos::services_grpc::BeaconBlockService;
|
||||
use slog::Logger;
|
||||
use slog::{error, info, trace, warn};
|
||||
use ssz::{ssz_encode, Decode, Encode};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use types::{BeaconBlock, Signature, Slot};
|
||||
|
||||
pub struct BeaconBlockServiceInstance<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub network_chan: mpsc::UnboundedSender<NetworkMessage>,
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
// NOTE: Deriving Clone puts bogus bounds on T, so we implement it manually.
|
||||
impl<T: BeaconChainTypes> Clone for BeaconBlockServiceInstance<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
chain: self.chain.clone(),
|
||||
network_chan: self.network_chan.clone(),
|
||||
log: self.log.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconBlockService for BeaconBlockServiceInstance<T> {
|
||||
/// Produce a `BeaconBlock` for signing by a validator.
|
||||
fn produce_beacon_block(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: ProduceBeaconBlockRequest,
|
||||
sink: UnarySink<ProduceBeaconBlockResponse>,
|
||||
) {
|
||||
trace!(self.log, "Generating a beacon block"; "req" => format!("{:?}", req));
|
||||
|
||||
// decode the request
|
||||
let requested_slot = Slot::from(req.get_slot());
|
||||
let randao_reveal = match Signature::from_ssz_bytes(req.get_randao_reveal()) {
|
||||
Ok(reveal) => reveal,
|
||||
Err(_) => {
|
||||
// decode error, incorrect signature
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::InvalidArgument,
|
||||
Some("Invalid randao reveal signature".to_string()),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
let produced_block = match self.chain.produce_block(randao_reveal, requested_slot) {
|
||||
Ok((block, _state)) => block,
|
||||
Err(e) => {
|
||||
// could not produce a block
|
||||
let log_clone = self.log.clone();
|
||||
warn!(self.log, "RPC Error"; "Error" => format!("Could not produce a block:{:?}",e));
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::Unknown,
|
||||
Some(format!("Could not produce a block: {:?}", e)),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
produced_block.slot, requested_slot,
|
||||
"should produce at the requested slot"
|
||||
);
|
||||
|
||||
let mut block = BeaconBlockProto::new();
|
||||
block.set_ssz(ssz_encode(&produced_block));
|
||||
|
||||
let mut resp = ProduceBeaconBlockResponse::new();
|
||||
resp.set_block(block);
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
|
||||
/// Accept some fully-formed `BeaconBlock`, process and publish it.
|
||||
fn publish_beacon_block(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: PublishBeaconBlockRequest,
|
||||
sink: UnarySink<PublishBeaconBlockResponse>,
|
||||
) {
|
||||
trace!(&self.log, "Attempting to publish a block");
|
||||
|
||||
let mut resp = PublishBeaconBlockResponse::new();
|
||||
|
||||
let ssz_serialized_block = req.get_block().get_ssz();
|
||||
|
||||
match BeaconBlock::from_ssz_bytes(ssz_serialized_block) {
|
||||
Ok(block) => {
|
||||
match self.chain.process_block(block.clone()) {
|
||||
Ok(outcome) => {
|
||||
if let BlockProcessingOutcome::Processed { block_root } = outcome {
|
||||
// Block was successfully processed.
|
||||
info!(
|
||||
self.log,
|
||||
"Valid block from RPC";
|
||||
"root" => format!("{}", block_root),
|
||||
"slot" => block.slot,
|
||||
);
|
||||
|
||||
// create the network topic to send on
|
||||
let topic_string = format!(
|
||||
"/{}/{}/{}",
|
||||
TOPIC_PREFIX, BEACON_BLOCK_TOPIC, TOPIC_ENCODING_POSTFIX
|
||||
);
|
||||
let topic = Topic::new(topic_string);
|
||||
let message = PubsubMessage::Block(block.as_ssz_bytes());
|
||||
|
||||
// Publish the block to the p2p network via gossipsub.
|
||||
self.network_chan
|
||||
.try_send(NetworkMessage::Publish {
|
||||
topics: vec![topic],
|
||||
message,
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
error!(
|
||||
self.log,
|
||||
"Failed to gossip beacon block";
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
});
|
||||
|
||||
resp.set_success(true);
|
||||
} else {
|
||||
// Block was not successfully processed.
|
||||
warn!(
|
||||
self.log,
|
||||
"Invalid block from RPC";
|
||||
"outcome" => format!("{:?}", outcome)
|
||||
);
|
||||
|
||||
resp.set_success(false);
|
||||
resp.set_msg(
|
||||
format!("InvalidBlock: {:?}", outcome).as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Some failure during processing.
|
||||
error!(
|
||||
self.log,
|
||||
"Failed to process beacon block";
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
|
||||
resp.set_success(false);
|
||||
resp.set_msg(format!("failed_to_process: {:?}", e).as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
resp.set_success(true);
|
||||
}
|
||||
Err(_) => {
|
||||
resp.set_success(false);
|
||||
resp.set_msg(b"Invalid SSZ".to_vec());
|
||||
}
|
||||
};
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, UnarySink};
|
||||
use protos::services::{Empty, Fork, NodeInfoResponse};
|
||||
use protos::services_grpc::BeaconNodeService;
|
||||
use slog::{trace, warn};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BeaconNodeServiceInstance<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub log: slog::Logger,
|
||||
}
|
||||
|
||||
// NOTE: Deriving Clone puts bogus bounds on T, so we implement it manually.
|
||||
impl<T: BeaconChainTypes> Clone for BeaconNodeServiceInstance<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
chain: self.chain.clone(),
|
||||
log: self.log.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconNodeService for BeaconNodeServiceInstance<T> {
|
||||
/// Provides basic node information.
|
||||
fn info(&mut self, ctx: RpcContext, _req: Empty, sink: UnarySink<NodeInfoResponse>) {
|
||||
trace!(self.log, "Node info requested via RPC");
|
||||
|
||||
// build the response
|
||||
let mut node_info = NodeInfoResponse::new();
|
||||
node_info.set_version(version::version());
|
||||
|
||||
// get the chain state
|
||||
let state = &self.chain.head().beacon_state;
|
||||
let state_fork = state.fork.clone();
|
||||
let genesis_time = state.genesis_time;
|
||||
|
||||
// build the rpc fork struct
|
||||
let mut fork = Fork::new();
|
||||
fork.set_previous_version(state_fork.previous_version.to_vec());
|
||||
fork.set_current_version(state_fork.current_version.to_vec());
|
||||
fork.set_epoch(state_fork.epoch.into());
|
||||
|
||||
let spec = &self.chain.spec;
|
||||
|
||||
node_info.set_fork(fork);
|
||||
node_info.set_genesis_time(genesis_time);
|
||||
node_info.set_genesis_slot(spec.genesis_slot.as_u64());
|
||||
node_info.set_network_id(u32::from(spec.network_id));
|
||||
|
||||
// send the node_info the requester
|
||||
let error_log = self.log.clone();
|
||||
let f = sink
|
||||
.success(node_info)
|
||||
.map_err(move |e| warn!(error_log, "failed to reply {:?}", e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
/// RPC Configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// Enable the RPC server.
|
||||
pub enabled: bool,
|
||||
/// The IPv4 address the RPC will listen on.
|
||||
pub listen_address: Ipv4Addr,
|
||||
/// The port the RPC will listen on.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
enabled: true,
|
||||
listen_address: Ipv4Addr::new(127, 0, 0, 1),
|
||||
port: 5051,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
|
||||
if args.is_present("no-grpc") {
|
||||
self.enabled = false;
|
||||
}
|
||||
|
||||
if let Some(rpc_address) = args.value_of("rpc-address") {
|
||||
self.listen_address = rpc_address
|
||||
.parse::<Ipv4Addr>()
|
||||
.map_err(|_| "rpc-address is not IPv4 address")?;
|
||||
}
|
||||
|
||||
if let Some(rpc_port) = args.value_of("rpc-port") {
|
||||
self.port = rpc_port.parse::<u16>().map_err(|_| "rpc-port is not u16")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
mod attestation;
|
||||
mod beacon_block;
|
||||
mod beacon_node;
|
||||
pub mod config;
|
||||
mod validator;
|
||||
|
||||
use self::attestation::AttestationServiceInstance;
|
||||
use self::beacon_block::BeaconBlockServiceInstance;
|
||||
use self::beacon_node::BeaconNodeServiceInstance;
|
||||
use self::validator::ValidatorServiceInstance;
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
pub use config::Config;
|
||||
use futures::Future;
|
||||
use grpcio::{Environment, ServerBuilder};
|
||||
use network::NetworkMessage;
|
||||
use protos::services_grpc::{
|
||||
create_attestation_service, create_beacon_block_service, create_beacon_node_service,
|
||||
create_validator_service,
|
||||
};
|
||||
use slog::{info, warn};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub fn start_server<T: BeaconChainTypes>(
|
||||
config: &Config,
|
||||
executor: &TaskExecutor,
|
||||
network_chan: mpsc::UnboundedSender<NetworkMessage>,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
log: slog::Logger,
|
||||
) -> exit_future::Signal {
|
||||
let env = Arc::new(Environment::new(1));
|
||||
|
||||
// build a channel to kill the rpc server
|
||||
let (rpc_exit_signal, rpc_exit) = exit_future::signal();
|
||||
|
||||
// build the individual rpc services
|
||||
let beacon_node_service = {
|
||||
let instance = BeaconNodeServiceInstance {
|
||||
chain: beacon_chain.clone(),
|
||||
log: log.clone(),
|
||||
};
|
||||
create_beacon_node_service(instance)
|
||||
};
|
||||
|
||||
let beacon_block_service = {
|
||||
let instance = BeaconBlockServiceInstance {
|
||||
chain: beacon_chain.clone(),
|
||||
network_chan: network_chan.clone(),
|
||||
log: log.clone(),
|
||||
};
|
||||
create_beacon_block_service(instance)
|
||||
};
|
||||
let validator_service = {
|
||||
let instance = ValidatorServiceInstance {
|
||||
chain: beacon_chain.clone(),
|
||||
log: log.clone(),
|
||||
};
|
||||
create_validator_service(instance)
|
||||
};
|
||||
let attestation_service = {
|
||||
let instance = AttestationServiceInstance {
|
||||
network_chan,
|
||||
chain: beacon_chain.clone(),
|
||||
log: log.clone(),
|
||||
};
|
||||
create_attestation_service(instance)
|
||||
};
|
||||
|
||||
let mut server = ServerBuilder::new(env)
|
||||
.register_service(beacon_block_service)
|
||||
.register_service(validator_service)
|
||||
.register_service(beacon_node_service)
|
||||
.register_service(attestation_service)
|
||||
.bind(config.listen_address.to_string(), config.port)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let spawn_rpc = {
|
||||
server.start();
|
||||
for &(ref host, port) in server.bind_addrs() {
|
||||
info!(
|
||||
log,
|
||||
"gRPC API started";
|
||||
"port" => port,
|
||||
"host" => host,
|
||||
);
|
||||
}
|
||||
rpc_exit.and_then(move |_| {
|
||||
info!(log, "RPC Server shutting down");
|
||||
server
|
||||
.shutdown()
|
||||
.wait()
|
||||
.map(|_| ())
|
||||
.map_err(|e| warn!(log, "RPC server failed to shutdown: {:?}", e))?;
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
executor.spawn(spawn_rpc);
|
||||
rpc_exit_signal
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use bls::PublicKey;
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||
use protos::services::{ActiveValidator, GetDutiesRequest, GetDutiesResponse, ValidatorDuty};
|
||||
use protos::services_grpc::ValidatorService;
|
||||
use slog::{trace, warn};
|
||||
use ssz::Decode;
|
||||
use std::sync::Arc;
|
||||
use types::{Epoch, EthSpec, RelativeEpoch};
|
||||
|
||||
pub struct ValidatorServiceInstance<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub log: slog::Logger,
|
||||
}
|
||||
|
||||
// NOTE: Deriving Clone puts bogus bounds on T, so we implement it manually.
|
||||
impl<T: BeaconChainTypes> Clone for ValidatorServiceInstance<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
chain: self.chain.clone(),
|
||||
log: self.log.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
||||
/// For a list of validator public keys, this function returns the slot at which each
|
||||
/// validator must propose a block, attest to a shard, their shard committee and the shard they
|
||||
/// need to attest to.
|
||||
fn get_validator_duties(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: GetDutiesRequest,
|
||||
sink: UnarySink<GetDutiesResponse>,
|
||||
) {
|
||||
trace!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch());
|
||||
let validators = req.get_validators();
|
||||
|
||||
let epoch = Epoch::from(req.get_epoch());
|
||||
let slot = epoch.start_slot(T::EthSpec::slots_per_epoch());
|
||||
|
||||
let mut state = if let Ok(state) = self.chain.state_at_slot(slot) {
|
||||
state.clone()
|
||||
} else {
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::FailedPrecondition,
|
||||
Some("No state".to_string()),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
};
|
||||
|
||||
let _ = state.build_all_caches(&self.chain.spec);
|
||||
|
||||
assert_eq!(
|
||||
state.current_epoch(),
|
||||
epoch,
|
||||
"Retrieved state should be from the same epoch"
|
||||
);
|
||||
|
||||
let mut resp = GetDutiesResponse::new();
|
||||
let resp_validators = resp.mut_active_validators();
|
||||
|
||||
let validator_proposers: Result<Vec<usize>, _> = epoch
|
||||
.slot_iter(T::EthSpec::slots_per_epoch())
|
||||
.map(|slot| state.get_beacon_proposer_index(slot, &self.chain.spec))
|
||||
.collect();
|
||||
let validator_proposers = match validator_proposers {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
// could not get the validator proposer index
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::FailedPrecondition,
|
||||
Some(format!("Could not find beacon proposers: {:?}", e)),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "failed to reply {:?} : {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
// get the duties for each validator
|
||||
for validator_pk in validators.get_public_keys() {
|
||||
let mut active_validator = ActiveValidator::new();
|
||||
|
||||
let public_key = match PublicKey::from_ssz_bytes(validator_pk) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::InvalidArgument,
|
||||
Some("Invalid public_key".to_string()),
|
||||
))
|
||||
.map_err(move |_| warn!(log_clone, "failed to reply {:?}", req));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
// get the validator index
|
||||
let val_index = match state.get_validator_index(&public_key) {
|
||||
Ok(Some(index)) => index,
|
||||
Ok(None) => {
|
||||
// index not present in registry, set the duties for this key to None
|
||||
warn!(
|
||||
self.log,
|
||||
"RPC requested a public key that is not in the registry: {:?}", public_key
|
||||
);
|
||||
active_validator.set_none(false);
|
||||
resp_validators.push(active_validator);
|
||||
continue;
|
||||
}
|
||||
// the cache is not built, throw an error
|
||||
Err(e) => {
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::FailedPrecondition,
|
||||
Some(format!("Beacon state error {:?}", e)),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "Failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
// get attestation duties and check if validator is active
|
||||
let attestation_duties = match state
|
||||
.get_attestation_duties(val_index, RelativeEpoch::Current)
|
||||
{
|
||||
Ok(Some(v)) => v,
|
||||
Ok(_) => {
|
||||
// validator is inactive, go to the next validator
|
||||
warn!(
|
||||
self.log,
|
||||
"RPC requested an inactive validator key: {:?}", public_key
|
||||
);
|
||||
active_validator.set_none(false);
|
||||
resp_validators.push(active_validator);
|
||||
continue;
|
||||
}
|
||||
// the cache is not built, throw an error
|
||||
Err(e) => {
|
||||
let log_clone = self.log.clone();
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::FailedPrecondition,
|
||||
Some(format!("Beacon state error {:?}", e)),
|
||||
))
|
||||
.map_err(move |e| warn!(log_clone, "Failed to reply {:?}: {:?}", req, e));
|
||||
return ctx.spawn(f);
|
||||
}
|
||||
};
|
||||
|
||||
// we have an active validator, set its duties
|
||||
let mut duty = ValidatorDuty::new();
|
||||
|
||||
// check if the validator needs to propose a block
|
||||
if let Some(slot) = validator_proposers.iter().position(|&v| val_index == v) {
|
||||
duty.set_block_production_slot(
|
||||
epoch.start_slot(T::EthSpec::slots_per_epoch()).as_u64() + slot as u64,
|
||||
);
|
||||
} else {
|
||||
// no blocks to propose this epoch
|
||||
duty.set_none(false)
|
||||
}
|
||||
|
||||
duty.set_committee_index(attestation_duties.committee_position as u64);
|
||||
duty.set_attestation_slot(attestation_duties.slot.as_u64());
|
||||
duty.set_attestation_shard(attestation_duties.index);
|
||||
duty.set_committee_len(attestation_duties.committee_len as u64);
|
||||
|
||||
active_validator.set_duty(duty);
|
||||
resp_validators.push(active_validator);
|
||||
}
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,14 @@
|
||||
use clap::{App, Arg, SubCommand};
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("Beacon Node")
|
||||
.visible_aliases(&["b", "bn", "beacon", "beacon_node"])
|
||||
App::new("beacon_node")
|
||||
.visible_aliases(&["b", "bn", "beacon"])
|
||||
.version(crate_version!())
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.about("Eth 2.0 Client")
|
||||
/*
|
||||
* Configuration directory locations.
|
||||
*/
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("network-dir")
|
||||
.long("network-dir")
|
||||
@@ -103,30 +95,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.help("A secp256k1 secret key, represented as ASCII-encoded hex bytes (with or without 0x prefix).")
|
||||
.takes_value(true),
|
||||
)
|
||||
/*
|
||||
* gRPC parameters.
|
||||
*/
|
||||
.arg(
|
||||
Arg::with_name("no-grpc")
|
||||
.long("no-grpc")
|
||||
.help("Disable the gRPC server.")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rpc-address")
|
||||
.long("rpc-address")
|
||||
.value_name("ADDRESS")
|
||||
.help("Listen address for RPC endpoint.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rpc-port")
|
||||
.long("rpc-port")
|
||||
.value_name("PORT")
|
||||
.help("Listen port for RPC endpoint.")
|
||||
.conflicts_with("port-bump")
|
||||
.takes_value(true),
|
||||
)
|
||||
/* REST API related arguments */
|
||||
.arg(
|
||||
Arg::with_name("no-api")
|
||||
@@ -299,7 +267,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.short("m")
|
||||
.value_name("MINUTES")
|
||||
.required(true)
|
||||
.default_value("15")
|
||||
.default_value("0")
|
||||
.help("The maximum number of minutes that will have elapsed before genesis"))
|
||||
)
|
||||
/*
|
||||
|
||||
@@ -587,7 +587,6 @@ impl ConfigBuilder {
|
||||
.map_err(|e| format!("Unable to parse default listen address: {:?}", e))?;
|
||||
|
||||
self.client_config.network.listen_address = addr.into();
|
||||
self.client_config.rpc.listen_address = addr;
|
||||
self.client_config.rest_api.listen_address = addr;
|
||||
|
||||
Ok(())
|
||||
@@ -607,7 +606,6 @@ impl ConfigBuilder {
|
||||
|
||||
self.client_config.network.libp2p_port += bump;
|
||||
self.client_config.network.discovery_port += bump;
|
||||
self.client_config.rpc.port += bump;
|
||||
self.client_config.rest_api.port += bump;
|
||||
self.client_config.websocket_server.port += bump;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,6 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
|
||||
.build_beacon_chain()?
|
||||
.libp2p_network(&client_config.network)?
|
||||
.http_server(&client_config, &http_eth2_config)?
|
||||
.grpc_server(&client_config.rpc)?
|
||||
.peer_count_notifier()?
|
||||
.slot_notifier()?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use node_test_rig::{environment::EnvironmentBuilder, LocalBeaconNode};
|
||||
use node_test_rig::{environment::EnvironmentBuilder, testing_client_config, LocalBeaconNode};
|
||||
use types::{MinimalEthSpec, Slot};
|
||||
|
||||
fn env_builder() -> EnvironmentBuilder<MinimalEthSpec> {
|
||||
@@ -17,12 +17,12 @@ fn http_server_genesis_state() {
|
||||
.build()
|
||||
.expect("environment should build");
|
||||
|
||||
let node = LocalBeaconNode::production(env.core_context());
|
||||
let node = LocalBeaconNode::production(env.core_context(), testing_client_config());
|
||||
let remote_node = node.remote_node().expect("should produce remote node");
|
||||
|
||||
let (api_state, _root) = env
|
||||
.runtime()
|
||||
.block_on(remote_node.http.beacon().state_at_slot(Slot::new(0)))
|
||||
.block_on(remote_node.http.beacon().get_state_by_slot(Slot::new(0)))
|
||||
.expect("should fetch state from http api");
|
||||
|
||||
let mut db_state = node
|
||||
|
||||
Reference in New Issue
Block a user