mirror of
https://gitlab.com/pulsechaincom/lighthouse-pulse.git
synced 2024-12-23 04:04:12 +00:00
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:
parent
3b405f10ea
commit
48f73b21e6
@ -77,7 +77,7 @@ fn get_sync_status<T: EthSpec>(
|
|||||||
let period = T::SlotsPerEth1VotingPeriod::to_u64();
|
let period = T::SlotsPerEth1VotingPeriod::to_u64();
|
||||||
// Since `period` is a "constant", we assume it is set sensibly.
|
// Since `period` is a "constant", we assume it is set sensibly.
|
||||||
let voting_period_start_slot = (current_slot / period) * period;
|
let voting_period_start_slot = (current_slot / period) * period;
|
||||||
let voting_period_start_timestamp = {
|
let voting_target_timestamp = {
|
||||||
let period_start = slot_start_seconds::<T>(
|
let period_start = slot_start_seconds::<T>(
|
||||||
genesis_time,
|
genesis_time,
|
||||||
spec.milliseconds_per_slot,
|
spec.milliseconds_per_slot,
|
||||||
@ -125,14 +125,14 @@ fn get_sync_status<T: EthSpec>(
|
|||||||
// Lighthouse is "cached and ready" when it has cached enough blocks to cover the start of the
|
// Lighthouse is "cached and ready" when it has cached enough blocks to cover the start of the
|
||||||
// current voting period.
|
// current voting period.
|
||||||
let lighthouse_is_cached_and_ready =
|
let lighthouse_is_cached_and_ready =
|
||||||
latest_cached_block_timestamp.map_or(false, |t| t >= voting_period_start_timestamp);
|
latest_cached_block_timestamp.map_or(false, |t| t >= voting_target_timestamp);
|
||||||
|
|
||||||
Some(Eth1SyncStatusData {
|
Some(Eth1SyncStatusData {
|
||||||
head_block_number,
|
head_block_number,
|
||||||
head_block_timestamp,
|
head_block_timestamp,
|
||||||
latest_cached_block_number,
|
latest_cached_block_number,
|
||||||
latest_cached_block_timestamp,
|
latest_cached_block_timestamp,
|
||||||
voting_period_start_timestamp,
|
voting_target_timestamp,
|
||||||
eth1_node_sync_status_percentage,
|
eth1_node_sync_status_percentage,
|
||||||
lighthouse_is_cached_and_ready,
|
lighthouse_is_cached_and_ready,
|
||||||
})
|
})
|
||||||
@ -450,13 +450,14 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
|
|||||||
// If no votes exist, choose `state.eth1_data` as default vote.
|
// If no votes exist, choose `state.eth1_data` as default vote.
|
||||||
votes_to_consider
|
votes_to_consider
|
||||||
.iter()
|
.iter()
|
||||||
.max_by(|(_, x), (_, y)| x.cmp(y))
|
.max_by_key(|(_, block_number)| *block_number)
|
||||||
.map(|vote| {
|
.map(|vote| {
|
||||||
let vote = vote.0.clone();
|
let vote = vote.0.clone();
|
||||||
debug!(
|
debug!(
|
||||||
self.log,
|
self.log,
|
||||||
"No valid eth1_data votes";
|
"No valid eth1_data votes";
|
||||||
"outcome" => "Casting vote corresponding to last candidate eth1 block",
|
"outcome" => "Casting vote corresponding to last candidate eth1 block",
|
||||||
|
"vote" => ?vote
|
||||||
);
|
);
|
||||||
vote
|
vote
|
||||||
})
|
})
|
||||||
|
@ -171,6 +171,38 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
|
|||||||
"current_slot" => current_slot,
|
"current_slot" => current_slot,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform some logging about the eth1 chain
|
||||||
|
if let Some(eth1_chain) = beacon_chain.eth1_chain.as_ref() {
|
||||||
|
if let Some(status) =
|
||||||
|
eth1_chain.sync_status(head_info.genesis_time, current_slot, &beacon_chain.spec)
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Eth1 cache sync status";
|
||||||
|
"eth1_head_block" => status.head_block_number,
|
||||||
|
"latest_cached_block_number" => status.latest_cached_block_number,
|
||||||
|
"latest_cached_timestamp" => status.latest_cached_block_timestamp,
|
||||||
|
"voting_target_timestamp" => status.voting_target_timestamp,
|
||||||
|
"ready" => status.lighthouse_is_cached_and_ready
|
||||||
|
);
|
||||||
|
|
||||||
|
if !status.lighthouse_is_cached_and_ready {
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"Syncing eth1 block cache";
|
||||||
|
"target_timestamp" => status.voting_target_timestamp,
|
||||||
|
"latest_timestamp" => status.latest_cached_block_timestamp,
|
||||||
|
"msg" => "block production temporarily impaired"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"Unable to determine eth1 sync status";
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok::<(), ()>(())
|
Ok::<(), ()>(())
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ use std::ops::{Range, RangeInclusive};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::time::{interval_at, Duration, Instant};
|
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.
|
/// Indicates the default eth1 network id we use for the deposit contract.
|
||||||
pub const DEFAULT_NETWORK_ID: Eth1Id = Eth1Id::Goerli;
|
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";
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The remote node is less synced that we expect, it is not useful until has done more
|
/// 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 {
|
RemoteNotSynced {
|
||||||
next_required_block: u64,
|
next_required_block: u64,
|
||||||
remote_highest_block: u64,
|
remote_highest_block: u64,
|
||||||
follow_distance: u64,
|
reduced_follow_distance: u64,
|
||||||
},
|
},
|
||||||
/// Failed to download a block from the eth1 node.
|
/// Failed to download a block from the eth1 node.
|
||||||
BlockDownloadFailed(String),
|
BlockDownloadFailed(String),
|
||||||
@ -113,6 +116,31 @@ pub struct Config {
|
|||||||
pub max_blocks_per_update: Option<usize>,
|
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 {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
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.
|
/// Return byte representation of deposit and block caches.
|
||||||
pub fn as_bytes(&self) -> Vec<u8> {
|
pub fn as_bytes(&self) -> Vec<u8> {
|
||||||
self.inner.as_bytes()
|
self.inner.as_bytes()
|
||||||
@ -471,7 +511,7 @@ impl Service {
|
|||||||
remote_highest_block_opt: Option<u64>,
|
remote_highest_block_opt: Option<u64>,
|
||||||
) -> Result<DepositCacheUpdateOutcome, Error> {
|
) -> Result<DepositCacheUpdateOutcome, Error> {
|
||||||
let endpoint = self.config().endpoint.clone();
|
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 deposit_contract_address = self.config().deposit_contract_address.clone();
|
||||||
|
|
||||||
let blocks_per_log_query = self.config().blocks_per_log_query;
|
let blocks_per_log_query = self.config().blocks_per_log_query;
|
||||||
@ -491,7 +531,7 @@ impl Service {
|
|||||||
&endpoint,
|
&endpoint,
|
||||||
remote_highest_block_opt,
|
remote_highest_block_opt,
|
||||||
next_required_block,
|
next_required_block,
|
||||||
follow_distance,
|
reduced_follow_distance,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -632,13 +672,13 @@ impl Service {
|
|||||||
.unwrap_or_else(|| self.config().lowest_cached_block_number);
|
.unwrap_or_else(|| self.config().lowest_cached_block_number);
|
||||||
|
|
||||||
let endpoint = self.config().endpoint.clone();
|
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(
|
let range = get_new_block_numbers(
|
||||||
&endpoint,
|
&endpoint,
|
||||||
remote_highest_block_opt,
|
remote_highest_block_opt,
|
||||||
next_required_block,
|
next_required_block,
|
||||||
follow_distance,
|
reduced_follow_distance,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
// Map the range of required blocks into a Vec.
|
// Map the range of required blocks into a Vec.
|
||||||
@ -778,7 +818,7 @@ async fn get_new_block_numbers<'a>(
|
|||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
remote_highest_block_opt: Option<u64>,
|
remote_highest_block_opt: Option<u64>,
|
||||||
next_required_block: u64,
|
next_required_block: u64,
|
||||||
follow_distance: u64,
|
reduced_follow_distance: u64,
|
||||||
) -> Result<Option<RangeInclusive<u64>>, Error> {
|
) -> Result<Option<RangeInclusive<u64>>, Error> {
|
||||||
let remote_highest_block = if let Some(block_number) = remote_highest_block_opt {
|
let remote_highest_block = if let Some(block_number) = remote_highest_block_opt {
|
||||||
block_number
|
block_number
|
||||||
@ -787,7 +827,7 @@ async fn get_new_block_numbers<'a>(
|
|||||||
.map_err(Error::GetBlockNumberFailed)
|
.map_err(Error::GetBlockNumberFailed)
|
||||||
.await?
|
.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 {
|
if next_required_block <= remote_follow_block {
|
||||||
Ok(Some(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
|
// 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).
|
// (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.
|
// happens, otherwise it is an error.
|
||||||
Err(Error::RemoteNotSynced {
|
Err(Error::RemoteNotSynced {
|
||||||
next_required_block,
|
next_required_block,
|
||||||
remote_highest_block,
|
remote_highest_block,
|
||||||
follow_distance,
|
reduced_follow_distance,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Return an empty range.
|
// Return an empty range.
|
||||||
@ -860,6 +900,7 @@ async fn download_eth1_block(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use toml;
|
use toml;
|
||||||
|
use types::MainnetEthSpec;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serde_serialize() {
|
fn serde_serialize() {
|
||||||
@ -867,4 +908,26 @@ mod tests {
|
|||||||
toml::to_string(&Config::default()).expect("Should serde encode default config");
|
toml::to_string(&Config::default()).expect("Should serde encode default config");
|
||||||
toml::from_str::<Config>(&serialized).expect("Should serde decode 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,6 +272,7 @@ pub fn get_config<E: EthSpec>(
|
|||||||
client_config.eth1.follow_distance = spec.eth1_follow_distance;
|
client_config.eth1.follow_distance = spec.eth1_follow_distance;
|
||||||
client_config.eth1.network_id = spec.deposit_network_id.into();
|
client_config.eth1.network_id = spec.deposit_network_id.into();
|
||||||
client_config.eth1.chain_id = spec.deposit_chain_id.into();
|
client_config.eth1.chain_id = spec.deposit_chain_id.into();
|
||||||
|
client_config.eth1.set_block_cache_truncation::<E>(spec);
|
||||||
|
|
||||||
if let Some(mut boot_nodes) = eth2_testnet_config.boot_enr {
|
if let Some(mut boot_nodes) = eth2_testnet_config.boot_enr {
|
||||||
client_config.network.boot_nodes_enr.append(&mut boot_nodes)
|
client_config.network.boot_nodes_enr.append(&mut boot_nodes)
|
||||||
|
@ -192,9 +192,7 @@ health of the Eth1 node that the beacon node is connected to.
|
|||||||
number and timestamp of the latest block we have in our block cache.
|
number and timestamp of the latest block we have in our block cache.
|
||||||
- For correct Eth1 voting this timestamp should be later than the
|
- For correct Eth1 voting this timestamp should be later than the
|
||||||
`voting_period_start_timestamp`.
|
`voting_period_start_timestamp`.
|
||||||
- `voting_period_start_timestamp`: the start of the period where block
|
- `voting_target_timestamp`: The latest timestamp allowed for an eth1 block in this voting period.
|
||||||
producers must include votes for blocks in the Eth1 chain. Provided for
|
|
||||||
reference.
|
|
||||||
- `eth1_node_sync_status_percentage` (float): An estimate of how far the head of the
|
- `eth1_node_sync_status_percentage` (float): An estimate of how far the head of the
|
||||||
Eth1 node is from the head of the Eth1 chain.
|
Eth1 node is from the head of the Eth1 chain.
|
||||||
- `100.0` indicates a fully synced Eth1 node.
|
- `100.0` indicates a fully synced Eth1 node.
|
||||||
@ -222,7 +220,7 @@ curl -X GET "http://localhost:5052/lighthouse/eth1/syncing" -H "accept: applica
|
|||||||
"head_block_timestamp": 1603249317,
|
"head_block_timestamp": 1603249317,
|
||||||
"latest_cached_block_number": 3610758,
|
"latest_cached_block_number": 3610758,
|
||||||
"latest_cached_block_timestamp": 1603233597,
|
"latest_cached_block_timestamp": 1603233597,
|
||||||
"voting_period_start_timestamp": 1603228632,
|
"voting_target_timestamp": 1603228632,
|
||||||
"eth1_node_sync_status_percentage": 100,
|
"eth1_node_sync_status_percentage": 100,
|
||||||
"lighthouse_is_cached_and_ready": true
|
"lighthouse_is_cached_and_ready": true
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ pub struct Eth1SyncStatusData {
|
|||||||
pub head_block_timestamp: Option<u64>,
|
pub head_block_timestamp: Option<u64>,
|
||||||
pub latest_cached_block_number: Option<u64>,
|
pub latest_cached_block_number: Option<u64>,
|
||||||
pub latest_cached_block_timestamp: Option<u64>,
|
pub latest_cached_block_timestamp: Option<u64>,
|
||||||
pub voting_period_start_timestamp: u64,
|
pub voting_target_timestamp: u64,
|
||||||
pub eth1_node_sync_status_percentage: f64,
|
pub eth1_node_sync_status_percentage: f64,
|
||||||
pub lighthouse_is_cached_and_ready: bool,
|
pub lighthouse_is_cached_and_ready: bool,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user