Faster attestation production (#838)

* Start adding interop genesis state to lcli

* Use more efficient method to generate genesis state

* Remove duplicate int_to_bytes32

* Add lcli command to change state genesis time

* Add option to allow VC to start with unsynced BN

* Set VC to do parallel key loading

* Don't default to dummy eth1 backend

* Add endpoint to dump operation pool

* Add metrics for op pool

* Remove state clone for slot notifier

* Add mem size approximation for tree hash cache

* Avoid cloning tree hash when getting head

* Avoid cloning tree hash when getting head

* Add working arena-based cached tree hash

* Add another benchmark

* Add pre-allocation for caches

* Make cache nullable

* Fix bugs in cache tree hash

* Add validator tree hash optimization

* Optimize hash_concat

* Make hash32_concat return fixed-len array

* Fix failing API tests

* Add new beacon state cache struct

* Add validator-specific cache

* Separate list and values arenas

* Add parallel validator registry hashing

* Remove MultiTreeHashCache

* Remove cached tree hash macro

* Fix failing tree hash test

* Address Michael's comments

* Add CachedTreeHash impl for ef tests

* Fix messy merge conflict

* Optimize attestation production

* Add first basic optimizations

* Fix SlotOutOfBounds error

* Resolved missed merge conflicts

* Fix another missed merge conflict

* Fix more merge conflict issues

* Add `StateSkipConfig`

* Fix test compile errors

* Add failing test

* Fix bug, make tests pass

* Add comment

* Delete unused function

* Replace deleted comment
This commit is contained in:
Paul Hauner
2020-03-04 17:10:22 +11:00
committed by GitHub
parent 1f16d8fe4d
commit 12999fb06c
13 changed files with 350 additions and 196 deletions

View File

@@ -2,7 +2,7 @@ use crate::helpers::*;
use crate::response_builder::ResponseBuilder;
use crate::validator::get_state_for_epoch;
use crate::{ApiError, ApiResult, BoxFut, UrlQuery};
use beacon_chain::{BeaconChain, BeaconChainTypes};
use beacon_chain::{BeaconChain, BeaconChainTypes, StateSkipConfig};
use futures::{Future, Stream};
use hyper::{Body, Request};
use serde::{Deserialize, Serialize};
@@ -381,7 +381,7 @@ pub fn get_committees<T: BeaconChainTypes>(
let epoch = query.epoch()?;
let mut state = get_state_for_epoch(&beacon_chain, epoch)?;
let mut state = get_state_for_epoch(&beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch).map_err(|e| {
ApiError::ServerError(format!("Failed to get state suitable for epoch: {:?}", e))
@@ -471,7 +471,7 @@ pub fn get_state_root<T: BeaconChainTypes>(
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
let slot = parse_slot(&slot_string)?;
let root = state_root_at_slot(&beacon_chain, slot)?;
let root = state_root_at_slot(&beacon_chain, slot, StateSkipConfig::WithStateRoots)?;
ResponseBuilder::new(&req)?.body(&root)
}

View File

@@ -1,5 +1,5 @@
use crate::{ApiError, ApiResult};
use beacon_chain::{BeaconChain, BeaconChainTypes};
use beacon_chain::{BeaconChain, BeaconChainTypes, StateSkipConfig};
use bls::PublicKeyBytes;
use eth2_libp2p::GossipTopic;
use eth2_libp2p::PubsubMessage;
@@ -142,7 +142,7 @@ pub fn state_at_slot<T: BeaconChainTypes>(
if head.beacon_state.slot == slot {
Ok((head.beacon_state_root, head.beacon_state))
} else {
let root = state_root_at_slot(beacon_chain, slot)?;
let root = state_root_at_slot(beacon_chain, slot, StateSkipConfig::WithStateRoots)?;
let state: BeaconState<T::EthSpec> = beacon_chain
.store
@@ -161,6 +161,7 @@ pub fn state_at_slot<T: BeaconChainTypes>(
pub fn state_root_at_slot<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
slot: Slot,
config: StateSkipConfig,
) -> Result<Hash256, ApiError> {
let head_state = &beacon_chain.head()?.beacon_state;
let current_slot = beacon_chain
@@ -206,11 +207,16 @@ pub fn state_root_at_slot<T: BeaconChainTypes>(
let mut state = beacon_chain.head()?.beacon_state;
let spec = &T::EthSpec::default_spec();
let skip_state_root = match config {
StateSkipConfig::WithStateRoots => None,
StateSkipConfig::WithoutStateRoots => Some(Hash256::zero()),
};
for _ in state.slot.as_u64()..slot.as_u64() {
// Ensure the next epoch state caches are built in case of an epoch transition.
state.build_committee_cache(RelativeEpoch::Next, spec)?;
state_processing::per_slot_processing(&mut state, None, spec)?;
state_processing::per_slot_processing(&mut state, skip_state_root, spec)?;
}
// Note: this is an expensive operation. Once the tree hash cache is implement it may be

View File

@@ -21,6 +21,21 @@ lazy_static! {
"http_server_success_total",
"Total count of HTTP 200 responses sent"
);
pub static ref VALIDATOR_GET_BLOCK_REQUEST_RESPONSE_TIME: Result<Histogram> =
try_create_histogram(
"http_server_validator_block_get_request_duration_seconds",
"Time taken to respond to GET /validator/block"
);
pub static ref VALIDATOR_GET_ATTESTATION_REQUEST_RESPONSE_TIME: Result<Histogram> =
try_create_histogram(
"http_server_validator_attestation_get_request_duration_seconds",
"Time taken to respond to GET /validator/attestation"
);
pub static ref VALIDATOR_GET_DUTIES_REQUEST_RESPONSE_TIME: Result<Histogram> =
try_create_histogram(
"http_server_validator_duties_get_request_duration_seconds",
"Time taken to respond to GET /validator/duties"
);
}
/// Returns the full set of Prometheus metrics for the Beacon Node application.

View File

@@ -10,6 +10,7 @@ use hyper::{Body, Error, Method, Request, Response};
use slog::debug;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
fn into_boxfut<F: IntoFuture + 'static>(item: F) -> BoxFut
where
@@ -33,6 +34,7 @@ pub fn route<T: BeaconChainTypes>(
) -> impl Future<Item = Response<Body>, Error = Error> {
metrics::inc_counter(&metrics::REQUEST_COUNT);
let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
let received_instant = Instant::now();
let path = req.uri().path().to_string();
@@ -113,7 +115,11 @@ pub fn route<T: BeaconChainTypes>(
// Methods for Validator
(&Method::POST, "/validator/duties") => {
validator::post_validator_duties::<T>(req, beacon_chain)
let timer =
metrics::start_timer(&metrics::VALIDATOR_GET_DUTIES_REQUEST_RESPONSE_TIME);
let response = validator::post_validator_duties::<T>(req, beacon_chain);
drop(timer);
into_boxfut(response)
}
(&Method::GET, "/validator/duties/all") => {
into_boxfut(validator::get_all_validator_duties::<T>(req, beacon_chain))
@@ -122,13 +128,21 @@ pub fn route<T: BeaconChainTypes>(
validator::get_active_validator_duties::<T>(req, beacon_chain),
),
(&Method::GET, "/validator/block") => {
into_boxfut(validator::get_new_beacon_block::<T>(req, beacon_chain, log))
let timer =
metrics::start_timer(&metrics::VALIDATOR_GET_BLOCK_REQUEST_RESPONSE_TIME);
let response = validator::get_new_beacon_block::<T>(req, beacon_chain, log);
drop(timer);
into_boxfut(response)
}
(&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))
let timer =
metrics::start_timer(&metrics::VALIDATOR_GET_ATTESTATION_REQUEST_RESPONSE_TIME);
let response = validator::get_new_attestation::<T>(req, beacon_chain);
drop(timer);
into_boxfut(response)
}
(&Method::POST, "/validator/attestation") => {
validator::publish_attestation::<T>(req, beacon_chain, network_channel, log)
@@ -176,21 +190,34 @@ pub fn route<T: BeaconChainTypes>(
// 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, "HTTP API request successful"; "path" => path);
metrics::inc_counter(&metrics::SUCCESS_COUNT);
metrics::stop_timer(timer);
request_result.then(move |result| {
let duration = Instant::now().duration_since(received_instant);
match result {
Ok(response) => {
debug!(
local_log,
"HTTP API request successful";
"path" => path,
"duration_ms" => duration.as_millis()
);
metrics::inc_counter(&metrics::SUCCESS_COUNT);
metrics::stop_timer(timer);
Ok(response)
}
Err(e) => {
let error_response = e.into();
Ok(response)
}
Err(e) => {
let error_response = e.into();
debug!(local_log, "HTTP API request failure"; "path" => path);
metrics::stop_timer(timer);
debug!(
local_log,
"HTTP API request failure";
"path" => path,
"duration_ms" => duration.as_millis()
);
metrics::stop_timer(timer);
Ok(error_response)
Ok(error_response)
}
}
})
}

View File

@@ -5,6 +5,7 @@ use crate::response_builder::ResponseBuilder;
use crate::{ApiError, ApiResult, BoxFut, NetworkChannel, UrlQuery};
use beacon_chain::{
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BlockProcessingOutcome,
StateSkipConfig,
};
use bls::PublicKeyBytes;
use futures::{Future, Stream};
@@ -82,7 +83,7 @@ pub fn get_all_validator_duties<T: BeaconChainTypes>(
let epoch = query.epoch()?;
let state = get_state_for_epoch(&beacon_chain, epoch)?;
let state = get_state_for_epoch(&beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let validator_pubkeys = state
.validators
@@ -104,7 +105,7 @@ pub fn get_active_validator_duties<T: BeaconChainTypes>(
let epoch = query.epoch()?;
let state = get_state_for_epoch(&beacon_chain, epoch)?;
let state = get_state_for_epoch(&beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let validator_pubkeys = state
.validators
@@ -122,6 +123,7 @@ pub fn get_active_validator_duties<T: BeaconChainTypes>(
pub fn get_state_for_epoch<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
epoch: Epoch,
config: StateSkipConfig,
) -> Result<BeaconState<T::EthSpec>, ApiError> {
let slots_per_epoch = T::EthSpec::slots_per_epoch();
let head_epoch = beacon_chain.head()?.beacon_state.current_epoch();
@@ -141,7 +143,7 @@ pub fn get_state_for_epoch<T: BeaconChainTypes>(
(epoch + 2).start_slot(slots_per_epoch) - 1
};
beacon_chain.state_at_slot(slot).map_err(|e| {
beacon_chain.state_at_slot(slot, config).map_err(|e| {
ApiError::ServerError(format!("Unable to load state for epoch {}: {:?}", epoch, e))
})
}
@@ -153,7 +155,7 @@ fn return_validator_duties<T: BeaconChainTypes>(
epoch: Epoch,
validator_pubkeys: Vec<PublicKeyBytes>,
) -> Result<Vec<ValidatorDuty>, ApiError> {
let mut state = get_state_for_epoch(&beacon_chain, epoch)?;
let mut state = get_state_for_epoch(&beacon_chain, epoch, StateSkipConfig::WithoutStateRoots)?;
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch)
.map_err(|_| ApiError::ServerError(String::from("Loaded state is in the wrong epoch")))?;

View File

@@ -1,6 +1,6 @@
#![cfg(test)]
use beacon_chain::{BeaconChain, BeaconChainTypes};
use beacon_chain::{BeaconChain, BeaconChainTypes, StateSkipConfig};
use node_test_rig::{
environment::{Environment, EnvironmentBuilder},
testing_client_config, ClientConfig, ClientGenesis, LocalBeaconNode,
@@ -242,7 +242,10 @@ fn check_duties<T: BeaconChainTypes>(
);
let mut state = beacon_chain
.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))
.state_at_slot(
epoch.start_slot(T::EthSpec::slots_per_epoch()),
StateSkipConfig::WithStateRoots,
)
.expect("should get state at slot");
state.build_all_caches(spec).expect("should build caches");
@@ -469,7 +472,7 @@ fn beacon_state() {
.client
.beacon_chain()
.expect("client should have beacon chain")
.state_at_slot(Slot::new(0))
.state_at_slot(Slot::new(0), StateSkipConfig::WithStateRoots)
.expect("should find state");
db_state.drop_all_caches();