Expand eth1 block cache, add more logs (#1938)

## Issue Addressed

NA

## Proposed Changes

- Caches later blocks than is required by `ETH1_FOLLOW_DISTANCE`.
- Adds logging to `warn` if the eth1 cache is insufficiently primed.
- Use `max_by_key` instead of `max_by` in `BeaconChain::Eth1Chain` since it's simpler.
- Rename `voting_period_start_timestamp` to `voting_target_timestamp` for accuracy.

## Additional Info

The reason for eating into the `ETH1_FOLLOW_DISTANCE` and caching blocks that are closer to the head is due to possibility for `SECONDS_PER_ETH1_BLOCK` to be incorrect (as is the case for the Pyrmont testnet on Goerli).

If `SECONDS_PER_ETH1_BLOCK` is too short, we'll skip back too far from the head and skip over blocks that would be valid [`is_candidate_block`](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/validator.md#eth1-data) blocks. This was the case on the Pyrmont testnet and resulted in Lighthouse choosing blocks that were about 30 minutes older than is ideal.
This commit is contained in:
Paul Hauner
2020-11-21 00:26:15 +00:00
parent 3b405f10ea
commit 48f73b21e6
6 changed files with 114 additions and 19 deletions

View File

@@ -16,7 +16,7 @@ use std::ops::{Range, RangeInclusive};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::{interval_at, Duration, Instant};
use types::ChainSpec;
use types::{ChainSpec, EthSpec, Unsigned};
/// Indicates the default eth1 network id we use for the deposit contract.
pub const DEFAULT_NETWORK_ID: Eth1Id = Eth1Id::Goerli;
@@ -34,6 +34,9 @@ const GET_DEPOSIT_LOG_TIMEOUT_MILLIS: u64 = STANDARD_TIMEOUT_MILLIS;
const WARNING_MSG: &str = "BLOCK PROPOSALS WILL FAIL WITHOUT VALID ETH1 CONNECTION";
/// A factor used to reduce the eth1 follow distance to account for discrepancies in the block time.
const ETH1_BLOCK_TIME_TOLERANCE_FACTOR: u64 = 4;
#[derive(Debug, PartialEq)]
pub enum Error {
/// The remote node is less synced that we expect, it is not useful until has done more
@@ -41,7 +44,7 @@ pub enum Error {
RemoteNotSynced {
next_required_block: u64,
remote_highest_block: u64,
follow_distance: u64,
reduced_follow_distance: u64,
},
/// Failed to download a block from the eth1 node.
BlockDownloadFailed(String),
@@ -113,6 +116,31 @@ pub struct Config {
pub max_blocks_per_update: Option<usize>,
}
impl Config {
/// Sets the block cache to a length that is suitable for the given `EthSpec` and `ChainSpec`.
pub fn set_block_cache_truncation<E: EthSpec>(&mut self, spec: &ChainSpec) {
// Compute the number of eth1 blocks in an eth1 voting period.
let seconds_per_voting_period =
E::SlotsPerEth1VotingPeriod::to_u64() * spec.milliseconds_per_slot / 1000;
let eth1_blocks_per_voting_period = seconds_per_voting_period / spec.seconds_per_eth1_block;
// Compute the number of extra blocks we store prior to the voting period start blocks.
let follow_distance_tolerance_blocks =
spec.eth1_follow_distance / ETH1_BLOCK_TIME_TOLERANCE_FACTOR;
// Ensure we can store two full windows of voting blocks.
let voting_windows = eth1_blocks_per_voting_period * 2;
// Extend the cache to account for varying eth1 block times and the follow distance
// tolerance blocks.
let length = voting_windows
+ (voting_windows / ETH1_BLOCK_TIME_TOLERANCE_FACTOR)
+ follow_distance_tolerance_blocks;
self.block_cache_truncation = Some(length as usize);
}
}
impl Default for Config {
fn default() -> Self {
Self {
@@ -161,6 +189,18 @@ impl Service {
}
}
/// Returns the follow distance that has been shortened to accommodate for differences in the
/// spacing between blocks.
///
/// ## Notes
///
/// This is useful since the spec declares `SECONDS_PER_ETH1_BLOCK` to be `14`, whilst it is
/// actually `15` on Goerli.
pub fn reduced_follow_distance(&self) -> u64 {
let full = self.config().follow_distance;
full.saturating_sub(full / ETH1_BLOCK_TIME_TOLERANCE_FACTOR)
}
/// Return byte representation of deposit and block caches.
pub fn as_bytes(&self) -> Vec<u8> {
self.inner.as_bytes()
@@ -471,7 +511,7 @@ impl Service {
remote_highest_block_opt: Option<u64>,
) -> Result<DepositCacheUpdateOutcome, Error> {
let endpoint = self.config().endpoint.clone();
let follow_distance = self.config().follow_distance;
let reduced_follow_distance = self.reduced_follow_distance();
let deposit_contract_address = self.config().deposit_contract_address.clone();
let blocks_per_log_query = self.config().blocks_per_log_query;
@@ -491,7 +531,7 @@ impl Service {
&endpoint,
remote_highest_block_opt,
next_required_block,
follow_distance,
reduced_follow_distance,
)
.await?;
@@ -632,13 +672,13 @@ impl Service {
.unwrap_or_else(|| self.config().lowest_cached_block_number);
let endpoint = self.config().endpoint.clone();
let follow_distance = self.config().follow_distance;
let reduced_follow_distance = self.reduced_follow_distance();
let range = get_new_block_numbers(
&endpoint,
remote_highest_block_opt,
next_required_block,
follow_distance,
reduced_follow_distance,
)
.await?;
// Map the range of required blocks into a Vec.
@@ -778,7 +818,7 @@ async fn get_new_block_numbers<'a>(
endpoint: &str,
remote_highest_block_opt: Option<u64>,
next_required_block: u64,
follow_distance: u64,
reduced_follow_distance: u64,
) -> Result<Option<RangeInclusive<u64>>, Error> {
let remote_highest_block = if let Some(block_number) = remote_highest_block_opt {
block_number
@@ -787,7 +827,7 @@ async fn get_new_block_numbers<'a>(
.map_err(Error::GetBlockNumberFailed)
.await?
};
let remote_follow_block = remote_highest_block.saturating_sub(follow_distance);
let remote_follow_block = remote_highest_block.saturating_sub(reduced_follow_distance);
if next_required_block <= remote_follow_block {
Ok(Some(next_required_block..=remote_follow_block))
@@ -795,12 +835,12 @@ async fn get_new_block_numbers<'a>(
// If this is the case, the node must have gone "backwards" in terms of it's sync
// (i.e., it's head block is lower than it was before).
//
// We assume that the `follow_distance` should be sufficient to ensure this never
// We assume that the `reduced_follow_distance` should be sufficient to ensure this never
// happens, otherwise it is an error.
Err(Error::RemoteNotSynced {
next_required_block,
remote_highest_block,
follow_distance,
reduced_follow_distance,
})
} else {
// Return an empty range.
@@ -860,6 +900,7 @@ async fn download_eth1_block(
mod tests {
use super::*;
use toml;
use types::MainnetEthSpec;
#[test]
fn serde_serialize() {
@@ -867,4 +908,26 @@ mod tests {
toml::to_string(&Config::default()).expect("Should serde encode default config");
toml::from_str::<Config>(&serialized).expect("Should serde decode default config");
}
#[test]
fn block_cache_size() {
let mut config = Config::default();
let spec = MainnetEthSpec::default_spec();
config.set_block_cache_truncation::<MainnetEthSpec>(&spec);
let len = config.block_cache_truncation.unwrap();
let seconds_per_voting_period =
<MainnetEthSpec as EthSpec>::SlotsPerEth1VotingPeriod::to_u64()
* (spec.milliseconds_per_slot / 1000);
let eth1_blocks_per_voting_period = seconds_per_voting_period / spec.seconds_per_eth1_block;
let reduce_follow_distance_blocks =
config.follow_distance / ETH1_BLOCK_TIME_TOLERANCE_FACTOR;
let minimum_len = eth1_blocks_per_voting_period * 2 + reduce_follow_distance_blocks;
assert!(len > minimum_len as usize);
}
}