Misc changes for merge testnets (#2667)

* Thread eth1_block_hash into interop genesis state

* Add merge-fork-epoch flag

* Build LH with minimal spec by default

* Add verbose logs to execution_layer

* Add --http-allow-sync-stalled flag

* Update lcli new-testnet to create genesis state

* Fix http test

* Fix compile errors in tests
This commit is contained in:
Paul Hauner 2021-10-03 06:57:23 +11:00
parent a1033a9247
commit b162b067de
No known key found for this signature in database
GPG Key ID: 5E2CFF9B75FA63DF
16 changed files with 236 additions and 60 deletions

View File

@ -23,9 +23,9 @@ FORKS=phase0 altair
# Binaries will most likely be found in `./target/release`
install:
ifeq ($(PORTABLE), true)
cargo install --path lighthouse --force --locked --features portable
cargo install --path lighthouse --force --locked --features portable,spec-minimal
else
cargo install --path lighthouse --force --locked
cargo install --path lighthouse --force --locked --features spec-minimal
endif
# Builds the lcli binary in release (optimized).

View File

@ -919,7 +919,9 @@ fn descriptive_db_error(item: &str, error: &StoreError) -> String {
mod test {
use super::*;
use eth2_hashing::hash;
use genesis::{generate_deterministic_keypairs, interop_genesis_state};
use genesis::{
generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH,
};
use sloggers::{null::NullLoggerBuilder, Build};
use ssz::Encode;
use std::time::Duration;
@ -951,6 +953,7 @@ mod test {
let genesis_state = interop_genesis_state(
&generate_deterministic_keypairs(validator_count),
genesis_time,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
&spec,
)
.expect("should create interop genesis state");
@ -1016,8 +1019,13 @@ mod test {
let keypairs = generate_deterministic_keypairs(validator_count);
let state = interop_genesis_state::<TestEthSpec>(&keypairs, genesis_time, spec)
.expect("should build state");
let state = interop_genesis_state::<TestEthSpec>(
&keypairs,
genesis_time,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
spec,
)
.expect("should build state");
assert_eq!(
state.eth1_data().block_hash,

View File

@ -18,7 +18,7 @@ use execution_layer::{
ExecutionLayer,
};
use futures::channel::mpsc::Receiver;
pub use genesis::interop_genesis_state;
pub use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
use int_to_bytes::int_to_bytes32;
use logging::test_logger;
use merkle_proof::MerkleTree;
@ -181,6 +181,7 @@ impl<E: EthSpec> Builder<EphemeralHarnessType<E>> {
let genesis_state = interop_genesis_state::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
builder.get_spec(),
)
.expect("should generate interop state");
@ -226,6 +227,7 @@ impl<E: EthSpec> Builder<DiskHarnessType<E>> {
let genesis_state = interop_genesis_state::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
builder.get_spec(),
)
.expect("should generate interop state");

View File

@ -17,7 +17,7 @@ use eth2::{
BeaconNodeHttpClient, Error as ApiError, Timeouts,
};
use execution_layer::ExecutionLayer;
use genesis::{interop_genesis_state, Eth1GenesisService};
use genesis::{interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH};
use lighthouse_network::NetworkGlobals;
use monitoring_api::{MonitoringHttpClient, ProcessType};
use network::{NetworkConfig, NetworkMessage, NetworkService};
@ -31,7 +31,8 @@ use std::time::Duration;
use timer::spawn_timer;
use tokio::sync::{mpsc::UnboundedSender, oneshot};
use types::{
test_utils::generate_deterministic_keypairs, BeaconState, ChainSpec, EthSpec, SignedBeaconBlock,
test_utils::generate_deterministic_keypairs, BeaconState, ChainSpec, EthSpec, Hash256,
SignedBeaconBlock,
};
/// Interval between polling the eth1 node for genesis information.
@ -229,7 +230,12 @@ where
genesis_time,
} => {
let keypairs = generate_deterministic_keypairs(validator_count);
let genesis_state = interop_genesis_state(&keypairs, genesis_time, &spec)?;
let genesis_state = interop_genesis_state(
&keypairs,
genesis_time,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
&spec,
)?;
builder.genesis_state(genesis_state).map(|v| (v, None))?
}
ClientGenesis::SszBytes {

View File

@ -8,7 +8,7 @@ use engine_api::{Error as ApiError, *};
use engines::{Engine, EngineError, Engines};
use lru::LruCache;
use sensitive_url::SensitiveUrl;
use slog::{crit, Logger};
use slog::{crit, info, Logger};
use std::future::Future;
use std::sync::Arc;
use task_executor::TaskExecutor;
@ -177,6 +177,14 @@ impl ExecutionLayer {
random: Hash256,
) -> Result<PayloadId, Error> {
let fee_recipient = self.fee_recipient()?;
info!(
self.log(),
"Issuing engine_preparePayload";
"fee_recipient" => ?fee_recipient,
"random" => ?random,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
self.engines()
.first_success(|engine| {
// TODO(merge): make a cache for these IDs, so we don't always have to perform this
@ -205,6 +213,14 @@ impl ExecutionLayer {
random: Hash256,
) -> Result<ExecutionPayload<T>, Error> {
let fee_recipient = self.fee_recipient()?;
info!(
self.log(),
"Issuing engine_getPayload";
"fee_recipient" => ?fee_recipient,
"random" => ?random,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
self.engines()
.first_success(|engine| async move {
// TODO(merge): make a cache for these IDs, so we don't always have to perform this
@ -236,6 +252,14 @@ impl ExecutionLayer {
&self,
execution_payload: &ExecutionPayload<T>,
) -> Result<(ExecutePayloadResponse, ExecutePayloadHandle), Error> {
info!(
self.log(),
"Issuing engine_executePayload";
"parent_hash" => ?execution_payload.parent_hash,
"block_hash" => ?execution_payload.block_hash,
"block_number" => execution_payload.block_number,
);
let broadcast_results = self
.engines()
.broadcast(|engine| engine.api.execute_payload(execution_payload.clone()))
@ -296,6 +320,12 @@ impl ExecutionLayer {
block_hash: Hash256,
status: ConsensusStatus,
) -> Result<(), Error> {
info!(
self.log(),
"Issuing engine_consensusValidated";
"status" => ?status,
"block_hash" => ?block_hash,
);
let broadcast_results = self
.engines()
.broadcast(|engine| engine.api.consensus_validated(block_hash, status))
@ -328,6 +358,12 @@ impl ExecutionLayer {
head_block_hash: Hash256,
finalized_block_hash: Hash256,
) -> Result<(), Error> {
info!(
self.log(),
"Issuing engine_forkchoiceUpdated";
"finalized_block_hash" => ?finalized_block_hash,
"head_block_hash" => ?head_block_hash,
);
let broadcast_results = self
.engines()
.broadcast(|engine| {
@ -357,7 +393,8 @@ impl ExecutionLayer {
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/merge/validator.md
pub async fn get_terminal_pow_block_hash(&self) -> Result<Option<Hash256>, Error> {
self.engines()
let hash_opt = self
.engines()
.first_success(|engine| async move {
if self.terminal_block_hash() != Hash256::zero() {
// Note: the specification is written such that if there are multiple blocks in
@ -376,7 +413,19 @@ impl ExecutionLayer {
}
})
.await
.map_err(Error::EngineErrors)
.map_err(Error::EngineErrors)?;
if let Some(hash) = &hash_opt {
info!(
self.log(),
"Found terminal block hash";
"terminal_block_hash_override" => ?self.terminal_block_hash(),
"terminal_total_difficulty" => ?self.terminal_total_difficulty(),
"block_hash" => ?hash,
);
}
Ok(hash_opt)
}
/// This function should remain internal. External users should use

View File

@ -5,6 +5,8 @@ use ssz::Encode;
use state_processing::initialize_beacon_state_from_eth1;
use types::{BeaconState, ChainSpec, DepositData, EthSpec, Hash256, Keypair, PublicKey, Signature};
pub const DEFAULT_ETH1_BLOCK_HASH: &[u8] = &[0x42; 32];
/// Builds a genesis state as defined by the Eth2 interop procedure (see below).
///
/// Reference:
@ -12,9 +14,10 @@ use types::{BeaconState, ChainSpec, DepositData, EthSpec, Hash256, Keypair, Publ
pub fn interop_genesis_state<T: EthSpec>(
keypairs: &[Keypair],
genesis_time: u64,
eth1_block_hash: Hash256,
spec: &ChainSpec,
) -> Result<BeaconState<T>, String> {
let eth1_block_hash = Hash256::from_slice(&[0x42; 32]);
let eth1_block_hash = eth1_block_hash;
let eth1_timestamp = 2_u64.pow(40);
let amount = spec.max_effective_balance;
@ -73,8 +76,13 @@ mod test {
let keypairs = generate_deterministic_keypairs(validator_count);
let state = interop_genesis_state::<TestEthSpec>(&keypairs, genesis_time, spec)
.expect("should build state");
let state = interop_genesis_state::<TestEthSpec>(
&keypairs,
genesis_time,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
spec,
)
.expect("should build state");
assert_eq!(
state.eth1_data().block_hash,

View File

@ -4,5 +4,5 @@ mod interop;
pub use eth1::Config as Eth1Config;
pub use eth1_genesis_service::{Eth1GenesisService, Statistics};
pub use interop::interop_genesis_state;
pub use interop::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
pub use types::test_utils::generate_deterministic_keypairs;

View File

@ -97,6 +97,7 @@ pub struct Config {
pub allow_origin: Option<String>,
pub serve_legacy_spec: bool,
pub tls_config: Option<TlsConfig>,
pub allow_sync_stalled: bool,
}
impl Default for Config {
@ -108,6 +109,7 @@ impl Default for Config {
allow_origin: None,
serve_legacy_spec: true,
tls_config: None,
allow_sync_stalled: false,
}
}
}
@ -237,6 +239,7 @@ pub fn serve<T: BeaconChainTypes>(
shutdown: impl Future<Output = ()> + Send + Sync + 'static,
) -> Result<HttpServer, Error> {
let config = ctx.config.clone();
let allow_sync_stalled = config.allow_sync_stalled;
let log = ctx.log.clone();
// Configure CORS.
@ -338,44 +341,49 @@ pub fn serve<T: BeaconChainTypes>(
});
// Create a `warp` filter that rejects request whilst the node is syncing.
let not_while_syncing_filter = warp::any()
.and(network_globals.clone())
.and(chain_filter.clone())
.and_then(
|network_globals: Arc<NetworkGlobals<T::EthSpec>>, chain: Arc<BeaconChain<T>>| async move {
match *network_globals.sync_state.read() {
SyncState::SyncingFinalized { .. } => {
let head_slot = chain.best_slot().map_err(warp_utils::reject::beacon_chain_error)?;
let not_while_syncing_filter =
warp::any()
.and(network_globals.clone())
.and(chain_filter.clone())
.and_then(
move |network_globals: Arc<NetworkGlobals<T::EthSpec>>,
chain: Arc<BeaconChain<T>>| async move {
match *network_globals.sync_state.read() {
SyncState::SyncingFinalized { .. } => {
let head_slot = chain
.best_slot()
.map_err(warp_utils::reject::beacon_chain_error)?;
let current_slot = chain
.slot_clock
.now_or_genesis()
.ok_or_else(|| {
warp_utils::reject::custom_server_error(
"unable to read slot clock".to_string(),
)
})?;
let current_slot =
chain.slot_clock.now_or_genesis().ok_or_else(|| {
warp_utils::reject::custom_server_error(
"unable to read slot clock".to_string(),
)
})?;
let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch();
let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch();
if head_slot + tolerance >= current_slot {
Ok(())
} else {
Err(warp_utils::reject::not_synced(format!(
"head slot is {}, current slot is {}",
head_slot, current_slot
)))
if head_slot + tolerance >= current_slot {
Ok(())
} else {
Err(warp_utils::reject::not_synced(format!(
"head slot is {}, current slot is {}",
head_slot, current_slot
)))
}
}
SyncState::SyncingHead { .. }
| SyncState::SyncTransition
| SyncState::BackFillSyncing { .. } => Ok(()),
SyncState::Synced => Ok(()),
SyncState::Stalled if allow_sync_stalled => Ok(()),
SyncState::Stalled => Err(warp_utils::reject::not_synced(
"sync is stalled".to_string(),
)),
}
SyncState::SyncingHead { .. } | SyncState::SyncTransition | SyncState::BackFillSyncing { .. } => Ok(()),
SyncState::Synced => Ok(()),
SyncState::Stalled => Err(warp_utils::reject::not_synced(
"sync is stalled".to_string(),
)),
}
},
)
.untuple_one();
},
)
.untuple_one();
// Create a `warp` filter that provides access to the logger.
let inner_ctx = ctx.clone();

View File

@ -133,6 +133,7 @@ pub async fn create_api_server<T: BeaconChainTypes>(
allow_origin: None,
serve_legacy_spec: true,
tls_config: None,
allow_sync_stalled: false,
},
chain: Some(chain.clone()),
network_tx: Some(network_tx),

View File

@ -5,7 +5,7 @@ use beacon_chain::{
BeaconChain,
};
use futures::prelude::*;
use genesis::{generate_deterministic_keypairs, interop_genesis_state};
use genesis::{generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
use lazy_static::lazy_static;
use lighthouse_network::NetworkConfig;
use slog::Logger;
@ -16,8 +16,8 @@ use std::time::{Duration, SystemTime};
use store::config::StoreConfig;
use store::{HotColdDB, MemoryStore};
use types::{
CommitteeIndex, Epoch, EthSpec, MainnetEthSpec, Slot, SubnetId, SyncCommitteeSubscription,
SyncSubnetId, ValidatorSubscription,
CommitteeIndex, Epoch, EthSpec, Hash256, MainnetEthSpec, Slot, SubnetId,
SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription,
};
const SLOT_DURATION_MILLIS: u64 = 400;
@ -52,8 +52,13 @@ impl TestBeaconChain {
.custom_spec(spec.clone())
.store(Arc::new(store))
.genesis_state(
interop_genesis_state::<MainnetEthSpec>(&keypairs, 0, &spec)
.expect("should generate interop state"),
interop_genesis_state::<MainnetEthSpec>(
&keypairs,
0,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
&spec,
)
.expect("should generate interop state"),
)
.expect("should build state using recent genesis")
.dummy_eth1_backend()

View File

@ -240,6 +240,13 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
over TLS. Must not be password-protected.")
.takes_value(true)
)
.arg(
Arg::with_name("http-allow-sync-stalled")
.long("http-allow-sync-stalled")
.help("Forces the HTTP to indicate that the node is synced when sync is actually \
stalled. This is useful for very small testnets. TESTING ONLY. DO NOT USE ON \
MAINNET.")
)
/* Prometheus metrics HTTP server related arguments */
.arg(
Arg::with_name("metrics")

View File

@ -131,6 +131,10 @@ pub fn get_config<E: EthSpec>(
});
}
if cli_args.is_present("http-allow-sync-stalled") {
client_config.http_api.allow_sync_stalled = true;
}
/*
* Prometheus metrics HTTP server
*/

View File

@ -3,6 +3,7 @@ use crate::test_utils::*;
use crate::test_utils::{SeedableRng, XorShiftRng};
use beacon_chain::test_utils::{
interop_genesis_state, test_spec, BeaconChainHarness, EphemeralHarnessType,
DEFAULT_ETH1_BLOCK_HASH,
};
use beacon_chain::types::{
test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateError,
@ -557,7 +558,13 @@ fn tree_hash_cache_linear_history_long_skip() {
let spec = &test_spec::<MinimalEthSpec>();
// This state has a cache that advances normally each slot.
let mut state: BeaconState<MinimalEthSpec> = interop_genesis_state(&keypairs, 0, spec).unwrap();
let mut state: BeaconState<MinimalEthSpec> = interop_genesis_state(
&keypairs,
0,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
spec,
)
.unwrap();
state.update_tree_hash_cache().unwrap();

View File

@ -1,11 +1,11 @@
use clap::ArgMatches;
use clap_utils::parse_ssz_optional;
use eth2_network_config::Eth2NetworkConfig;
use genesis::interop_genesis_state;
use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
use ssz::Encode;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use types::{test_utils::generate_deterministic_keypairs, EthSpec};
use types::{test_utils::generate_deterministic_keypairs, EthSpec, Hash256};
pub fn run<T: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), String> {
let validator_count = matches
@ -34,7 +34,12 @@ pub fn run<T: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(),
}
let keypairs = generate_deterministic_keypairs(validator_count);
let genesis_state = interop_genesis_state::<T>(&keypairs, genesis_time, &spec)?;
let genesis_state = interop_genesis_state::<T>(
&keypairs,
genesis_time,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
&spec,
)?;
eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes());
eth2_network_config.force_write_to_file(testnet_dir)?;

View File

@ -284,6 +284,14 @@ fn main() {
.takes_value(false)
.help("Overwrites any previous testnet configurations"),
)
.arg(
Arg::with_name("interop-genesis-state")
.long("interop-genesis-state")
.takes_value(false)
.help(
"If present, a interop-style genesis.ssz file will be generated.",
),
)
.arg(
Arg::with_name("min-genesis-time")
.long("min-genesis-time")
@ -402,6 +410,36 @@ fn main() {
"The epoch at which to enable the Altair hard fork",
),
)
.arg(
Arg::with_name("merge-fork-epoch")
.long("merge-fork-epoch")
.value_name("EPOCH")
.takes_value(true)
.help(
"The epoch at which to enable the Merge hard fork",
),
)
.arg(
Arg::with_name("eth1-block-hash")
.long("eth1-block-hash")
.value_name("BLOCK_HASH")
.takes_value(true)
.help("The eth1 block hash used when generating a genesis state."),
)
.arg(
Arg::with_name("validator-count")
.long("validator-count")
.value_name("INTEGER")
.takes_value(true)
.help("The number of validators when generating a genesis state."),
)
.arg(
Arg::with_name("genesis-time")
.long("genesis-time")
.value_name("INTEGER")
.takes_value(true)
.help("The genesis time when generating a genesis state."),
)
)
.subcommand(
SubCommand::with_name("check-deposit-data")

View File

@ -1,8 +1,11 @@
use clap::ArgMatches;
use clap_utils::{parse_optional, parse_required, parse_ssz_optional};
use eth2_network_config::Eth2NetworkConfig;
use genesis::interop_genesis_state;
use ssz::Encode;
use std::path::PathBuf;
use types::{Address, Config, EthSpec};
use std::time::{SystemTime, UNIX_EPOCH};
use types::{test_utils::generate_deterministic_keypairs, Address, Config, EthSpec};
pub fn run<T: EthSpec>(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Result<(), String> {
let deposit_contract_address: Address = parse_required(matches, "deposit-contract-address")?;
@ -54,10 +57,35 @@ pub fn run<T: EthSpec>(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul
spec.altair_fork_epoch = Some(fork_epoch);
}
if let Some(fork_epoch) = parse_optional(matches, "merge-fork-epoch")? {
spec.merge_fork_epoch = Some(fork_epoch);
}
let genesis_state_bytes = if matches.is_present("interop-genesis-state") {
let eth1_block_hash = parse_required(matches, "eth1-block-hash")?;
let validator_count = parse_required(matches, "validator-count")?;
let genesis_time = if let Some(time) = parse_optional(matches, "genesis-time")? {
time
} else {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| format!("Unable to get time: {:?}", e))?
.as_secs()
};
let keypairs = generate_deterministic_keypairs(validator_count);
let genesis_state =
interop_genesis_state::<T>(&keypairs, genesis_time, eth1_block_hash, &spec)?;
Some(genesis_state.as_ssz_bytes())
} else {
None
};
let testnet = Eth2NetworkConfig {
deposit_contract_deploy_block,
boot_enr: Some(vec![]),
genesis_state_bytes: None,
genesis_state_bytes,
config: Config::from_chain_spec::<T>(&spec),
};