775d222299
## Proposed Changes With proposer boosting implemented (#2822) we have an opportunity to re-org out late blocks. This PR adds three flags to the BN to control this behaviour: * `--disable-proposer-reorgs`: turn aggressive re-orging off (it's on by default). * `--proposer-reorg-threshold N`: attempt to orphan blocks with less than N% of the committee vote. If this parameter isn't set then N defaults to 20% when the feature is enabled. * `--proposer-reorg-epochs-since-finalization N`: only attempt to re-org late blocks when the number of epochs since finalization is less than or equal to N. The default is 2 epochs, meaning re-orgs will only be attempted when the chain is finalizing optimally. For safety Lighthouse will only attempt a re-org under very specific conditions: 1. The block being proposed is 1 slot after the canonical head, and the canonical head is 1 slot after its parent. i.e. at slot `n + 1` rather than building on the block from slot `n` we build on the block from slot `n - 1`. 2. The current canonical head received less than N% of the committee vote. N should be set depending on the proposer boost fraction itself, the fraction of the network that is believed to be applying it, and the size of the largest entity that could be hoarding votes. 3. The current canonical head arrived after the attestation deadline from our perspective. This condition was only added to support suppression of forkchoiceUpdated messages, but makes intuitive sense. 4. The block is being proposed in the first 2 seconds of the slot. This gives it time to propagate and receive the proposer boost. ## Additional Info For the initial idea and background, see: https://github.com/ethereum/consensus-specs/pull/2353#issuecomment-950238004 There is also a specification for this feature here: https://github.com/ethereum/consensus-specs/pull/3034 Co-authored-by: Michael Sproul <micsproul@gmail.com> Co-authored-by: pawan <pawandhananjay@gmail.com>
371 lines
14 KiB
Rust
371 lines
14 KiB
Rust
use crate::graffiti_file::GraffitiFile;
|
|
use crate::{http_api, http_metrics};
|
|
use clap::ArgMatches;
|
|
use clap_utils::{flags::DISABLE_MALLOC_TUNING_FLAG, parse_optional, parse_required};
|
|
use directory::{
|
|
get_network_dir, DEFAULT_HARDCODED_NETWORK, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR,
|
|
DEFAULT_VALIDATOR_DIR,
|
|
};
|
|
use eth2::types::Graffiti;
|
|
use sensitive_url::SensitiveUrl;
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use slog::{info, warn, Logger};
|
|
use std::fs;
|
|
use std::net::IpAddr;
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
use types::{Address, GRAFFITI_BYTES_LEN};
|
|
|
|
pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/";
|
|
|
|
/// Stores the core configuration for this validator instance.
|
|
#[derive(Clone, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
/// The data directory, which stores all validator databases
|
|
pub validator_dir: PathBuf,
|
|
/// The directory containing the passwords to unlock validator keystores.
|
|
pub secrets_dir: PathBuf,
|
|
/// The http endpoints of the beacon node APIs.
|
|
///
|
|
/// Should be similar to `["http://localhost:8080"]`
|
|
pub beacon_nodes: Vec<SensitiveUrl>,
|
|
/// If true, the validator client will still poll for duties and produce blocks even if the
|
|
/// beacon node is not synced at startup.
|
|
pub allow_unsynced_beacon_node: bool,
|
|
/// If true, don't scan the validators dir for new keystores.
|
|
pub disable_auto_discover: bool,
|
|
/// If true, re-register existing validators in definitions.yml for slashing protection.
|
|
pub init_slashing_protection: bool,
|
|
/// If true, use longer timeouts for requests made to the beacon node.
|
|
pub use_long_timeouts: bool,
|
|
/// Graffiti to be inserted everytime we create a block.
|
|
pub graffiti: Option<Graffiti>,
|
|
/// Graffiti file to load per validator graffitis.
|
|
pub graffiti_file: Option<GraffitiFile>,
|
|
/// Fallback fallback address.
|
|
pub fee_recipient: Option<Address>,
|
|
/// Configuration for the HTTP REST API.
|
|
pub http_api: http_api::Config,
|
|
/// Configuration for the HTTP REST API.
|
|
pub http_metrics: http_metrics::Config,
|
|
/// Configuration for sending metrics to a remote explorer endpoint.
|
|
pub monitoring_api: Option<monitoring_api::Config>,
|
|
/// If true, enable functionality that monitors the network for attestations or proposals from
|
|
/// any of the validators managed by this client before starting up.
|
|
pub enable_doppelganger_protection: bool,
|
|
/// Enable use of the blinded block endpoints during proposals.
|
|
pub builder_proposals: bool,
|
|
/// Overrides the timestamp field in builder api ValidatorRegistrationV1
|
|
pub builder_registration_timestamp_override: Option<u64>,
|
|
/// Fallback gas limit.
|
|
pub gas_limit: Option<u64>,
|
|
/// A list of custom certificates that the validator client will additionally use when
|
|
/// connecting to a beacon node over SSL/TLS.
|
|
pub beacon_nodes_tls_certs: Option<Vec<PathBuf>>,
|
|
/// Delay from the start of the slot to wait before publishing a block.
|
|
///
|
|
/// This is *not* recommended in prod and should only be used for testing.
|
|
pub block_delay: Option<Duration>,
|
|
/// Disables publishing http api requests to all beacon nodes for select api calls.
|
|
pub disable_run_on_all: bool,
|
|
}
|
|
|
|
impl Default for Config {
|
|
/// Build a new configuration from defaults.
|
|
fn default() -> Self {
|
|
// WARNING: these directory defaults should be always overwritten with parameters from cli
|
|
// for specific networks.
|
|
let base_dir = dirs::home_dir()
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
.join(DEFAULT_ROOT_DIR)
|
|
.join(DEFAULT_HARDCODED_NETWORK);
|
|
let validator_dir = base_dir.join(DEFAULT_VALIDATOR_DIR);
|
|
let secrets_dir = base_dir.join(DEFAULT_SECRET_DIR);
|
|
|
|
let beacon_nodes = vec![SensitiveUrl::parse(DEFAULT_BEACON_NODE)
|
|
.expect("beacon_nodes must always be a valid url.")];
|
|
Self {
|
|
validator_dir,
|
|
secrets_dir,
|
|
beacon_nodes,
|
|
allow_unsynced_beacon_node: false,
|
|
disable_auto_discover: false,
|
|
init_slashing_protection: false,
|
|
use_long_timeouts: false,
|
|
graffiti: None,
|
|
graffiti_file: None,
|
|
fee_recipient: None,
|
|
http_api: <_>::default(),
|
|
http_metrics: <_>::default(),
|
|
monitoring_api: None,
|
|
enable_doppelganger_protection: false,
|
|
beacon_nodes_tls_certs: None,
|
|
block_delay: None,
|
|
builder_proposals: false,
|
|
builder_registration_timestamp_override: None,
|
|
gas_limit: None,
|
|
disable_run_on_all: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Returns a `Default` implementation of `Self` with some parameters modified by the supplied
|
|
/// `cli_args`.
|
|
pub fn from_cli(cli_args: &ArgMatches, log: &Logger) -> Result<Config, String> {
|
|
let mut config = Config::default();
|
|
|
|
let default_root_dir = dirs::home_dir()
|
|
.map(|home| home.join(DEFAULT_ROOT_DIR))
|
|
.unwrap_or_else(|| PathBuf::from("."));
|
|
|
|
let (mut validator_dir, mut secrets_dir) = (None, None);
|
|
if cli_args.value_of("datadir").is_some() {
|
|
let base_dir: PathBuf = parse_required(cli_args, "datadir")?;
|
|
validator_dir = Some(base_dir.join(DEFAULT_VALIDATOR_DIR));
|
|
secrets_dir = Some(base_dir.join(DEFAULT_SECRET_DIR));
|
|
}
|
|
if cli_args.value_of("validators-dir").is_some() {
|
|
validator_dir = Some(parse_required(cli_args, "validators-dir")?);
|
|
}
|
|
if cli_args.value_of("secrets-dir").is_some() {
|
|
secrets_dir = Some(parse_required(cli_args, "secrets-dir")?);
|
|
}
|
|
|
|
config.validator_dir = validator_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_network_dir(cli_args))
|
|
.join(DEFAULT_VALIDATOR_DIR)
|
|
});
|
|
|
|
config.secrets_dir = secrets_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_network_dir(cli_args))
|
|
.join(DEFAULT_SECRET_DIR)
|
|
});
|
|
|
|
if !config.validator_dir.exists() {
|
|
fs::create_dir_all(&config.validator_dir)
|
|
.map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?;
|
|
}
|
|
|
|
if let Some(beacon_nodes) = parse_optional::<String>(cli_args, "beacon-nodes")? {
|
|
config.beacon_nodes = beacon_nodes
|
|
.split(',')
|
|
.map(SensitiveUrl::parse)
|
|
.collect::<Result<_, _>>()
|
|
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
|
|
}
|
|
// To be deprecated.
|
|
else if let Some(beacon_node) = parse_optional::<String>(cli_args, "beacon-node")? {
|
|
warn!(
|
|
log,
|
|
"The --beacon-node flag is deprecated";
|
|
"msg" => "please use --beacon-nodes instead"
|
|
);
|
|
config.beacon_nodes = vec![SensitiveUrl::parse(&beacon_node)
|
|
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?];
|
|
}
|
|
// To be deprecated.
|
|
else if let Some(server) = parse_optional::<String>(cli_args, "server")? {
|
|
warn!(
|
|
log,
|
|
"The --server flag is deprecated";
|
|
"msg" => "please use --beacon-nodes instead"
|
|
);
|
|
config.beacon_nodes = vec![SensitiveUrl::parse(&server)
|
|
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?];
|
|
}
|
|
|
|
if cli_args.is_present("delete-lockfiles") {
|
|
warn!(
|
|
log,
|
|
"The --delete-lockfiles flag is deprecated";
|
|
"msg" => "it is no longer necessary, and no longer has any effect",
|
|
);
|
|
}
|
|
|
|
config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced");
|
|
config.disable_run_on_all = cli_args.is_present("disable-run-on-all");
|
|
config.disable_auto_discover = cli_args.is_present("disable-auto-discover");
|
|
config.init_slashing_protection = cli_args.is_present("init-slashing-protection");
|
|
config.use_long_timeouts = cli_args.is_present("use-long-timeouts");
|
|
|
|
if let Some(graffiti_file_path) = cli_args.value_of("graffiti-file") {
|
|
let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into());
|
|
graffiti_file
|
|
.read_graffiti_file()
|
|
.map_err(|e| format!("Error reading graffiti file: {:?}", e))?;
|
|
config.graffiti_file = Some(graffiti_file);
|
|
info!(log, "Successfully loaded graffiti file"; "path" => graffiti_file_path);
|
|
}
|
|
|
|
if let Some(input_graffiti) = cli_args.value_of("graffiti") {
|
|
let graffiti_bytes = input_graffiti.as_bytes();
|
|
if graffiti_bytes.len() > GRAFFITI_BYTES_LEN {
|
|
return Err(format!(
|
|
"Your graffiti is too long! {} bytes maximum!",
|
|
GRAFFITI_BYTES_LEN
|
|
));
|
|
} else {
|
|
let mut graffiti = [0; 32];
|
|
|
|
// Copy the provided bytes over.
|
|
//
|
|
// Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`.
|
|
graffiti[..graffiti_bytes.len()].copy_from_slice(graffiti_bytes);
|
|
|
|
config.graffiti = Some(graffiti.into());
|
|
}
|
|
}
|
|
|
|
if let Some(input_fee_recipient) =
|
|
parse_optional::<Address>(cli_args, "suggested-fee-recipient")?
|
|
{
|
|
config.fee_recipient = Some(input_fee_recipient);
|
|
}
|
|
|
|
if let Some(tls_certs) = parse_optional::<String>(cli_args, "beacon-nodes-tls-certs")? {
|
|
config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect());
|
|
}
|
|
|
|
/*
|
|
* Http API server
|
|
*/
|
|
|
|
if cli_args.is_present("http") {
|
|
config.http_api.enabled = true;
|
|
}
|
|
|
|
if let Some(address) = cli_args.value_of("http-address") {
|
|
if cli_args.is_present("unencrypted-http-transport") {
|
|
config.http_api.listen_addr = address
|
|
.parse::<IpAddr>()
|
|
.map_err(|_| "http-address is not a valid IP address.")?;
|
|
} else {
|
|
return Err(
|
|
"While using `--http-address`, you must also use `--unencrypted-http-transport`."
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
|
|
if let Some(port) = cli_args.value_of("http-port") {
|
|
config.http_api.listen_port = port
|
|
.parse::<u16>()
|
|
.map_err(|_| "http-port is not a valid u16.")?;
|
|
}
|
|
|
|
if let Some(allow_origin) = cli_args.value_of("http-allow-origin") {
|
|
// Pre-validate the config value to give feedback to the user on node startup, instead of
|
|
// as late as when the first API response is produced.
|
|
hyper::header::HeaderValue::from_str(allow_origin)
|
|
.map_err(|_| "Invalid allow-origin value")?;
|
|
|
|
config.http_api.allow_origin = Some(allow_origin.to_string());
|
|
}
|
|
|
|
/*
|
|
* Prometheus metrics HTTP server
|
|
*/
|
|
|
|
if cli_args.is_present("metrics") {
|
|
config.http_metrics.enabled = true;
|
|
}
|
|
|
|
if let Some(address) = cli_args.value_of("metrics-address") {
|
|
config.http_metrics.listen_addr = address
|
|
.parse::<IpAddr>()
|
|
.map_err(|_| "metrics-address is not a valid IP address.")?;
|
|
}
|
|
|
|
if let Some(port) = cli_args.value_of("metrics-port") {
|
|
config.http_metrics.listen_port = port
|
|
.parse::<u16>()
|
|
.map_err(|_| "metrics-port is not a valid u16.")?;
|
|
}
|
|
|
|
if let Some(allow_origin) = cli_args.value_of("metrics-allow-origin") {
|
|
// Pre-validate the config value to give feedback to the user on node startup, instead of
|
|
// as late as when the first API response is produced.
|
|
hyper::header::HeaderValue::from_str(allow_origin)
|
|
.map_err(|_| "Invalid allow-origin value")?;
|
|
|
|
config.http_metrics.allow_origin = Some(allow_origin.to_string());
|
|
}
|
|
|
|
if cli_args.is_present(DISABLE_MALLOC_TUNING_FLAG) {
|
|
config.http_metrics.allocator_metrics_enabled = false;
|
|
}
|
|
|
|
/*
|
|
* Explorer metrics
|
|
*/
|
|
if let Some(monitoring_endpoint) = cli_args.value_of("monitoring-endpoint") {
|
|
let update_period_secs =
|
|
clap_utils::parse_optional(cli_args, "monitoring-endpoint-period")?;
|
|
config.monitoring_api = Some(monitoring_api::Config {
|
|
db_path: None,
|
|
freezer_db_path: None,
|
|
update_period_secs,
|
|
monitoring_endpoint: monitoring_endpoint.to_string(),
|
|
});
|
|
}
|
|
|
|
if cli_args.is_present("enable-doppelganger-protection") {
|
|
config.enable_doppelganger_protection = true;
|
|
}
|
|
|
|
if cli_args.is_present("builder-proposals") {
|
|
config.builder_proposals = true;
|
|
}
|
|
|
|
config.gas_limit = cli_args
|
|
.value_of("gas-limit")
|
|
.map(|gas_limit| {
|
|
gas_limit
|
|
.parse::<u64>()
|
|
.map_err(|_| "gas-limit is not a valid u64.")
|
|
})
|
|
.transpose()?;
|
|
|
|
if let Some(registration_timestamp_override) =
|
|
cli_args.value_of("builder-registration-timestamp-override")
|
|
{
|
|
config.builder_registration_timestamp_override = Some(
|
|
registration_timestamp_override
|
|
.parse::<u64>()
|
|
.map_err(|_| "builder-registration-timestamp-override is not a valid u64.")?,
|
|
);
|
|
}
|
|
|
|
if cli_args.is_present("strict-fee-recipient") {
|
|
warn!(
|
|
log,
|
|
"The flag `--strict-fee-recipient` has been deprecated due to a bug causing \
|
|
missed proposals. The flag will be ignored."
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Experimental
|
|
*/
|
|
if let Some(delay_ms) = parse_optional::<u64>(cli_args, "block-delay-ms")? {
|
|
config.block_delay = Some(Duration::from_millis(delay_ms));
|
|
}
|
|
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
// Ensures the default config does not panic.
|
|
fn default_config() {
|
|
Config::default();
|
|
}
|
|
}
|