diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..f5e4c0356f
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+tests/ef_tests/eth2.0-spec-tests
+target/
diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml
new file mode 100644
index 0000000000..35692aa237
--- /dev/null
+++ b/.github/workflows/test-suite.yml
@@ -0,0 +1,45 @@
+name: test-suite
+
+on: [push]
+
+jobs:
+ cargo-fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: Get latest version of stable Rust
+ run: rustup update stable
+ - name: Check formatting with cargo fmt
+ run: make cargo-fmt
+ release-tests-ubuntu:
+ runs-on: ubuntu-latest
+ needs: cargo-fmt
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install ganache-cli
+ run: sudo npm install -g ganache-cli
+ - name: Run tests in release
+ run: make test-release
+ debug-tests-ubuntu:
+ runs-on: ubuntu-latest
+ needs: cargo-fmt
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install ganache-cli
+ run: sudo npm install -g ganache-cli
+ - name: Run tests in debug
+ run: make test-debug
+ ef-tests-ubuntu:
+ runs-on: ubuntu-latest
+ needs: cargo-fmt
+ steps:
+ - uses: actions/checkout@v1
+ - name: Run eth2.0-spec-tests with and without fake_crypto
+ run: make test-ef
+ dockerfile-ubuntu:
+ runs-on: ubuntu-latest
+ needs: cargo-fmt
+ steps:
+ - uses: actions/checkout@v1
+ - name: Build the root Dockerfile
+ run: docker build .
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 7f68a0fd19..0000000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-#Adapted from https://users.rust-lang.org/t/my-gitlab-config-docs-tests/16396
-
-default:
- image: 'sigp/lighthouse:latest'
- cache:
- paths:
- - tests/ef_tests/*-v0.9.1.tar.gz
-
-stages:
- - test
- - document
-
-variables:
- CARGO_HOME: /cache/cargocache
-
-check-fmt:
- stage: test
- script:
- - cargo build --manifest-path protos/Cargo.toml
- - cargo fmt --all -- --check
-
-test-dev:
- stage: test
- variables:
- GIT_SUBMODULE_STRATEGY: normal
- script:
- - cargo test --verbose --all
-
-test-release:
- stage: test
- variables:
- GIT_SUBMODULE_STRATEGY: normal
- script:
- - cargo test --verbose --all --release
-
-test-ef:
- stage: test
- variables:
- GIT_SUBMODULE_STRATEGY: normal
- script:
- - make make-ef-tests
- - cargo test --manifest-path tests/ef_tests/Cargo.toml --release --features ef_tests
-
-test-ef-fake-crypto:
- stage: test
- variables:
- GIT_SUBMODULE_STRATEGY: normal
- script:
- - make make-ef-tests
- - cargo test --manifest-path tests/ef_tests/Cargo.toml --release --features ef_tests,fake_crypto
-
-documentation:
- stage: document
- script:
- - cargo doc --no-deps
- - aws s3 sync target/doc/ s3://lighthouse-docs.sigmaprime.io/ --exclude '.lock' --delete
- # Configure the below when we want to have a default page (and update S3 bucket index).
- # - echo '' > public/index.html
- only:
- - master
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b9754eb1eb..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-language: rust
-cache:
- directories:
- - /home/travis/.cargo
-before_install:
- - curl -OL https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip
- - unzip protoc-3.4.0-linux-x86_64.zip -d protoc3
- - sudo mv protoc3/bin/* /usr/local/bin/
- - sudo mv protoc3/include/* /usr/local/include/
- - sudo chown $USER /usr/local/bin/protoc
- - sudo chown -R $USER /usr/local/include/google
-script:
- - cargo build --verbose --all --release
- - cargo fmt --all -- --check
-rust:
- - beta
- - nightly
-matrix:
- allow_failures:
- - rust: beta
- - rust: nightly
- fast_finish: true
-install:
- - rustup component add rustfmt
diff --git a/Cargo.toml b/Cargo.toml
index f7abd8ae2b..23ae186571 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ members = [
"eth2/utils/bls",
"eth2/utils/compare_fields",
"eth2/utils/compare_fields_derive",
+ "eth2/utils/deposit_contract",
"eth2/utils/eth2_config",
"eth2/utils/eth2_interop_keypairs",
"eth2/utils/logging",
@@ -31,16 +32,15 @@ members = [
"beacon_node/rest_api",
"beacon_node/network",
"beacon_node/eth2-libp2p",
- "beacon_node/rpc",
"beacon_node/version",
"beacon_node/eth1",
"beacon_node/beacon_chain",
"beacon_node/websocket_server",
+ "tests/beacon_chain_sim",
"tests/ef_tests",
"tests/eth1_test_rig",
"tests/node_test_rig",
"lcli",
- "protos",
"validator_client",
"account_manager",
"lighthouse",
diff --git a/Dockerfile b/Dockerfile
index 0aa5582066..20ba79202a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,29 +1,4 @@
FROM rust:latest
-RUN apt-get update && apt-get install -y clang libclang-dev cmake build-essential git unzip autoconf libtool awscli software-properties-common
-
-RUN add-apt-repository -y ppa:git-core/ppa
-
-RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
-
-RUN apt-get install -y git-lfs
-
-RUN git clone https://github.com/google/protobuf.git && \
- cd protobuf && \
- ./autogen.sh && \
- ./configure && \
- make && \
- make install && \
- ldconfig && \
- make clean && \
- cd .. && \
- rm -r protobuf
-
-RUN apt-get install -y nodejs npm
-RUN npm install -g ganache-cli --unsafe-perm
-
-RUN mkdir -p /cache/cargocache && chmod -R ugo+rwX /cache/cargocache
-
-ENV CARGO_HOME /cache/cargocache
-
-RUN rustup component add rustfmt clippy
+COPY . lighthouse
+RUN cd lighthouse && make
diff --git a/Makefile b/Makefile
index 1f111b3c11..8a93fef0f5 100644
--- a/Makefile
+++ b/Makefile
@@ -5,24 +5,37 @@ EF_TESTS = "tests/ef_tests"
# Builds the entire workspace in release (optimized).
#
# Binaries will most likely be found in `./target/release`
-release:
- cargo build --release --all
+install:
+ cargo install --path lighthouse --force
-# Runs the full workspace tests, without downloading any additional test
+# Runs the full workspace tests in **release**, without downloading any additional
+# test vectors.
+test-release:
+ cargo test --all --release --exclude ef_tests
+
+# Runs the full workspace tests in **debug**, without downloading any additional test
# vectors.
-test:
- cargo test --all --all-features --release --exclude ef_tests
+test-debug:
+ cargo test --all --exclude ef_tests
+# Runs cargo-fmt (linter).
+cargo-fmt:
+ cargo fmt --all -- --check
-# only run the ef-test vectors
-run-ef-tests:
+# Runs only the ef-test vectors.
+run-ef-tests:
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests"
+ cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,fake_crypto"
+# Downloads and runs the EF test vectors.
test-ef: make-ef-tests run-ef-tests
-# Runs the entire test suite, downloading test vectors if required.
-test-full: test test-ef
+# Runs the full workspace tests in release, without downloading any additional
+# test vectors.
+test: test-release
+# Runs the entire test suite, downloading test vectors if required.
+test-full: cargo-fmt test-release test-debug test-ef
# Runs the makefile in the `ef_tests` repo.
#
diff --git a/README.md b/README.md
index 793a275ef8..402c1c6480 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ An open-source Ethereum 2.0 client, written in Rust and maintained by Sigma Prim
[![Build Status]][Build Link] [![Book Status]][Book Link] [![RustDoc Status]][RustDoc Link] [![Chat Badge]][Chat Link] [![Swagger Badge]][Swagger Link]
-[Build Status]: https://gitlab.sigmaprime.io/sigp/lighthouse/badges/master/build.svg
-[Build Link]: https://gitlab.sigmaprime.io/sigp/lighthouse/pipelines
+[Build Status]: https://github.com/sigp/lighthouse/workflows/test-suite/badge.svg
+[Build Link]: https://github.com/sigp/lighthouse/actions
[Chat Badge]: https://img.shields.io/badge/chat-discord-%237289da
[Chat Link]: https://discord.gg/cyAszAh
[Book Status]:https://img.shields.io/badge/user--docs-master-informational
diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml
index 2838b242de..729adb3353 100644
--- a/account_manager/Cargo.toml
+++ b/account_manager/Cargo.toml
@@ -1,16 +1,25 @@
[package]
name = "account_manager"
version = "0.0.1"
-authors = ["Luke Anderson "]
+authors = ["Paul Hauner ", "Luke Anderson "]
edition = "2018"
+[dev-dependencies]
+tempdir = "0.3"
+
[dependencies]
bls = { path = "../eth2/utils/bls" }
clap = "2.33.0"
slog = "2.5.2"
slog-term = "2.4.2"
slog-async = "2.3.0"
-validator_client = { path = "../validator_client" }
types = { path = "../eth2/types" }
dirs = "2.0.2"
environment = { path = "../lighthouse/environment" }
+deposit_contract = { path = "../eth2/utils/deposit_contract" }
+libc = "0.2.65"
+eth2_ssz = { path = "../eth2/utils/ssz" }
+eth2_ssz_derive = { path = "../eth2/utils/ssz_derive" }
+hex = "0.4"
+validator_client = { path = "../validator_client" }
+rayon = "1.2.0"
diff --git a/account_manager/src/cli.rs b/account_manager/src/cli.rs
index 9eded83cc5..01d6376cdb 100644
--- a/account_manager/src/cli.rs
+++ b/account_manager/src/cli.rs
@@ -1,54 +1,47 @@
use clap::{App, Arg, SubCommand};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
- App::new("Account Manager")
- .visible_aliases(&["am", "accounts", "accounts_manager"])
- .version("0.0.1")
- .author("Sigma Prime ")
- .about("Eth 2.0 Accounts Manager")
- .arg(
- Arg::with_name("logfile")
- .long("logfile")
- .value_name("logfile")
- .help("File path where output will be written.")
- .takes_value(true),
- )
- .arg(
- Arg::with_name("datadir")
- .long("datadir")
- .short("d")
- .value_name("DIR")
- .help("Data directory for keys and databases.")
- .takes_value(true),
- )
+ App::new("account_manager")
+ .visible_aliases(&["a", "am", "account", "account_manager"])
+ .about("Utilities for generating and managing Ethereum 2.0 accounts.")
.subcommand(
- SubCommand::with_name("generate")
- .about("Generates a new validator private key")
- .version("0.0.1")
- .author("Sigma Prime "),
- )
- .subcommand(
- SubCommand::with_name("generate_deterministic")
- .about("Generates a deterministic validator private key FOR TESTING")
- .version("0.0.1")
- .author("Sigma Prime ")
- .arg(
- Arg::with_name("validator index")
- .long("index")
- .short("i")
- .value_name("index")
- .help("The index of the validator, for which the test key is generated")
- .takes_value(true)
- .required(true),
+ SubCommand::with_name("validator")
+ .about("Generate or manage Etheruem 2.0 validators.")
+ .subcommand(
+ SubCommand::with_name("new")
+ .about("Create a new Ethereum 2.0 validator.")
+ .subcommand(
+ SubCommand::with_name("insecure")
+ .about("Produce insecure, ephemeral validators. DO NOT USE TO STORE VALUE.")
+ .arg(
+ Arg::with_name("first")
+ .index(1)
+ .value_name("INDEX")
+ .help("Index of the first validator")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("last")
+ .index(2)
+ .value_name("INDEX")
+ .help("Index of the first validator")
+ .takes_value(true)
+ .required(true),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("random")
+ .about("Produces public keys using entropy from the Rust 'rand' library.")
+ .arg(
+ Arg::with_name("validator_count")
+ .index(1)
+ .value_name("INTEGER")
+ .help("The number of new validators to generate.")
+ .takes_value(true)
+ .default_value("1"),
+ ),
+ )
)
- .arg(
- Arg::with_name("validator count")
- .long("validator_count")
- .short("n")
- .value_name("validator_count")
- .help("If supplied along with `index`, generates keys `i..i + n`.")
- .takes_value(true)
- .default_value("1"),
- ),
)
}
diff --git a/account_manager/src/lib.rs b/account_manager/src/lib.rs
index 90a80e6dd7..37d2d7692a 100644
--- a/account_manager/src/lib.rs
+++ b/account_manager/src/lib.rs
@@ -1,122 +1,150 @@
mod cli;
-use bls::Keypair;
use clap::ArgMatches;
use environment::RuntimeContext;
-use slog::{crit, debug, info};
+use rayon::prelude::*;
+use slog::{crit, info};
use std::fs;
use std::path::PathBuf;
-use types::{test_utils::generate_deterministic_keypair, EthSpec};
-use validator_client::Config as ValidatorClientConfig;
+use types::{ChainSpec, EthSpec};
+use validator_client::validator_directory::{ValidatorDirectory, ValidatorDirectoryBuilder};
pub use cli::cli_app;
-pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator";
-pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml";
-
+/// Run the account manager, logging an error if the operation did not succeed.
pub fn run(matches: &ArgMatches, context: RuntimeContext) {
- let mut log = context.log;
+ let log = context.log.clone();
+ match run_account_manager(matches, context) {
+ Ok(()) => (),
+ Err(e) => crit!(log, "Account manager failed"; "error" => e),
+ }
+}
- let data_dir = match matches
+/// Run the account manager, returning an error if the operation did not succeed.
+fn run_account_manager(
+ matches: &ArgMatches,
+ context: RuntimeContext,
+) -> Result<(), String> {
+ let log = context.log.clone();
+
+ let datadir = matches
.value_of("datadir")
- .and_then(|v| Some(PathBuf::from(v)))
- {
- Some(v) => v,
- None => {
- // use the default
+ .map(PathBuf::from)
+ .unwrap_or_else(|| {
let mut default_dir = match dirs::home_dir() {
Some(v) => v,
None => {
- crit!(log, "Failed to find a home directory");
- return;
+ panic!("Failed to find a home directory");
}
};
- default_dir.push(DEFAULT_DATA_DIR);
+ default_dir.push(".lighthouse");
+ default_dir.push("validators");
default_dir
- }
- };
+ });
- // create the directory if needed
- match fs::create_dir_all(&data_dir) {
- Ok(_) => {}
- Err(e) => {
- crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e));
- return;
- }
- }
+ fs::create_dir_all(&datadir).map_err(|e| format!("Failed to initialize datadir: {}", e))?;
- let mut client_config = ValidatorClientConfig::default();
-
- // Ensure the `data_dir` in the config matches that supplied to the CLI.
- client_config.data_dir = data_dir.clone();
-
- if let Err(e) = client_config.apply_cli_args(&matches, &mut log) {
- crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => format!("{:?}", e));
- return;
- };
-
- // Log configuration
- info!(log, "";
- "data_dir" => &client_config.data_dir.to_str());
+ info!(
+ log,
+ "Located data directory";
+ "path" => format!("{:?}", datadir)
+ );
match matches.subcommand() {
- ("generate", Some(_)) => generate_random(&client_config, &log),
- ("generate_deterministic", Some(m)) => {
- if let Some(string) = m.value_of("validator index") {
- let i: usize = string.parse().expect("Invalid validator index");
- if let Some(string) = m.value_of("validator count") {
- let n: usize = string.parse().expect("Invalid end validator count");
-
- let indices: Vec = (i..i + n).collect();
- generate_deterministic_multiple(&indices, &client_config, &log)
- } else {
- generate_deterministic(i, &client_config, &log)
- }
+ ("validator", Some(matches)) => match matches.subcommand() {
+ ("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, context)?,
+ _ => {
+ return Err("Invalid 'validator new' command. See --help.".to_string());
}
+ },
+ _ => {
+ return Err("Invalid 'validator' command. See --help.".to_string());
+ }
+ }
+
+ Ok(())
+}
+
+/// Describes the crypto key generation methods for a validator.
+enum KeygenMethod {
+ /// Produce an insecure "deterministic" keypair. Used only for interop and testing.
+ Insecure(usize),
+ /// Generate a new key from the `rand` thread random RNG.
+ ThreadRandom,
+}
+
+/// Process the subcommand for creating new validators.
+fn run_new_validator_subcommand(
+ matches: &ArgMatches,
+ datadir: PathBuf,
+ context: RuntimeContext,
+) -> Result<(), String> {
+ let log = context.log.clone();
+
+ let methods: Vec = match matches.subcommand() {
+ ("insecure", Some(matches)) => {
+ let first = matches
+ .value_of("first")
+ .ok_or_else(|| "No first index".to_string())?
+ .parse::()
+ .map_err(|e| format!("Unable to parse first index: {}", e))?;
+ let last = matches
+ .value_of("last")
+ .ok_or_else(|| "No last index".to_string())?
+ .parse::()
+ .map_err(|e| format!("Unable to parse first index: {}", e))?;
+
+ (first..last).map(KeygenMethod::Insecure).collect()
+ }
+ ("random", Some(matches)) => {
+ let count = matches
+ .value_of("validator_count")
+ .ok_or_else(|| "No validator count".to_string())?
+ .parse::()
+ .map_err(|e| format!("Unable to parse validator count: {}", e))?;
+
+ (0..count).map(|_| KeygenMethod::ThreadRandom).collect()
}
_ => {
- crit!(
- log,
- "The account manager must be run with a subcommand. See help for more information."
- );
+ return Err("Invalid 'validator' command. See --help.".to_string());
}
- }
-}
+ };
-fn generate_random(config: &ValidatorClientConfig, log: &slog::Logger) {
- save_key(&Keypair::random(), config, log)
-}
+ let validators = make_validators(datadir.clone(), &methods, context.eth2_config.spec)?;
-fn generate_deterministic_multiple(
- validator_indices: &[usize],
- config: &ValidatorClientConfig,
- log: &slog::Logger,
-) {
- for validator_index in validator_indices {
- generate_deterministic(*validator_index, config, log)
- }
-}
-
-fn generate_deterministic(
- validator_index: usize,
- config: &ValidatorClientConfig,
- log: &slog::Logger,
-) {
- save_key(
- &generate_deterministic_keypair(validator_index),
- config,
+ info!(
log,
- )
-}
-
-fn save_key(keypair: &Keypair, config: &ValidatorClientConfig, log: &slog::Logger) {
- let key_path: PathBuf = config
- .save_key(&keypair)
- .expect("Unable to save newly generated private key.");
- debug!(
- log,
- "Keypair generated {:?}, saved to: {:?}",
- keypair.identifier(),
- key_path.to_string_lossy()
+ "Generated validator directories";
+ "base_path" => format!("{:?}", datadir),
+ "count" => validators.len(),
);
+
+ Ok(())
+}
+
+/// Produces a validator directory for each of the key generation methods provided in `methods`.
+fn make_validators(
+ datadir: PathBuf,
+ methods: &[KeygenMethod],
+ spec: ChainSpec,
+) -> Result, String> {
+ methods
+ .par_iter()
+ .map(|method| {
+ let mut builder = ValidatorDirectoryBuilder::default()
+ .spec(spec.clone())
+ .full_deposit_amount()?;
+
+ builder = match method {
+ KeygenMethod::Insecure(index) => builder.insecure_keypairs(*index),
+ KeygenMethod::ThreadRandom => builder.thread_random_keypairs(),
+ };
+
+ builder
+ .create_directory(datadir.clone())?
+ .write_keypair_files()?
+ .write_eth1_data_file()?
+ .build()
+ })
+ .collect()
}
diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml
index cf368be374..0abe4596be 100644
--- a/beacon_node/beacon_chain/Cargo.toml
+++ b/beacon_node/beacon_chain/Cargo.toml
@@ -28,6 +28,7 @@ sloggers = "0.3.4"
slot_clock = { path = "../../eth2/utils/slot_clock" }
eth2_hashing = "0.1.0"
eth2_ssz = "0.1.2"
+eth2_ssz_types = { path = "../../eth2/utils/ssz_types" }
eth2_ssz_derive = "0.1.0"
state_processing = { path = "../../eth2/state_processing" }
tree_hash = "0.1.0"
diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs
index ad05b155c4..a35aa85dec 100644
--- a/beacon_node/beacon_chain/src/beacon_chain.rs
+++ b/beacon_node/beacon_chain/src/beacon_chain.rs
@@ -10,7 +10,7 @@ use lmd_ghost::LmdGhost;
use operation_pool::DepositInsertStatus;
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::RwLock;
-use slog::{debug, error, info, trace, warn, Logger};
+use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock;
use ssz::Encode;
use state_processing::per_block_processing::{
@@ -44,6 +44,7 @@ pub const GRAFFITI: &str = "sigp/lighthouse-0.0.0-prerelease";
const WRITE_BLOCK_PROCESSING_SSZ: bool = cfg!(feature = "write_ssz_files");
const BLOCK_SKIPPING_LOGGING_THRESHOLD: u64 = 3;
+const BLOCK_SKIPPING_FAILURE_THRESHOLD: u64 = 128;
#[derive(Debug, PartialEq)]
pub enum BlockProcessingOutcome {
@@ -74,6 +75,7 @@ pub enum BlockProcessingOutcome {
#[derive(Debug, PartialEq)]
pub enum AttestationProcessingOutcome {
Processed,
+ EmptyAggregationBitfield,
UnknownHeadBlock {
beacon_block_root: Hash256,
},
@@ -175,7 +177,7 @@ impl BeaconChain {
) -> Result>, Error> {
let bodies: Result, _> = roots
.iter()
- .map(|root| match self.get_block(root)? {
+ .map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.body),
None => Err(Error::DBInconsistent(format!("Missing block: {}", root))),
})
@@ -190,7 +192,7 @@ impl BeaconChain {
pub fn get_block_headers(&self, roots: &[Hash256]) -> Result, Error> {
let headers: Result, _> = roots
.iter()
- .map(|root| match self.get_block(root)? {
+ .map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.block_header()),
None => Err(Error::DBInconsistent("Missing block".into())),
})
@@ -274,6 +276,36 @@ impl BeaconChain {
ReverseStateRootIterator::new((head.beacon_state_root, slot), iter)
}
+ /// Returns the block at the given root, if any.
+ ///
+ /// ## Errors
+ ///
+ /// May return a database error.
+ pub fn block_at_root(
+ &self,
+ block_root: Hash256,
+ ) -> Result