Add validator duties post endpoint

This commit is contained in:
Paul Hauner
2019-11-22 15:20:58 +11:00
parent 07d054576a
commit af20a345bc
5 changed files with 170 additions and 56 deletions

View File

@@ -40,7 +40,7 @@ use url_query::UrlQuery;
pub use crate::helpers::parse_pubkey;
pub use beacon::{BlockResponse, HeadResponse, StateResponse};
pub use config::Config;
pub use validator::ValidatorDuty;
pub use validator::{BulkValidatorDutiesRequest, ValidatorDuty};
pub type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
pub type NetworkChannel = Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>;

View File

@@ -96,7 +96,10 @@ pub fn route<T: BeaconChainTypes>(
// Methods for Validator
(&Method::GET, "/validator/duties") => {
into_boxfut(validator::get_validator_duties::<T>(req, beacon_chain, log))
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))

View File

@@ -12,10 +12,11 @@ use futures::future::Future;
use futures::stream::Stream;
use hyper::{Body, Request};
use serde::{Deserialize, Serialize};
use slog::{info, trace, warn, Logger};
use slog::{info, warn, Logger};
use ssz_derive::{Decode, Encode};
use std::sync::Arc;
use types::beacon_state::EthSpec;
use types::{Attestation, BeaconBlock, CommitteeIndex, RelativeEpoch, Slot};
use types::{Attestation, BeaconBlock, CommitteeIndex, Epoch, RelativeEpoch, Slot};
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDuty {
@@ -31,20 +32,67 @@ pub struct ValidatorDuty {
pub block_proposal_slot: Option<Slot>,
}
#[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 + 'static>(
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
///
/// The given `epoch` must be within one epoch of the current epoch.
pub fn get_validator_duties<T: BeaconChainTypes + 'static>(
req: Request<Body>,
beacon_chain: Arc<BeaconChain<T>>,
log: Logger,
) -> ApiResult {
slog::trace!(log, "Validator duties requested of API: {:?}", &req);
let query = UrlQuery::from_request(&req)?;
let epoch = query.epoch()?;
let validator_pubkeys = query
.all_of("validator_pubkeys")?
.iter()
.map(|validator_pubkey_str| parse_pubkey(validator_pubkey_str))
.collect::<Result<_, _>>()?;
let duties = return_validator_duties(beacon_chain, epoch, validator_pubkeys)?;
ResponseBuilder::new(&req)?.body_no_ssz(&duties)
}
fn return_validator_duties<T: BeaconChainTypes + 'static>(
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| {
@@ -83,51 +131,46 @@ pub fn get_validator_duties<T: BeaconChainTypes + 'static>(
})
.collect::<Result<Vec<_>, _>>()?;
let duties = query
.all_of("validator_pubkeys")?
.iter()
.map(|validator_pubkey_str| {
parse_pubkey(validator_pubkey_str).and_then(|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
))
})?;
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
))
})?;
let block_proposal_slot = validator_proposers
.iter()
.find(|(i, _slot)| validator_index == *i)
.map(|(_i, slot)| *slot);
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,
})
}
})
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,
})
}
})
.collect::<Result<Vec<_>, ApiError>>()?;
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.
@@ -163,10 +206,6 @@ pub fn publish_beacon_block<T: BeaconChainTypes + 'static>(
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)))

View File

@@ -5,12 +5,12 @@ use node_test_rig::{
environment::{Environment, EnvironmentBuilder},
testing_client_config, ClientGenesis, LocalBeaconNode,
};
use remote_beacon_node::PublishStatus;
use remote_beacon_node::{PublishStatus, ValidatorDuty};
use std::sync::Arc;
use tree_hash::{SignedRoot, TreeHash};
use types::{
test_utils::generate_deterministic_keypair, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec,
MinimalEthSpec, RelativeEpoch, Signature, Slot,
MinimalEthSpec, PublicKey, RelativeEpoch, Signature, Slot,
};
type E = MinimalEthSpec;
@@ -159,6 +159,43 @@ fn validator_produce_attestation() {
);
}
#[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();
@@ -188,6 +225,16 @@ fn validator_duties() {
.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(),