Compare commits

...

10 Commits

Author SHA1 Message Date
Paul Hauner
3a24ca5f14 v1.3.0 (#2310)
## Issue Addressed

NA

## Proposed Changes

Bump versions.

## Additional Info

This is a minor release (not patch) due to the very slight change introduced by #2291.
2021-04-13 22:46:34 +00:00
Michael Sproul
3b901dc5ec Pack attestations into blocks in parallel (#2307)
## Proposed Changes

Use two instances of max cover when packing attestations into blocks: one for the previous epoch, and one for the current epoch. This reduces the amount of computation done by roughly half due to the `O(n^2)` running time of max cover (`2 * (n/2)^2 = n^2/2`). This should help alleviate some load on block proposal, particularly on Prater.
2021-04-13 05:27:42 +00:00
Paul Hauner
c1203f5e52 Add specific log and metric for delayed blocks (#2308)
## Issue Addressed

NA

## Proposed Changes

- Adds a specific log and metric for when a block is enshrined as head with a delay that will caused bad attestations
    - We *technically* already expose this information, but it's a little tricky to determine during debugging. This makes it nice and explicit.
- Fixes a minor reporting bug with the validator monitor where it was expecting agg. attestations too early (at half-slot rather than two-thirds-slot).

## Additional Info

NA
2021-04-13 02:16:59 +00:00
Paul Hauner
0df7be1814 Add check for aggregate target (#2306)
## Issue Addressed
NA

## Proposed Changes

- Ensure that the [target consistency check](b356f52c5c) is always performed on aggregates.
- Add a regression test.

## Additional Info

NA
2021-04-13 00:24:39 +00:00
Age Manning
aaa14073ff Clean up warnings (#2240)
This is a small PR that cleans up compiler warnings. 

The most controversial change is removing the `data_dir` field from the `BeaconChainBuilder`. 

It was removed because it was never read.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
Co-authored-by: Herman Junge <hermanjunge@protonmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-04-12 00:57:43 +00:00
Mac L
f6f64cf0f5 Correcting disable-enr-auto-update flag definition (#2303)
## Issue Addressed

N/A

## Proposed Changes

Correct the `disable-enr-auto-update` boolean flag so that it no longer requires a value.
Previously it would require a value which was never used.

## Additional Info

Flag is read here: https://github.com/sigp/lighthouse/blob/unstable/beacon_node/src/config.rs#L585-L587
2021-04-11 23:52:29 +00:00
Paul Hauner
e7e5878953 Avoid BeaconState clone during metrics scrape (#2298)
## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

Avoids cloning the `BeaconState` each time Prometheus scrapes our metrics (generally every 5s 😱).

I think the original motivation behind this was *"don't hold the lock on the head whilst we do computation on it"*, however I think is flawed since our computation here is so small that it'll be quicker than the clone.

The primary motivation here is to maintain a small memory footprint by holding less in memory (i.e., the cloned `BeaconState`) and to avoid the fragmentation-creep that occurs when cloning the big contiguous slabs of memory in the `BeaconState`.

I also collapsed the active/slashed/withdrawn counters into a single loop to increase efficiency.

## Additional Info

NA
2021-04-07 01:02:56 +00:00
stefa2k
66590d043c Correcting command example validator exit (#2291)
## Issue Addressed

None

## Proposed Changes

Using correct flag in example for exiting validator.

## Additional Info

None
2021-04-03 00:38:51 +00:00
Paul Hauner
52995ab5f5 Use generic BLS object instead of BLST (#2290)
## Issue Addressed

NA

## Proposed Changes

Fixes a compile error when using the `milagro` feature. I can't see any need to use the specific BLST object here. @pawanjay176 can you please confirm?

## Additional Info

NA
2021-04-02 23:34:17 +00:00
Pawan Dhananjay
95a362213d Fix local testnet scripts (#2229)
## Issue Addressed

Resolves #2094 

## Proposed Changes

Fixes scripts for creating local testnets. Adds an option in `lighthouse boot_node` to run with a previously generated enr.
2021-03-30 05:17:58 +00:00
41 changed files with 659 additions and 285 deletions

14
Cargo.lock generated
View File

@@ -628,7 +628,7 @@ dependencies = [
[[package]]
name = "beacon_node"
version = "1.2.2"
version = "1.3.0"
dependencies = [
"beacon_chain",
"clap",
@@ -841,7 +841,7 @@ dependencies = [
[[package]]
name = "boot_node"
version = "1.2.2"
version = "1.3.0"
dependencies = [
"beacon_node",
"clap",
@@ -3335,7 +3335,7 @@ dependencies = [
[[package]]
name = "lcli"
version = "1.2.2"
version = "1.3.0"
dependencies = [
"account_utils",
"bls",
@@ -3345,6 +3345,7 @@ dependencies = [
"directory",
"dirs 3.0.1",
"environment",
"eth1_test_rig",
"eth2_keystore",
"eth2_libp2p",
"eth2_network_config",
@@ -3365,6 +3366,7 @@ dependencies = [
"tree_hash",
"types",
"validator_dir",
"web3",
]
[[package]]
@@ -3707,7 +3709,7 @@ dependencies = [
[[package]]
name = "lighthouse"
version = "1.2.2"
version = "1.3.0"
dependencies = [
"account_manager",
"account_utils",
@@ -4398,8 +4400,12 @@ dependencies = [
"eth2_ssz",
"eth2_ssz_derive",
"int_to_bytes",
"itertools 0.10.0",
"lazy_static",
"lighthouse_metrics",
"parking_lot",
"rand 0.7.3",
"rayon",
"serde",
"serde_derive",
"state_processing",

View File

@@ -1,6 +1,6 @@
[package]
name = "beacon_node"
version = "1.2.2"
version = "1.3.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com"]
edition = "2018"

View File

@@ -399,6 +399,16 @@ impl<T: BeaconChainTypes> VerifiedAggregatedAttestation<T> {
// We do not queue future attestations for later processing.
verify_propagation_slot_range(chain, attestation)?;
// Check the attestation's epoch matches its target.
if attestation.data.slot.epoch(T::EthSpec::slots_per_epoch())
!= attestation.data.target.epoch
{
return Err(Error::InvalidTargetEpoch {
slot: attestation.data.slot,
epoch: attestation.data.target.epoch,
});
}
// Ensure the valid aggregated attestation has not already been seen locally.
let attestation_root = attestation.tree_hash_root();
if chain

View File

@@ -1160,6 +1160,26 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(signed_aggregate)
}
/// Filter an attestation from the op pool for shuffling compatibility.
///
/// Use the provided `filter_cache` map to memoize results.
pub fn filter_op_pool_attestation(
&self,
filter_cache: &mut HashMap<(Hash256, Epoch), bool>,
att: &Attestation<T::EthSpec>,
state: &BeaconState<T::EthSpec>,
) -> bool {
*filter_cache
.entry((att.data.beacon_block_root, att.data.target.epoch))
.or_insert_with(|| {
self.shuffling_is_compatible(
&att.data.beacon_block_root,
att.data.target.epoch,
&state,
)
})
}
/// Check that the shuffling at `block_root` is equal to one of the shufflings of `state`.
///
/// The `target_epoch` argument determines which shuffling to check compatibility with, it
@@ -1968,21 +1988,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.deposits_for_block_inclusion(&state, &eth1_data, &self.spec)?
.into();
// Map from attestation head block root to shuffling compatibility.
// Used to memoize the `attestation_shuffling_is_compatible` function.
let mut shuffling_filter_cache = HashMap::new();
let attestation_filter = |att: &&Attestation<T::EthSpec>| -> bool {
*shuffling_filter_cache
.entry((att.data.beacon_block_root, att.data.target.epoch))
.or_insert_with(|| {
self.shuffling_is_compatible(
&att.data.beacon_block_root,
att.data.target.epoch,
&state,
)
})
};
// Iterate through the naive aggregation pool and ensure all the attestations from there
// are included in the operation pool.
let unagg_import_timer =
@@ -2012,9 +2017,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let attestation_packing_timer =
metrics::start_timer(&metrics::BLOCK_PRODUCTION_ATTESTATION_TIMES);
let mut prev_filter_cache = HashMap::new();
let prev_attestation_filter = |att: &&Attestation<T::EthSpec>| {
self.filter_op_pool_attestation(&mut prev_filter_cache, *att, &state)
};
let mut curr_filter_cache = HashMap::new();
let curr_attestation_filter = |att: &&Attestation<T::EthSpec>| {
self.filter_op_pool_attestation(&mut curr_filter_cache, *att, &state)
};
let attestations = self
.op_pool
.get_attestations(&state, attestation_filter, &self.spec)
.get_attestations(
&state,
prev_attestation_filter,
curr_attestation_filter,
&self.spec,
)
.map_err(BlockProductionError::OpPoolError)?
.into();
drop(attestation_packing_timer);
@@ -2214,12 +2234,32 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
metrics::stop_timer(update_head_timer);
let block_delay = get_slot_delay_ms(timestamp_now(), head_slot, &self.slot_clock);
// Observe the delay between the start of the slot and when we set the block as head.
metrics::observe_duration(
&metrics::BEACON_BLOCK_HEAD_SLOT_START_DELAY_TIME,
get_slot_delay_ms(timestamp_now(), head_slot, &self.slot_clock),
block_delay,
);
// If the block was enshrined as head too late for attestations to be created for it, log a
// debug warning and increment a metric.
//
// Don't create this log if the block was > 4 slots old, this helps prevent noise during
// sync.
if block_delay >= self.slot_clock.unagg_attestation_production_delay()
&& block_delay < self.slot_clock.slot_duration() * 4
{
metrics::inc_counter(&metrics::BEACON_BLOCK_HEAD_SLOT_START_DELAY_EXCEEDED_TOTAL);
debug!(
self.log,
"Delayed head block";
"delay" => ?block_delay,
"root" => ?beacon_block_root,
"slot" => head_slot,
);
}
self.snapshot_cache
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
.map(|mut snapshot_cache| {

View File

@@ -22,7 +22,6 @@ use slasher::Slasher;
use slog::{crit, info, Logger};
use slot_clock::{SlotClock, TestingSlotClock};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use store::{HotColdDB, ItemStore};
@@ -78,7 +77,6 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
slot_clock: Option<T::SlotClock>,
shutdown_sender: Option<Sender<&'static str>>,
head_tracker: Option<HeadTracker>,
data_dir: Option<PathBuf>,
validator_pubkey_cache: Option<ValidatorPubkeyCache<T>>,
spec: ChainSpec,
chain_config: ChainConfig,
@@ -116,7 +114,6 @@ where
slot_clock: None,
shutdown_sender: None,
head_tracker: None,
data_dir: None,
disabled_forks: Vec::new(),
validator_pubkey_cache: None,
spec: TEthSpec::default_spec(),
@@ -174,14 +171,6 @@ where
self
}
/// Sets the location to the pubkey cache file.
///
/// Should generally be called early in the build chain.
pub fn data_dir(mut self, path: PathBuf) -> Self {
self.data_dir = Some(path);
self
}
/// Sets a list of hard-coded forks that will not be activated.
pub fn disabled_forks(mut self, disabled_forks: Vec<String>) -> Self {
self.disabled_forks = disabled_forks;
@@ -673,7 +662,6 @@ mod test {
use std::time::Duration;
use store::config::StoreConfig;
use store::{HotColdDB, MemoryStore};
use tempfile::tempdir;
use types::{EthSpec, MinimalEthSpec, Slot};
type TestEthSpec = MinimalEthSpec;
@@ -696,7 +684,6 @@ mod test {
> = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal(), log.clone())
.unwrap();
let spec = MinimalEthSpec::default_spec();
let data_dir = tempdir().expect("should create temporary data_dir");
let genesis_state = interop_genesis_state(
&generate_deterministic_keypairs(validator_count),
@@ -710,7 +697,6 @@ mod test {
let chain = BeaconChainBuilder::new(MinimalEthSpec)
.logger(log.clone())
.store(Arc::new(store))
.data_dir(data_dir.path().to_path_buf())
.genesis_state(genesis_state)
.expect("should build state using recent genesis")
.dummy_eth1_backend()

View File

@@ -1,4 +1,4 @@
use crate::{BeaconChain, BeaconChainTypes};
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use lazy_static::lazy_static;
pub use lighthouse_metrics::*;
use slot_clock::SlotClock;
@@ -629,6 +629,11 @@ lazy_static! {
"beacon_block_head_slot_start_delay_time",
"Duration between the start of the blocks slot and the current time when it was as head.",
);
pub static ref BEACON_BLOCK_HEAD_SLOT_START_DELAY_EXCEEDED_TOTAL: Result<IntCounter> = try_create_int_counter(
"beacon_block_head_slot_start_delay_exceeded_total",
"Triggered when the duration between the start of the blocks slot and the current time \
will result in failed attestations.",
);
/*
* General block metrics
@@ -643,9 +648,10 @@ lazy_static! {
/// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot,
/// head state info, etc) and update the Prometheus `DEFAULT_REGISTRY`.
pub fn scrape_for_metrics<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) {
if let Ok(head) = beacon_chain.head() {
scrape_head_state::<T>(&head.beacon_state, head.beacon_state_root())
}
let _ = beacon_chain.with_head(|head| {
scrape_head_state(&head.beacon_state, head.beacon_state_root());
Ok::<_, BeaconChainError>(())
});
if let Some(slot) = beacon_chain.slot_clock.now() {
scrape_attestation_observation(slot, beacon_chain);
@@ -675,7 +681,7 @@ pub fn scrape_for_metrics<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) {
}
/// Scrape the given `state` assuming it's the head state, updating the `DEFAULT_REGISTRY`.
fn scrape_head_state<T: BeaconChainTypes>(state: &BeaconState<T::EthSpec>, state_root: Hash256) {
fn scrape_head_state<T: EthSpec>(state: &BeaconState<T>, state_root: Hash256) {
set_gauge_by_slot(&HEAD_STATE_SLOT, state.slot);
set_gauge_by_hash(&HEAD_STATE_ROOT, state_root);
set_gauge_by_slot(
@@ -703,29 +709,31 @@ fn scrape_head_state<T: BeaconChainTypes>(state: &BeaconState<T::EthSpec>, state
&HEAD_STATE_FINALIZED_EPOCH,
state.finalized_checkpoint.epoch,
);
set_gauge_by_u64(&HEAD_STATE_ETH1_DEPOSIT_INDEX, state.eth1_deposit_index);
set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators.len());
set_gauge_by_u64(&HEAD_STATE_VALIDATOR_BALANCES, state.balances.iter().sum());
set_gauge_by_usize(
&HEAD_STATE_ACTIVE_VALIDATORS,
state
.validators
.iter()
.filter(|v| v.is_active_at(state.current_epoch()))
.count(),
);
set_gauge_by_usize(
&HEAD_STATE_SLASHED_VALIDATORS,
state.validators.iter().filter(|v| v.slashed).count(),
);
set_gauge_by_usize(
&HEAD_STATE_WITHDRAWN_VALIDATORS,
state
.validators
.iter()
.filter(|v| v.is_withdrawable_at(state.current_epoch()))
.count(),
);
set_gauge_by_u64(&HEAD_STATE_ETH1_DEPOSIT_INDEX, state.eth1_deposit_index);
let mut num_active: usize = 0;
let mut num_slashed: usize = 0;
let mut num_withdrawn: usize = 0;
for v in &state.validators {
if v.is_active_at(state.current_epoch()) {
num_active += 1;
}
if v.slashed {
num_slashed += 1;
}
if v.is_withdrawable_at(state.current_epoch()) {
num_withdrawn += 1;
}
}
set_gauge_by_usize(&HEAD_STATE_ACTIVE_VALIDATORS, num_active);
set_gauge_by_usize(&HEAD_STATE_SLASHED_VALIDATORS, num_slashed);
set_gauge_by_usize(&HEAD_STATE_WITHDRAWN_VALIDATORS, num_withdrawn);
}
fn scrape_attestation_observation<T: BeaconChainTypes>(slot_now: Slot, chain: &BeaconChain<T>) {

View File

@@ -186,7 +186,6 @@ impl<E: EthSpec> BeaconChainHarness<EphemeralHarnessType<E>> {
.custom_spec(spec.clone())
.store(Arc::new(store))
.store_migrator_config(MigratorConfig::default().blocking())
.data_dir(data_dir.path().to_path_buf())
.genesis_state(
interop_genesis_state::<E>(&validator_keypairs, HARNESS_GENESIS_TIME, &spec)
.expect("should generate interop state"),
@@ -236,7 +235,6 @@ impl<E: EthSpec> BeaconChainHarness<DiskHarnessType<E>> {
.import_max_skip_slots(None)
.store(store)
.store_migrator_config(MigratorConfig::default().blocking())
.data_dir(data_dir.path().to_path_buf())
.genesis_state(
interop_genesis_state::<E>(&validator_keypairs, HARNESS_GENESIS_TIME, &spec)
.expect("should generate interop state"),
@@ -281,7 +279,6 @@ impl<E: EthSpec> BeaconChainHarness<DiskHarnessType<E>> {
.import_max_skip_slots(None)
.store(store)
.store_migrator_config(MigratorConfig::default().blocking())
.data_dir(data_dir.path().to_path_buf())
.resume_from_db()
.expect("should resume beacon chain from db")
.dummy_eth1_backend()

View File

@@ -528,8 +528,7 @@ impl<T: EthSpec> ValidatorMonitor<T> {
.start_of(data.slot)
.and_then(|slot_start| seen_timestamp.checked_sub(slot_start))
.and_then(|gross_delay| {
let production_delay = slot_clock.slot_duration() / 3;
gross_delay.checked_sub(production_delay)
gross_delay.checked_sub(slot_clock.unagg_attestation_production_delay())
})
.unwrap_or_else(|| Duration::from_secs(0))
}
@@ -619,8 +618,7 @@ impl<T: EthSpec> ValidatorMonitor<T> {
.start_of(data.slot)
.and_then(|slot_start| seen_timestamp.checked_sub(slot_start))
.and_then(|gross_delay| {
let production_delay = slot_clock.slot_duration() / 2;
gross_delay.checked_sub(production_delay)
gross_delay.checked_sub(slot_clock.agg_attestation_production_delay())
})
.unwrap_or_else(|| Duration::from_secs(0))
}

View File

@@ -276,6 +276,23 @@ fn aggregated_gossip_verification() {
&& earliest_permissible_slot == current_slot - E::slots_per_epoch() - 1
);
/*
* The following test ensures:
*
* The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch ==
* compute_epoch_at_slot(attestation.data.slot)`
*
*/
assert_invalid!(
"attestation with invalid target epoch",
{
let mut a = valid_aggregate.clone();
a.message.aggregate.data.target.epoch += 1;
a
},
AttnError::InvalidTargetEpoch { .. }
);
/*
* This is not in the specification for aggregate attestations (only unaggregates), but we
* check it anyway to avoid weird edge cases.

View File

@@ -122,7 +122,6 @@ where
let chain_spec = self.chain_spec.clone();
let runtime_context = self.runtime_context.clone();
let eth_spec_instance = self.eth_spec_instance.clone();
let data_dir = config.data_dir.clone();
let disabled_forks = config.disabled_forks.clone();
let chain_config = config.chain.clone();
let graffiti = config.graffiti;
@@ -141,7 +140,6 @@ where
let builder = BeaconChainBuilder::new(eth_spec_instance)
.logger(context.log().clone())
.store(store)
.data_dir(data_dir)
.custom_spec(spec.clone())
.chain_config(chain_config)
.disabled_forks(disabled_forks)

View File

@@ -174,6 +174,21 @@ fn compare_enr(local_enr: &Enr, disk_enr: &Enr) -> bool {
&& local_enr.get(BITFIELD_ENR_KEY) == disk_enr.get(BITFIELD_ENR_KEY)
}
/// Loads enr from the given directory
pub fn load_enr_from_disk(dir: &Path) -> Result<Enr, String> {
let enr_f = dir.join(ENR_FILENAME);
let mut enr_file =
File::open(enr_f).map_err(|e| format!("Failed to open enr file: {:?}", e))?;
let mut enr_string = String::new();
match enr_file.read_to_string(&mut enr_string) {
Err(_) => Err("Could not read ENR from file".to_string()),
Ok(_) => match Enr::from_str(&enr_string) {
Ok(disk_enr) => Ok(disk_enr),
Err(e) => Err(format!("ENR from file could not be decoded: {:?}", e)),
},
}
}
/// Saves an ENR to disk
pub fn save_enr_to_disk(dir: &Path, enr: &Enr, log: &slog::Logger) {
let _ = std::fs::create_dir_all(dir);

View File

@@ -3,7 +3,10 @@ pub(crate) mod enr;
pub mod enr_ext;
// Allow external use of the lighthouse ENR builder
pub use enr::{build_enr, create_enr_builder_from_config, use_or_load_enr, CombinedKey, Eth2Enr};
pub use enr::{
build_enr, create_enr_builder_from_config, load_enr_from_disk, use_or_load_enr, CombinedKey,
Eth2Enr,
};
pub use enr_ext::{peer_id_to_node_id, CombinedKeyExt, EnrExt};
pub use libp2p::core::identity::{Keypair, PublicKey};

View File

@@ -13,7 +13,6 @@ use slot_clock::{SlotClock, SystemTimeSlotClock};
use std::time::{Duration, SystemTime};
use store::config::StoreConfig;
use store::{HotColdDB, MemoryStore};
use tempfile::tempdir;
use types::{CommitteeIndex, EthSpec, MinimalEthSpec};
const SLOT_DURATION_MILLIS: u64 = 400;
@@ -32,7 +31,6 @@ pub struct TestBeaconChain {
impl TestBeaconChain {
pub fn new_with_system_clock() -> Self {
let data_dir = tempdir().expect("should create temporary data_dir");
let spec = MinimalEthSpec::default_spec();
let keypairs = generate_deterministic_keypairs(1);
@@ -48,7 +46,6 @@ impl TestBeaconChain {
.logger(log.clone())
.custom_spec(spec.clone())
.store(Arc::new(store))
.data_dir(data_dir.path().to_path_buf())
.genesis_state(
interop_genesis_state::<MinimalEthSpec>(&keypairs, 0, &spec)
.expect("should generate interop state"),

View File

@@ -5,12 +5,16 @@ authors = ["Michael Sproul <michael@sigmaprime.io>"]
edition = "2018"
[dependencies]
itertools = "0.10.0"
int_to_bytes = { path = "../../consensus/int_to_bytes" }
lazy_static = "1.4.0"
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
parking_lot = "0.11.0"
types = { path = "../../consensus/types" }
state_processing = { path = "../../consensus/state_processing" }
eth2_ssz = "0.1.2"
eth2_ssz_derive = "0.1.0"
rayon = "1.5.0"
serde = "1.0.116"
serde_derive = "1.0.116"
store = { path = "../store" }

View File

@@ -3,6 +3,7 @@ use state_processing::common::{get_attesting_indices, get_base_reward};
use std::collections::HashMap;
use types::{Attestation, BeaconState, BitList, ChainSpec, EthSpec};
#[derive(Debug, Clone)]
pub struct AttMaxCover<'a, T: EthSpec> {
/// Underlying attestation.
att: &'a Attestation<T>,
@@ -44,8 +45,8 @@ impl<'a, T: EthSpec> MaxCover for AttMaxCover<'a, T> {
type Object = Attestation<T>;
type Set = HashMap<u64, u64>;
fn object(&self) -> Attestation<T> {
self.att.clone()
fn object(&self) -> &Attestation<T> {
self.att
}
fn covering_set(&self) -> &HashMap<u64, u64> {
@@ -100,8 +101,6 @@ pub fn earliest_attestation_validators<T: EthSpec>(
state_attestations
.iter()
// In a single epoch, an attester should only be attesting for one slot and index.
// TODO: we avoid including slashable attestations in the state here,
// but maybe we should do something else with them (like construct slashings).
.filter(|existing_attestation| {
existing_attestation.data.slot == attestation.data.slot
&& existing_attestation.data.index == attestation.data.index

View File

@@ -3,6 +3,7 @@ use state_processing::per_block_processing::get_slashable_indices_modular;
use std::collections::{HashMap, HashSet};
use types::{AttesterSlashing, BeaconState, ChainSpec, EthSpec};
#[derive(Debug, Clone)]
pub struct AttesterSlashingMaxCover<'a, T: EthSpec> {
slashing: &'a AttesterSlashing<T>,
effective_balances: HashMap<u64, u64>,
@@ -46,8 +47,8 @@ impl<'a, T: EthSpec> MaxCover for AttesterSlashingMaxCover<'a, T> {
type Set = HashMap<u64, u64>;
/// Extract an object for inclusion in a solution.
fn object(&self) -> AttesterSlashing<T> {
self.slashing.clone()
fn object(&self) -> &AttesterSlashing<T> {
self.slashing
}
/// Get the set of elements covered.

View File

@@ -2,6 +2,7 @@ mod attestation;
mod attestation_id;
mod attester_slashing;
mod max_cover;
mod metrics;
mod persistence;
pub use persistence::PersistedOperationPool;
@@ -9,7 +10,7 @@ pub use persistence::PersistedOperationPool;
use attestation::AttMaxCover;
use attestation_id::AttestationId;
use attester_slashing::AttesterSlashingMaxCover;
use max_cover::maximum_cover;
use max_cover::{maximum_cover, MaxCover};
use parking_lot::RwLock;
use state_processing::per_block_processing::errors::AttestationValidationError;
use state_processing::per_block_processing::{
@@ -96,49 +97,29 @@ impl<T: EthSpec> OperationPool<T> {
self.attestations.read().values().map(Vec::len).sum()
}
/// Get a list of attestations for inclusion in a block.
///
/// The `validity_filter` is a closure that provides extra filtering of the attestations
/// before an approximately optimal bundle is constructed. We use it to provide access
/// to the fork choice data from the `BeaconChain` struct that doesn't logically belong
/// in the operation pool.
pub fn get_attestations(
&self,
state: &BeaconState<T>,
validity_filter: impl FnMut(&&Attestation<T>) -> bool,
spec: &ChainSpec,
) -> Result<Vec<Attestation<T>>, OpPoolError> {
// Attestations for the current fork, which may be from the current or previous epoch.
let prev_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
let prev_domain_bytes = AttestationId::compute_domain_bytes(
prev_epoch,
/// Return all valid attestations for the given epoch, for use in max cover.
fn get_valid_attestations_for_epoch<'a>(
&'a self,
epoch: Epoch,
all_attestations: &'a HashMap<AttestationId, Vec<Attestation<T>>>,
state: &'a BeaconState<T>,
total_active_balance: u64,
validity_filter: impl FnMut(&&Attestation<T>) -> bool + Send,
spec: &'a ChainSpec,
) -> impl Iterator<Item = AttMaxCover<'a, T>> + Send {
let domain_bytes = AttestationId::compute_domain_bytes(
epoch,
&state.fork,
state.genesis_validators_root,
spec,
);
let curr_domain_bytes = AttestationId::compute_domain_bytes(
current_epoch,
&state.fork,
state.genesis_validators_root,
spec,
);
let reader = self.attestations.read();
let active_indices = state
.get_cached_active_validator_indices(RelativeEpoch::Current)
.map_err(OpPoolError::GetAttestationsTotalBalanceError)?;
let total_active_balance = state
.get_total_balance(&active_indices, spec)
.map_err(OpPoolError::GetAttestationsTotalBalanceError)?;
let valid_attestations = reader
all_attestations
.iter()
.filter(|(key, _)| {
key.domain_bytes_match(&prev_domain_bytes)
|| key.domain_bytes_match(&curr_domain_bytes)
})
.filter(move |(key, _)| key.domain_bytes_match(&domain_bytes))
.flat_map(|(_, attestations)| attestations)
// That are valid...
.filter(|attestation| {
.filter(move |attestation| attestation.data.target.epoch == epoch)
.filter(move |attestation| {
// Ensure attestations are valid for block inclusion
verify_attestation_for_block_inclusion(
state,
attestation,
@@ -148,10 +129,77 @@ impl<T: EthSpec> OperationPool<T> {
.is_ok()
})
.filter(validity_filter)
.flat_map(|att| AttMaxCover::new(att, state, total_active_balance, spec));
.filter_map(move |att| AttMaxCover::new(att, state, total_active_balance, spec))
}
Ok(maximum_cover(
valid_attestations,
/// Get a list of attestations for inclusion in a block.
///
/// The `validity_filter` is a closure that provides extra filtering of the attestations
/// before an approximately optimal bundle is constructed. We use it to provide access
/// to the fork choice data from the `BeaconChain` struct that doesn't logically belong
/// in the operation pool.
pub fn get_attestations(
&self,
state: &BeaconState<T>,
prev_epoch_validity_filter: impl FnMut(&&Attestation<T>) -> bool + Send,
curr_epoch_validity_filter: impl FnMut(&&Attestation<T>) -> bool + Send,
spec: &ChainSpec,
) -> Result<Vec<Attestation<T>>, OpPoolError> {
// Attestations for the current fork, which may be from the current or previous epoch.
let prev_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
let all_attestations = self.attestations.read();
let active_indices = state
.get_cached_active_validator_indices(RelativeEpoch::Current)
.map_err(OpPoolError::GetAttestationsTotalBalanceError)?;
let total_active_balance = state
.get_total_balance(&active_indices, spec)
.map_err(OpPoolError::GetAttestationsTotalBalanceError)?;
// Split attestations for the previous & current epochs, so that we
// can optimise them individually in parallel.
let prev_epoch_att = self.get_valid_attestations_for_epoch(
prev_epoch,
&*all_attestations,
state,
total_active_balance,
prev_epoch_validity_filter,
spec,
);
let curr_epoch_att = self.get_valid_attestations_for_epoch(
current_epoch,
&*all_attestations,
state,
total_active_balance,
curr_epoch_validity_filter,
spec,
);
let prev_epoch_limit = std::cmp::min(
T::MaxPendingAttestations::to_usize()
.saturating_sub(state.previous_epoch_attestations.len()),
T::MaxAttestations::to_usize(),
);
let (prev_cover, curr_cover) = rayon::join(
move || {
let _timer = metrics::start_timer(&metrics::ATTESTATION_PREV_EPOCH_PACKING_TIME);
// If we're in the genesis epoch, just use the current epoch attestations.
if prev_epoch == current_epoch {
vec![]
} else {
maximum_cover(prev_epoch_att, prev_epoch_limit)
}
},
move || {
let _timer = metrics::start_timer(&metrics::ATTESTATION_CURR_EPOCH_PACKING_TIME);
maximum_cover(curr_epoch_att, T::MaxAttestations::to_usize())
},
);
Ok(max_cover::merge_solutions(
curr_cover,
prev_cover,
T::MaxAttestations::to_usize(),
))
}
@@ -231,7 +279,10 @@ impl<T: EthSpec> OperationPool<T> {
let attester_slashings = maximum_cover(
relevant_attester_slashings,
T::MaxAttesterSlashings::to_usize(),
);
)
.into_iter()
.map(|cover| cover.object().clone())
.collect();
(proposer_slashings, attester_slashings)
}
@@ -619,7 +670,7 @@ mod release_tests {
state.slot -= 1;
assert_eq!(
op_pool
.get_attestations(state, |_| true, spec)
.get_attestations(state, |_| true, |_| true, spec)
.expect("should have attestations")
.len(),
0
@@ -629,7 +680,7 @@ mod release_tests {
state.slot += spec.min_attestation_inclusion_delay;
let block_attestations = op_pool
.get_attestations(state, |_| true, spec)
.get_attestations(state, |_| true, |_| true, spec)
.expect("Should have block attestations");
assert_eq!(block_attestations.len(), committees.len());
@@ -799,7 +850,7 @@ mod release_tests {
state.slot += spec.min_attestation_inclusion_delay;
let best_attestations = op_pool
.get_attestations(state, |_| true, spec)
.get_attestations(state, |_| true, |_| true, spec)
.expect("should have best attestations");
assert_eq!(best_attestations.len(), max_attestations);
@@ -874,7 +925,7 @@ mod release_tests {
state.slot += spec.min_attestation_inclusion_delay;
let best_attestations = op_pool
.get_attestations(state, |_| true, spec)
.get_attestations(state, |_| true, |_| true, spec)
.expect("should have valid best attestations");
assert_eq!(best_attestations.len(), max_attestations);

View File

@@ -1,3 +1,5 @@
use itertools::Itertools;
/// Trait for types that we can compute a maximum cover for.
///
/// Terminology:
@@ -5,14 +7,14 @@
/// * `element`: something contained in a set, and covered by the covering set of an item
/// * `object`: something extracted from an item in order to comprise a solution
/// See: https://en.wikipedia.org/wiki/Maximum_coverage_problem
pub trait MaxCover {
pub trait MaxCover: Clone {
/// The result type, of which we would eventually like a collection of maximal quality.
type Object;
type Object: Clone;
/// The type used to represent sets.
type Set: Clone;
/// Extract an object for inclusion in a solution.
fn object(&self) -> Self::Object;
fn object(&self) -> &Self::Object;
/// Get the set of elements covered.
fn covering_set(&self) -> &Self::Set;
@@ -42,7 +44,7 @@ impl<T> MaxCoverItem<T> {
///
/// * Time complexity: `O(limit * items_iter.len())`
/// * Space complexity: `O(item_iter.len())`
pub fn maximum_cover<I, T>(items_iter: I, limit: usize) -> Vec<T::Object>
pub fn maximum_cover<I, T>(items_iter: I, limit: usize) -> Vec<T>
where
I: IntoIterator<Item = T>,
T: MaxCover,
@@ -58,14 +60,14 @@ where
for _ in 0..limit {
// Select the item with the maximum score.
let (best_item, best_cover) = match all_items
let best = match all_items
.iter_mut()
.filter(|x| x.available && x.item.score() != 0)
.max_by_key(|x| x.item.score())
{
Some(x) => {
x.available = false;
(x.item.object(), x.item.covering_set().clone())
x.item.clone()
}
None => return result,
};
@@ -75,14 +77,32 @@ where
all_items
.iter_mut()
.filter(|x| x.available && x.item.score() != 0)
.for_each(|x| x.item.update_covering_set(&best_item, &best_cover));
.for_each(|x| {
x.item
.update_covering_set(best.object(), best.covering_set())
});
result.push(best_item);
result.push(best);
}
result
}
/// Perform a greedy merge of two max cover solutions, preferring higher-score values.
pub fn merge_solutions<I1, I2, T>(cover1: I1, cover2: I2, limit: usize) -> Vec<T::Object>
where
I1: IntoIterator<Item = T>,
I2: IntoIterator<Item = T>,
T: MaxCover,
{
cover1
.into_iter()
.merge_by(cover2, |item1, item2| item1.score() >= item2.score())
.take(limit)
.map(|item| item.object().clone())
.collect()
}
#[cfg(test)]
mod test {
use super::*;
@@ -96,8 +116,8 @@ mod test {
type Object = Self;
type Set = Self;
fn object(&self) -> Self {
self.clone()
fn object(&self) -> &Self {
self
}
fn covering_set(&self) -> &Self {

View File

@@ -0,0 +1,14 @@
use lazy_static::lazy_static;
pub use lighthouse_metrics::*;
lazy_static! {
pub static ref ATTESTATION_PREV_EPOCH_PACKING_TIME: Result<Histogram> = try_create_histogram(
"op_pool_attestation_prev_epoch_packing_time",
"Time to pack previous epoch attestations"
);
pub static ref ATTESTATION_CURR_EPOCH_PACKING_TIME: Result<Histogram> = try_create_histogram(
"op_pool_attestation_curr_epoch_packing_time",
"Time to pack current epoch attestations"
);
}

View File

@@ -142,8 +142,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.short("x")
.long("disable-enr-auto-update")
.help("Discovery automatically updates the nodes local ENR with an external IP address and port as seen by other peers on the network. \
This disables this feature, fixing the ENR's IP/PORT to those specified on boot.")
.takes_value(true),
This disables this feature, fixing the ENR's IP/PORT to those specified on boot."),
)
.arg(
Arg::with_name("libp2p-addresses")

View File

@@ -42,7 +42,7 @@ The exit phrase is the following:
Below is an example for initiating a voluntary exit on the Pyrmont testnet.
```
$ lighthouse --network pyrmont account validator exit --keystore /path/to/keystore --beacon-nodes http://localhost:5052
$ lighthouse --network pyrmont account validator exit --keystore /path/to/keystore --beacon-node http://localhost:5052
Running account manager for pyrmont network
validator-dir path: ~/.lighthouse/pyrmont/validators

View File

@@ -1,6 +1,6 @@
[package]
name = "boot_node"
version = "1.2.2"
version = "1.3.0"
authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2018"

View File

@@ -18,14 +18,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
If a DNS address is provided, the enr-address is set to the IP address it resolves to and \
does not auto-update based on PONG responses in discovery.")
.required(true)
.takes_value(true),
.takes_value(true)
.conflicts_with("network-dir")
)
.arg(
Arg::with_name("port")
.long("port")
.value_name("PORT")
.help("The UDP port to listen on.")
.default_value("9000")
.takes_value(true),
.takes_value(true)
)
.arg(
Arg::with_name("listen-address")
@@ -48,7 +50,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.long("enr-port")
.value_name("PORT")
.help("The UDP port of the boot node's ENR. This is the port that external peers will dial to reach this boot node. Set this only if the external port differs from the listening port.")
.takes_value(true),
.takes_value(true)
.conflicts_with("network-dir")
)
.arg(
Arg::with_name("enable-enr-auto-update")
@@ -57,4 +60,11 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.help("Discovery can automatically update the node's local ENR with an external IP address and port as seen by other peers on the network. \
This enables this feature.")
)
.arg(
Arg::with_name("network-dir")
.value_name("NETWORK_DIR")
.long("network-dir")
.help("The directory which contains the enr and it's assoicated private key")
.takes_value(true)
)
}

View File

@@ -2,13 +2,13 @@ use beacon_node::{get_data_dir, get_eth2_network_config, set_network_config};
use clap::ArgMatches;
use eth2_libp2p::discv5::{enr::CombinedKey, Enr};
use eth2_libp2p::{
discovery::{create_enr_builder_from_config, use_or_load_enr},
discovery::{create_enr_builder_from_config, load_enr_from_disk, use_or_load_enr},
load_private_key, CombinedKeyExt, NetworkConfig,
};
use ssz::Encode;
use std::convert::TryFrom;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::{marker::PhantomData, path::PathBuf};
use types::EthSpec;
/// A set of configuration parameters for the bootnode, established from CLI arguments.
@@ -68,59 +68,65 @@ impl<T: EthSpec> TryFrom<&ArgMatches<'_>> for BootNodeConfig<T> {
);
}
let private_key = load_private_key(&network_config, &logger);
let local_key = CombinedKey::from_libp2p(&private_key)?;
// build the enr_fork_id and add it to the local_enr if it exists
let enr_fork = {
let spec = eth2_network_config
.yaml_config
.as_ref()
.ok_or("The network directory must contain a spec config")?
.apply_to_chain_spec::<T>(&T::default_spec())
.ok_or("The loaded config is not compatible with the current spec")?;
if eth2_network_config.beacon_state_is_known() {
let genesis_state = eth2_network_config.beacon_state::<T>()?;
slog::info!(logger, "Genesis state found"; "root" => genesis_state.canonical_root().to_string());
let enr_fork = spec.enr_fork_id(
types::Slot::from(0u64),
genesis_state.genesis_validators_root,
);
Some(enr_fork.as_ssz_bytes())
} else {
slog::warn!(
logger,
"No genesis state provided. No Eth2 field added to the ENR"
);
None
}
};
// Build the local ENR
let mut local_enr = {
let mut builder = create_enr_builder_from_config(&network_config, false);
// If we know of the ENR field, add it to the initial construction
if let Some(enr_fork_bytes) = enr_fork {
builder.add_value("eth2", &enr_fork_bytes);
}
builder
.build(&local_key)
.map_err(|e| format!("Failed to build ENR: {:?}", e))?
};
use_or_load_enr(&local_key, &mut local_enr, &network_config, &logger)?;
let auto_update = matches.is_present("enable-enr_auto_update");
// the address to listen on
let listen_socket =
SocketAddr::new(network_config.listen_address, network_config.discovery_port);
let private_key = load_private_key(&network_config, &logger);
let local_key = CombinedKey::from_libp2p(&private_key)?;
let local_enr = if let Some(dir) = matches.value_of("network-dir") {
let network_dir: PathBuf = dir.into();
load_enr_from_disk(&network_dir)?
} else {
// build the enr_fork_id and add it to the local_enr if it exists
let enr_fork = {
let spec = eth2_network_config
.yaml_config
.as_ref()
.ok_or("The network directory must contain a spec config")?
.apply_to_chain_spec::<T>(&T::default_spec())
.ok_or("The loaded config is not compatible with the current spec")?;
if eth2_network_config.beacon_state_is_known() {
let genesis_state = eth2_network_config.beacon_state::<T>()?;
slog::info!(logger, "Genesis state found"; "root" => genesis_state.canonical_root().to_string());
let enr_fork = spec.enr_fork_id(
types::Slot::from(0u64),
genesis_state.genesis_validators_root,
);
Some(enr_fork.as_ssz_bytes())
} else {
slog::warn!(
logger,
"No genesis state provided. No Eth2 field added to the ENR"
);
None
}
};
// Build the local ENR
let mut local_enr = {
let mut builder = create_enr_builder_from_config(&network_config, false);
// If we know of the ENR field, add it to the initial construction
if let Some(enr_fork_bytes) = enr_fork {
builder.add_value("eth2", &enr_fork_bytes);
}
builder
.build(&local_key)
.map_err(|e| format!("Failed to build ENR: {:?}", e))?
};
use_or_load_enr(&local_key, &mut local_enr, &network_config, &logger)?;
local_enr
};
Ok(BootNodeConfig {
listen_socket,
boot_nodes,

View File

@@ -16,7 +16,7 @@ pub const VERSION: &str = git_version!(
// NOTE: using --match instead of --exclude for compatibility with old Git
"--match=thiswillnevermatchlol"
],
prefix = "Lighthouse/v1.2.2-",
prefix = "Lighthouse/v1.3.0-",
fallback = "unknown"
);

View File

@@ -75,4 +75,16 @@ pub trait SlotClock: Send + Sync + Sized + Clone {
self.slot_of(self.now_duration()?.checked_sub(tolerance)?)
.or_else(|| Some(self.genesis_slot()))
}
/// Returns the delay between the start of the slot and when unaggregated attestations should be
/// produced.
fn unagg_attestation_production_delay(&self) -> Duration {
self.slot_duration() / 3
}
/// Returns the delay between the start of the slot and when aggregated attestations should be
/// produced.
fn agg_attestation_production_delay(&self) -> Duration {
self.slot_duration() * 2 / 3
}
}

View File

@@ -1,7 +1,7 @@
[package]
name = "lcli"
description = "Lighthouse CLI (modeled after zcli)"
version = "1.2.2"
version = "1.3.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
@@ -37,3 +37,5 @@ lighthouse_version = { path = "../common/lighthouse_version" }
directory = { path = "../common/directory" }
account_utils = { path = "../common/account_utils" }
eth2_wallet = { path = "../crypto/eth2_wallet" }
web3 = "0.14.0"
eth1_test_rig = { path = "../testing/eth1_test_rig" }

View File

@@ -0,0 +1,33 @@
use clap::ArgMatches;
use environment::Environment;
use types::EthSpec;
use web3::{transports::Http, Web3};
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches<'_>) -> Result<(), String> {
let eth1_http: String = clap_utils::parse_required(matches, "eth1-http")?;
let confirmations: usize = clap_utils::parse_required(matches, "confirmations")?;
let validator_count: Option<usize> = clap_utils::parse_optional(matches, "validator-count")?;
let transport =
Http::new(&eth1_http).map_err(|e| format!("Unable to connect to eth1 HTTP: {:?}", e))?;
let web3 = Web3::new(transport);
env.runtime().block_on(async {
let contract = eth1_test_rig::DepositContract::deploy(web3, confirmations, None)
.await
.map_err(|e| format!("Failed to deploy deposit contract: {:?}", e))?;
println!("Deposit contract address: {:?}", contract.address());
// Deposit insecure validators to the deposit contract created
if let Some(validator_count) = validator_count {
let amount = env.eth2_config.spec.max_effective_balance;
for i in 0..validator_count {
println!("Submitting deposit for validator {}...", i);
contract.deposit_deterministic_async::<T>(i, amount).await?;
}
}
Ok(())
})
}

View File

@@ -3,11 +3,13 @@ use std::fs;
use std::path::PathBuf;
use validator_dir::Builder as ValidatorBuilder;
pub fn run(matches: &ArgMatches) -> Result<(), String> {
let validator_count: usize = clap_utils::parse_required(matches, "count")?;
let validators_dir: PathBuf = clap_utils::parse_required(matches, "validators-dir")?;
let secrets_dir: PathBuf = clap_utils::parse_required(matches, "secrets-dir")?;
/// Generates validator directories with INSECURE, deterministic keypairs given the range
/// of indices, validator and secret directories.
pub fn generate_validator_dirs(
indices: &[usize],
validators_dir: PathBuf,
secrets_dir: PathBuf,
) -> Result<(), String> {
if !validators_dir.exists() {
fs::create_dir_all(&validators_dir)
.map_err(|e| format!("Unable to create validators dir: {:?}", e))?;
@@ -18,13 +20,13 @@ pub fn run(matches: &ArgMatches) -> Result<(), String> {
.map_err(|e| format!("Unable to create secrets dir: {:?}", e))?;
}
for i in 0..validator_count {
println!("Validator {}/{}", i + 1, validator_count);
for i in indices {
println!("Validator {}", i + 1);
ValidatorBuilder::new(validators_dir.clone())
.password_dir(secrets_dir.clone())
.store_withdrawal_keystore(false)
.insecure_voting_keypair(i)
.insecure_voting_keypair(*i)
.map_err(|e| format!("Unable to generate keys: {:?}", e))?
.build()
.map_err(|e| format!("Unable to build validator: {:?}", e))?;
@@ -32,3 +34,31 @@ pub fn run(matches: &ArgMatches) -> Result<(), String> {
Ok(())
}
pub fn run(matches: &ArgMatches) -> Result<(), String> {
let validator_count: usize = clap_utils::parse_required(matches, "count")?;
let base_dir: PathBuf = clap_utils::parse_required(matches, "base-dir")?;
let node_count: Option<usize> = clap_utils::parse_optional(matches, "node-count")?;
if let Some(node_count) = node_count {
let validators_per_node = validator_count / node_count;
let validator_range = (0..validator_count).collect::<Vec<_>>();
let indices_range = validator_range
.chunks(validators_per_node)
.collect::<Vec<_>>();
for (i, indices) in indices_range.iter().enumerate() {
let validators_dir = base_dir.join(format!("node_{}", i + 1)).join("validators");
let secrets_dir = base_dir.join(format!("node_{}", i + 1)).join("secrets");
generate_validator_dirs(indices, validators_dir, secrets_dir)?;
}
} else {
let validators_dir = base_dir.join("validators");
let secrets_dir = base_dir.join("secrets");
generate_validator_dirs(
(0..validator_count).collect::<Vec<_>>().as_slice(),
validators_dir,
secrets_dir,
)?;
}
Ok(())
}

View File

@@ -2,6 +2,7 @@
extern crate log;
mod change_genesis_time;
mod check_deposit_data;
mod deploy_deposit_contract;
mod eth1_genesis;
mod generate_bootnode_enr;
mod insecure_validators;
@@ -155,6 +156,38 @@ fn main() {
.help("SSZ encoded as 0x-prefixed hex"),
),
)
.subcommand(
SubCommand::with_name("deploy-deposit-contract")
.about(
"Deploy a testing eth1 deposit contract.",
)
.arg(
Arg::with_name("eth1-http")
.long("eth1-http")
.short("e")
.value_name("ETH1_HTTP_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("confirmations")
.value_name("INTEGER")
.long("confirmations")
.takes_value(true)
.default_value("3")
.help("The number of block confirmations before declaring the contract deployed."),
)
.arg(
Arg::with_name("validator-count")
.value_name("VALIDATOR_COUNT")
.long("validator-count")
.takes_value(true)
.help("If present, makes `validator_count` number of INSECURE deterministic deposits after \
deploying the deposit contract."
),
)
)
.subcommand(
SubCommand::with_name("eth1-genesis")
.about("Listens to the eth1 chain and finds the genesis beacon state")
@@ -343,6 +376,20 @@ fn main() {
non-default.",
),
)
.arg(
Arg::with_name("seconds-per-eth1-block")
.long("seconds-per-eth1-block")
.value_name("SECONDS")
.takes_value(true)
.help("Eth1 block time"),
)
.arg(
Arg::with_name("eth1-id")
.long("eth1-id")
.value_name("ETH1_ID")
.takes_value(true)
.help("The chain id and network id for the eth1 testnet."),
)
.arg(
Arg::with_name("deposit-contract-address")
.long("deposit-contract-address")
@@ -446,19 +493,19 @@ fn main() {
.help("Produces validators in the range of 0..count."),
)
.arg(
Arg::with_name("validators-dir")
.long("validators-dir")
.value_name("VALIDATOR_DIR")
Arg::with_name("base-dir")
.long("base-dir")
.value_name("BASE_DIR")
.takes_value(true)
.help("The directory for storing validators."),
.help("The base directory where validator keypairs and secrets are stored"),
)
.arg(
Arg::with_name("secrets-dir")
.long("secrets-dir")
.value_name("SECRETS_DIR")
Arg::with_name("node-count")
.long("node-count")
.value_name("NODE_COUNT")
.takes_value(true)
.help("The directory for storing secrets."),
),
.help("The number of nodes to divide the validator keys to"),
)
)
.get_matches();
@@ -540,6 +587,10 @@ fn run<T: EthSpec>(
("pretty-hex", Some(matches)) => {
run_parse_hex::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
}
("deploy-deposit-contract", Some(matches)) => {
deploy_deposit_contract::run::<T>(env, matches)
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
}
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, matches)
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e)),
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(env, matches)

View File

@@ -48,6 +48,9 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
maybe_update!("ejection-balance", ejection_balance);
maybe_update!("eth1-follow-distance", eth1_follow_distance);
maybe_update!("genesis-delay", genesis_delay);
maybe_update!("eth1-id", deposit_chain_id);
maybe_update!("eth1-id", deposit_network_id);
maybe_update!("seconds-per-eth1-block", seconds_per_eth1_block);
if let Some(v) = parse_ssz_optional(matches, "genesis-fork-version")? {
spec.genesis_fork_version = v;

View File

@@ -1,6 +1,6 @@
[package]
name = "lighthouse"
version = "1.2.2"
version = "1.3.0"
authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2018"

View File

@@ -1,58 +1,67 @@
# Simple Local Testnet
These scripts allow for running a small local testnet with two beacon nodes and
one validator client. This setup can be useful for testing and development.
These scripts allow for running a small local testnet with multiple beacon nodes and validator clients.
This setup can be useful for testing and development.
## Requirements
The scripts require `lci` and `lighthouse` to be installed on `PATH`. From the
The scripts require `lcli` and `lighthouse` to be installed on `PATH`. From the
root of this repository, run:
```bash
cargo install --path lighthouse --force --locked
cargo install --path lcli --force --locked
make
make install-lcli
```
## Starting the testnet
Assuming you are happy with the configuration in `var.env`, create the testnet
directory, genesis state and validator keys with:
Start a local eth1 ganache server
```bash
./ganache_test_node.sh
```
Assuming you are happy with the configuration in `var.env`, deploy the deposit contract, make deposits,
create the testnet directory, genesis state and validator keys with:
```bash
./setup.sh
```
Start the first beacon node:
Generate bootnode enr and start a discv5 bootnode so that multiple beacon nodes can find each other
```bash
./bootnode.sh
```
Start a beacon node:
```bash
./beacon_node.sh
./beacon_node.sh <DATADIR> <NETWORK-PORT> <HTTP-PORT> <OPTIONAL-DEBUG-LEVEL>
```
e.g.
```bash
./beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9000 8000
```
In a new terminal, start the validator client which will attach to the first
beacon node:
```bash
./validator_client.sh
./validator_client.sh <DATADIR> <BEACON-NODE-HTTP> <OPTIONAL-DEBUG-LEVEL>
```
In a new terminal, start the second beacon node which will peer with the first:
e.g. to attach to the above created beacon node
```bash
./second_beacon_node.sh
./validator_client.sh $HOME/.lighthouse/local-testnet/node_1 http://localhost:8000
```
You can create additional beacon node and validator client instances with appropriate parameters.
## Additional Info
### Debug level
The beacon nodes and validator client have their `--debug-level` set to `info`.
Specify a different debug level like this:
```bash
./validator_client.sh debug
./beacon_node.sh trace
./second_beacon_node.sh warn
```
### Adjusting number and distribution of validators
The `VALIDATOR_COUNT` parameter is used to specify the number of insecure validator keystores to generate and make deposits for.
The `NODE_COUNT` parameter is used to adjust the division of these generated keys among separate validator client instances.
For e.g. for `VALIDATOR_COUNT=80` and `NODE_COUNT=4`, the validator keys are distributed over 4 datadirs with 20 keystores per datadir. The datadirs are located in `$DATADIR/node_{i}` which can be passed to separate validator client
instances using the `--datadir` parameter.
### Starting fresh
@@ -62,7 +71,6 @@ Delete the current testnet and all related files using:
./clean.sh
```
### Updating the genesis time of the beacon state
If it's been a while since you ran `./setup` then the genesis time of the

View File

@@ -2,20 +2,22 @@
#
# Starts a beacon node based upon a genesis state created by
# `./local_testnet_genesis_state`.
# `./setup.sh`.
#
# Usage: ./beacon_node.sh <DATADIR> <NETWORK-PORT> <HTTP-PORT> <OPTIONAL-DEBUG-LEVEL>
source ./vars.env
DEBUG_LEVEL=${1:-info}
DEBUG_LEVEL=${4:-info}
exec lighthouse \
--debug-level $DEBUG_LEVEL \
bn \
--datadir $BEACON_DIR \
--datadir $1 \
--testnet-dir $TESTNET_DIR \
--dummy-eth1 \
--http \
--staking \
--enr-address 127.0.0.1 \
--enr-udp-port 9000 \
--enr-tcp-port 9000 \
--enr-udp-port $2 \
--enr-tcp-port $2 \
--port $2 \
--http-port $3

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
#
# Generates a bootnode enr and saves it in $TESTNET/boot_enr.yaml
# Starts a bootnode from the generated enr.
#
source ./vars.env
echo "Generating bootnode enr"
lcli \
generate-bootnode-enr \
--ip 127.0.0.1 \
--udp-port $BOOTNODE_PORT \
--tcp-port $BOOTNODE_PORT \
--genesis-fork-version $GENESIS_FORK_VERSION \
--output-dir $DATADIR/bootnode
bootnode_enr=`cat $DATADIR/bootnode/enr.dat`
echo "- $bootnode_enr" > $TESTNET_DIR/boot_enr.yaml
echo "Generated bootnode enr and written to $TESTNET_DIR/boot_enr.yaml"
DEBUG_LEVEL=${1:-info}
echo "Starting bootnode"
exec lighthouse boot_node \
--testnet-dir $TESTNET_DIR \
--port $BOOTNODE_PORT \
--listen-address 127.0.0.1 \
--network-dir $DATADIR/bootnode \

View File

@@ -1,8 +1,13 @@
#!/usr/bin/env bash
source ./vars.env
ganache-cli \
--defaultBalanceEther 1000000000 \
--gasLimit 1000000000 \
--accounts 10 \
--mnemonic "vast thought differ pull jewel broom cook wrist tribe word before omit" \
--mnemonic "$ETH1_NETWORK_MNEMONIC" \
--port 8545 \
--blockTime 3 \
--networkId 4242 \
--chainId 4242

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bash
#
# Starts a beacon node based upon a genesis state created by
# `./local_testnet_genesis_state`.
#
source ./vars.env
DEBUG_LEVEL=${1:-info}
exec lighthouse \
--debug-level $DEBUG_LEVEL \
bn \
--datadir $BEACON_DIR-2 \
--testnet-dir $TESTNET_DIR \
--dummy-eth1 \
--http \
--port 9902 \
--http-port 6052 \
--boot-nodes $(cat $BEACON_DIR/beacon/network/enr.dat)

View File

@@ -1,18 +1,38 @@
#!/usr/bin/env bash
#
# Deploys the deposit contract and makes deposits for $VALIDATOR_COUNT insecure deterministic validators.
# Produces a testnet specification and a genesis state where the genesis time
# is now.
# is now + $GENESIS_DELAY.
#
# Generates datadirs for multiple validator keys according to the
# $VALIDATOR_COUNT and $NODE_COUNT variables.
#
source ./vars.env
lcli \
deploy-deposit-contract \
--eth1-http http://localhost:8545 \
--confirmations 1 \
--validator-count $VALIDATOR_COUNT
NOW=`date +%s`
GENESIS_TIME=`expr $NOW + $GENESIS_DELAY`
lcli \
--spec mainnet \
new-testnet \
--deposit-contract-address 1234567890123456789012345678901234567890 \
--deposit-contract-address $DEPOSIT_CONTRACT_ADDRESS \
--testnet-dir $TESTNET_DIR \
--min-genesis-active-validator-count $VALIDATOR_COUNT \
--min-genesis-active-validator-count $GENESIS_VALIDATOR_COUNT \
--min-genesis-time $GENESIS_TIME \
--genesis-delay $GENESIS_DELAY \
--genesis-fork-version $GENESIS_FORK_VERSION \
--eth1-id $BOOTNODE_PORT \
--eth1-follow-distance 1 \
--seconds-per-eth1-block 1 \
--force
echo Specification generated at $TESTNET_DIR.
@@ -21,16 +41,17 @@ echo "Generating $VALIDATOR_COUNT validators concurrently... (this may take a wh
lcli \
insecure-validators \
--count $VALIDATOR_COUNT \
--validators-dir $VALIDATORS_DIR \
--secrets-dir $SECRETS_DIR
--base-dir $DATADIR \
--node-count $NODE_COUNT
echo Validators generated at $VALIDATORS_DIR with keystore passwords at $SECRETS_DIR.
echo Validators generated with keystore passwords at $DATADIR.
echo "Building genesis state... (this might take a while)"
lcli \
--spec mainnet \
interop-genesis \
--genesis-time $GENESIS_TIME \
--testnet-dir $TESTNET_DIR \
$VALIDATOR_COUNT
$GENESIS_VALIDATOR_COUNT
echo Created genesis state in $TESTNET_DIR
echo Created genesis state in $TESTNET_DIR

View File

@@ -2,17 +2,18 @@
#
# Starts a validator client based upon a genesis state created by
# `./local_testnet_genesis_state`.
# `./setup.sh`.
#
# Usage: ./validator_client.sh <DATADIR> <BEACON-NODE-HTTP> <OPTIONAL-DEBUG-LEVEL>
source ./vars.env
DEBUG_LEVEL=${1:-info}
DEBUG_LEVEL=${3:-info}
exec lighthouse \
--debug-level $DEBUG_LEVEL \
vc \
--datadir $DATADIR \
--datadir $1 \
--testnet-dir $TESTNET_DIR \
--init-slashing-protection \
--allow-unsynced
--beacon-nodes $2

View File

@@ -1,7 +1,22 @@
# Base directories for the validator keys and secrets
DATADIR=~/.lighthouse/local-testnet
TESTNET_DIR=$DATADIR/testnet
BEACON_DIR=$DATADIR/beacon
VALIDATORS_DIR=$DATADIR/validators
SECRETS_DIR=$DATADIR/secrets
VALIDATOR_COUNT=1024
# Directory for the eth2 config
TESTNET_DIR=$DATADIR/testnet
# Mnemonic for the ganache test network
ETH1_NETWORK_MNEMONIC="vast thought differ pull jewel broom cook wrist tribe word before omit"
# Hardcoded deposit contract based on ETH1_NETWORK_MNEMONIC
DEPOSIT_CONTRACT_ADDRESS=8c594691c0e592ffa21f153a16ae41db5befcaaa
GENESIS_FORK_VERSION=0x42424242
VALIDATOR_COUNT=80
GENESIS_VALIDATOR_COUNT=80
# Number of validator client instances that you intend to run
NODE_COUNT=4
GENESIS_DELAY=180
BOOTNODE_PORT=4242

View File

@@ -5,7 +5,7 @@ use std::io::{prelude::*, BufReader};
use std::path::PathBuf;
use std::str::FromStr;
use bls::blst_implementations::PublicKeyBytes;
use bls::PublicKeyBytes;
use types::{graffiti::GraffitiString, Graffiti};
#[derive(Debug)]