From 5de00b7ee821dc28f80285acbce083edda4f14a1 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Wed, 29 Jun 2022 09:07:09 +0000 Subject: [PATCH] Unify execution layer endpoints (#3214) ## Issue Addressed Resolves #3069 ## Proposed Changes Unify the `eth1-endpoints` and `execution-endpoints` flags in a backwards compatible way as described in https://github.com/sigp/lighthouse/issues/3069#issuecomment-1134219221 Users have 2 options: 1. Use multiple non auth execution endpoints for deposit processing pre-merge 2. Use a single jwt authenticated execution endpoint for both execution layer and deposit processing post merge Related https://github.com/sigp/lighthouse/issues/3118 To enable jwt authenticated deposit processing, this PR removes the calls to `net_version` as the `net` namespace is not exposed in the auth server in execution clients. Moving away from using `networkId` is a good step in my opinion as it doesn't provide us with any added guarantees over `chainId`. See https://github.com/ethereum/consensus-specs/issues/2163 and https://github.com/sigp/lighthouse/issues/2115 Co-authored-by: Paul Hauner --- Cargo.lock | 11 +- beacon_node/client/Cargo.toml | 2 +- beacon_node/client/src/config.rs | 5 +- beacon_node/eth1/Cargo.toml | 3 +- beacon_node/eth1/src/deposit_cache.rs | 31 +- beacon_node/eth1/src/deposit_log.rs | 107 ---- beacon_node/eth1/src/http.rs | 489 ------------------ beacon_node/eth1/src/lib.rs | 8 +- beacon_node/eth1/src/service.rs | 186 ++++--- beacon_node/eth1/tests/test.rs | 246 ++++----- beacon_node/execution_layer/Cargo.toml | 4 +- beacon_node/execution_layer/src/engine_api.rs | 2 +- .../execution_layer/src/engine_api/auth.rs | 33 ++ .../execution_layer/src/engine_api/http.rs | 484 ++++++++++++++++- beacon_node/execution_layer/src/lib.rs | 15 +- .../genesis/src/eth1_genesis_service.rs | 2 +- beacon_node/genesis/src/lib.rs | 1 + beacon_node/genesis/tests/tests.rs | 9 +- beacon_node/src/cli.rs | 66 +-- beacon_node/src/config.rs | 145 ++++-- lcli/src/eth1_genesis.rs | 5 +- lighthouse/Cargo.toml | 1 + lighthouse/tests/beacon_node.rs | 191 +++++-- scripts/local_testnet/ganache_test_node.sh | 3 +- scripts/local_testnet/setup.sh | 2 +- scripts/local_testnet/vars.env | 2 +- scripts/tests/vars.env | 2 +- testing/eth1_test_rig/src/ganache.rs | 22 +- testing/eth1_test_rig/src/lib.rs | 4 +- testing/simulator/Cargo.toml | 1 + testing/simulator/src/eth1_sim.rs | 23 +- 31 files changed, 1113 insertions(+), 992 deletions(-) delete mode 100644 beacon_node/eth1/src/deposit_log.rs delete mode 100644 beacon_node/eth1/src/http.rs diff --git a/Cargo.lock b/Cargo.lock index d461027e77..3dbe005658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,6 +744,7 @@ dependencies = [ "sensitive_url", "serde", "serde_derive", + "serde_yaml", "slasher", "slasher_service", "slog", @@ -753,7 +754,6 @@ dependencies = [ "time 0.3.9", "timer", "tokio", - "toml", "types", ] @@ -1530,6 +1530,7 @@ dependencies = [ "eth2", "eth2_ssz", "eth2_ssz_derive", + "execution_layer", "fallback", "futures", "hex", @@ -1541,12 +1542,12 @@ dependencies = [ "sensitive_url", "serde", "serde_json", + "serde_yaml", "slog", "sloggers", "state_processing", "task_executor", "tokio", - "toml", "tree_hash", "types", "web3", @@ -1877,8 +1878,9 @@ dependencies = [ "async-trait", "bytes", "environment", - "eth1", + "eth2", "eth2_serde_utils", + "eth2_ssz", "eth2_ssz_types", "ethers-core", "exit-future", @@ -1896,6 +1898,7 @@ dependencies = [ "serde_json", "slog", "slot_clock", + "state_processing", "task_executor", "tempfile", "tokio", @@ -3397,6 +3400,7 @@ dependencies = [ "directory", "env_logger 0.9.0", "environment", + "eth1", "eth2_hashing", "eth2_network_config", "futures", @@ -5636,6 +5640,7 @@ dependencies = [ "env_logger 0.9.0", "eth1", "eth1_test_rig", + "execution_layer", "futures", "node_test_rig", "parking_lot 0.12.1", diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 3079d7744e..d01f2505cc 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Sigma Prime "] edition = "2021" [dev-dependencies] -toml = "0.5.6" +serde_yaml = "0.8.13" [dependencies] beacon_chain = { path = "../beacon_chain" } diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 13614af12e..b13ca8f489 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -198,7 +198,8 @@ mod tests { #[test] fn serde() { let config = Config::default(); - let serialized = toml::to_string(&config).expect("should serde encode default config"); - toml::from_str::(&serialized).expect("should serde decode default config"); + let serialized = + serde_yaml::to_string(&config).expect("should serde encode default config"); + serde_yaml::from_str::(&serialized).expect("should serde decode default config"); } } diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index ecf3c19e30..403869cc9c 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -6,13 +6,14 @@ edition = "2021" [dev-dependencies] eth1_test_rig = { path = "../../testing/eth1_test_rig" } -toml = "0.5.6" +serde_yaml = "0.8.13" web3 = { version = "0.18.0", default-features = false, features = ["http-tls", "signing", "ws-tls-tokio"] } sloggers = { version = "2.1.1", features = ["json"] } environment = { path = "../../lighthouse/environment" } [dependencies] reqwest = { version = "0.11.0", features = ["native-tls-vendored"] } +execution_layer = { path = "../execution_layer" } futures = "0.3.7" serde_json = "1.0.58" serde = { version = "1.0.116", features = ["derive"] } diff --git a/beacon_node/eth1/src/deposit_cache.rs b/beacon_node/eth1/src/deposit_cache.rs index 7c67893fb3..078e3602f5 100644 --- a/beacon_node/eth1/src/deposit_cache.rs +++ b/beacon_node/eth1/src/deposit_cache.rs @@ -1,4 +1,4 @@ -use crate::DepositLog; +use execution_layer::http::deposit_log::DepositLog; use ssz_derive::{Decode, Encode}; use state_processing::common::DepositDataTree; use std::cmp::Ordering; @@ -297,12 +297,37 @@ impl DepositCache { #[cfg(test)] pub mod tests { use super::*; - use crate::deposit_log::tests::EXAMPLE_LOG; - use crate::http::Log; + use execution_layer::http::deposit_log::Log; use types::{EthSpec, MainnetEthSpec}; pub const TREE_DEPTH: usize = 32; + /// The data from a deposit event, using the v0.8.3 version of the deposit contract. + pub const EXAMPLE_LOG: &[u8] = &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 167, 108, 6, 69, 88, 17, 3, 51, 6, 4, 158, 232, 82, + 248, 218, 2, 71, 219, 55, 102, 86, 125, 136, 203, 36, 77, 64, 213, 43, 52, 175, 154, 239, + 50, 142, 52, 201, 77, 54, 239, 0, 229, 22, 46, 139, 120, 62, 240, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 64, 89, 115, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 140, 74, 175, 158, 209, 20, 206, + 30, 63, 215, 238, 113, 60, 132, 216, 211, 100, 186, 202, 71, 34, 200, 160, 225, 212, 213, + 119, 88, 51, 80, 101, 74, 2, 45, 78, 153, 12, 192, 44, 51, 77, 40, 10, 72, 246, 34, 193, + 187, 22, 95, 4, 211, 245, 224, 13, 162, 21, 163, 54, 225, 22, 124, 3, 56, 14, 81, 122, 189, + 149, 250, 251, 159, 22, 77, 94, 157, 197, 196, 253, 110, 201, 88, 193, 246, 136, 226, 221, + 18, 113, 232, 105, 100, 114, 103, 237, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + fn example_log() -> DepositLog { let spec = MainnetEthSpec::default_spec(); diff --git a/beacon_node/eth1/src/deposit_log.rs b/beacon_node/eth1/src/deposit_log.rs deleted file mode 100644 index 1b3cfa01a0..0000000000 --- a/beacon_node/eth1/src/deposit_log.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::http::Log; -use ssz::Decode; -use state_processing::per_block_processing::signature_sets::deposit_pubkey_signature_message; -use types::{ChainSpec, DepositData, Hash256, PublicKeyBytes, SignatureBytes}; - -pub use eth2::lighthouse::DepositLog; - -/// The following constants define the layout of bytes in the deposit contract `DepositEvent`. The -/// event bytes are formatted according to the Ethereum ABI. -const PUBKEY_START: usize = 192; -const PUBKEY_LEN: usize = 48; -const CREDS_START: usize = PUBKEY_START + 64 + 32; -const CREDS_LEN: usize = 32; -const AMOUNT_START: usize = CREDS_START + 32 + 32; -const AMOUNT_LEN: usize = 8; -const SIG_START: usize = AMOUNT_START + 32 + 32; -const SIG_LEN: usize = 96; -const INDEX_START: usize = SIG_START + 96 + 32; -const INDEX_LEN: usize = 8; - -impl Log { - /// Attempts to parse a raw `Log` from the deposit contract into a `DepositLog`. - pub fn to_deposit_log(&self, spec: &ChainSpec) -> Result { - let bytes = &self.data; - - let pubkey = bytes - .get(PUBKEY_START..PUBKEY_START + PUBKEY_LEN) - .ok_or("Insufficient bytes for pubkey")?; - let withdrawal_credentials = bytes - .get(CREDS_START..CREDS_START + CREDS_LEN) - .ok_or("Insufficient bytes for withdrawal credential")?; - let amount = bytes - .get(AMOUNT_START..AMOUNT_START + AMOUNT_LEN) - .ok_or("Insufficient bytes for amount")?; - let signature = bytes - .get(SIG_START..SIG_START + SIG_LEN) - .ok_or("Insufficient bytes for signature")?; - let index = bytes - .get(INDEX_START..INDEX_START + INDEX_LEN) - .ok_or("Insufficient bytes for index")?; - - let deposit_data = DepositData { - pubkey: PublicKeyBytes::from_ssz_bytes(pubkey) - .map_err(|e| format!("Invalid pubkey ssz: {:?}", e))?, - withdrawal_credentials: Hash256::from_ssz_bytes(withdrawal_credentials) - .map_err(|e| format!("Invalid withdrawal_credentials ssz: {:?}", e))?, - amount: u64::from_ssz_bytes(amount) - .map_err(|e| format!("Invalid amount ssz: {:?}", e))?, - signature: SignatureBytes::from_ssz_bytes(signature) - .map_err(|e| format!("Invalid signature ssz: {:?}", e))?, - }; - - let signature_is_valid = deposit_pubkey_signature_message(&deposit_data, spec) - .map_or(false, |(public_key, signature, msg)| { - signature.verify(&public_key, msg) - }); - - Ok(DepositLog { - deposit_data, - block_number: self.block_number, - index: u64::from_ssz_bytes(index).map_err(|e| format!("Invalid index ssz: {:?}", e))?, - signature_is_valid, - }) - } -} - -#[cfg(test)] -pub mod tests { - use crate::http::Log; - use types::{EthSpec, MainnetEthSpec}; - - /// The data from a deposit event, using the v0.8.3 version of the deposit contract. - pub const EXAMPLE_LOG: &[u8] = &[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 167, 108, 6, 69, 88, 17, 3, 51, 6, 4, 158, 232, 82, - 248, 218, 2, 71, 219, 55, 102, 86, 125, 136, 203, 36, 77, 64, 213, 43, 52, 175, 154, 239, - 50, 142, 52, 201, 77, 54, 239, 0, 229, 22, 46, 139, 120, 62, 240, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 64, 89, 115, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 140, 74, 175, 158, 209, 20, 206, - 30, 63, 215, 238, 113, 60, 132, 216, 211, 100, 186, 202, 71, 34, 200, 160, 225, 212, 213, - 119, 88, 51, 80, 101, 74, 2, 45, 78, 153, 12, 192, 44, 51, 77, 40, 10, 72, 246, 34, 193, - 187, 22, 95, 4, 211, 245, 224, 13, 162, 21, 163, 54, 225, 22, 124, 3, 56, 14, 81, 122, 189, - 149, 250, 251, 159, 22, 77, 94, 157, 197, 196, 253, 110, 201, 88, 193, 246, 136, 226, 221, - 18, 113, 232, 105, 100, 114, 103, 237, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - - #[test] - fn can_parse_example_log() { - let log = Log { - block_number: 42, - data: EXAMPLE_LOG.to_vec(), - }; - log.to_deposit_log(&MainnetEthSpec::default_spec()) - .expect("should decode log"); - } -} diff --git a/beacon_node/eth1/src/http.rs b/beacon_node/eth1/src/http.rs deleted file mode 100644 index 71b1b5b4b2..0000000000 --- a/beacon_node/eth1/src/http.rs +++ /dev/null @@ -1,489 +0,0 @@ -//! Provides a very minimal set of functions for interfacing with the eth2 deposit contract via an -//! eth1 HTTP JSON-RPC endpoint. -//! -//! All remote functions return a future (i.e., are async). -//! -//! Does not use a web3 library, instead it uses `reqwest` (`hyper`) to call the remote endpoint -//! and `serde` to decode the response. -//! -//! ## Note -//! -//! There is no ABI parsing here, all function signatures and topics are hard-coded as constants. - -use futures::future::TryFutureExt; -use reqwest::{header::CONTENT_TYPE, ClientBuilder, StatusCode}; -use sensitive_url::SensitiveUrl; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; -use std::fmt; -use std::ops::Range; -use std::str::FromStr; -use std::time::Duration; -use types::Hash256; - -/// `keccak("DepositEvent(bytes,bytes,bytes,bytes,bytes)")` -pub const DEPOSIT_EVENT_TOPIC: &str = - "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"; -/// `keccak("get_deposit_root()")[0..4]` -pub const DEPOSIT_ROOT_FN_SIGNATURE: &str = "0xc5f2892f"; -/// `keccak("get_deposit_count()")[0..4]` -pub const DEPOSIT_COUNT_FN_SIGNATURE: &str = "0x621fd130"; - -/// Number of bytes in deposit contract deposit root response. -pub const DEPOSIT_COUNT_RESPONSE_BYTES: usize = 96; -/// Number of bytes in deposit contract deposit root (value only). -pub const DEPOSIT_ROOT_BYTES: usize = 32; - -/// This error is returned during a `chainId` call by Geth. -pub const EIP155_ERROR_STR: &str = "chain not synced beyond EIP-155 replay-protection fork block"; - -/// Represents an eth1 chain/network id. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub enum Eth1Id { - Goerli, - Mainnet, - Custom(u64), -} - -/// Used to identify a block when querying the Eth1 node. -#[derive(Clone, Copy)] -pub enum BlockQuery { - Number(u64), - Latest, -} - -/// Represents an error received from a remote procecdure call. -#[derive(Debug, Serialize, Deserialize)] -pub enum RpcError { - NoResultField, - Eip155Error, - InvalidJson(String), - Error(String), -} - -impl fmt::Display for RpcError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RpcError::NoResultField => write!(f, "No result field in response"), - RpcError::Eip155Error => write!(f, "Not synced past EIP-155"), - RpcError::InvalidJson(e) => write!(f, "Malformed JSON received: {}", e), - RpcError::Error(s) => write!(f, "{}", s), - } - } -} - -impl From for String { - fn from(e: RpcError) -> String { - e.to_string() - } -} - -impl Into for Eth1Id { - fn into(self) -> u64 { - match self { - Eth1Id::Mainnet => 1, - Eth1Id::Goerli => 5, - Eth1Id::Custom(id) => id, - } - } -} - -impl From for Eth1Id { - fn from(id: u64) -> Self { - let into = |x: Eth1Id| -> u64 { x.into() }; - match id { - id if id == into(Eth1Id::Mainnet) => Eth1Id::Mainnet, - id if id == into(Eth1Id::Goerli) => Eth1Id::Goerli, - id => Eth1Id::Custom(id), - } - } -} - -impl FromStr for Eth1Id { - type Err = String; - - fn from_str(s: &str) -> Result { - s.parse::() - .map(Into::into) - .map_err(|e| format!("Failed to parse eth1 network id {}", e)) - } -} - -/// Get the eth1 network id of the given endpoint. -pub async fn get_network_id(endpoint: &SensitiveUrl, timeout: Duration) -> Result { - let response_body = send_rpc_request(endpoint, "net_version", json!([]), timeout).await?; - Eth1Id::from_str( - response_result_or_error(&response_body)? - .as_str() - .ok_or("Data was not string")?, - ) -} - -/// Get the eth1 chain id of the given endpoint. -pub async fn get_chain_id(endpoint: &SensitiveUrl, timeout: Duration) -> Result { - let response_body: String = - send_rpc_request(endpoint, "eth_chainId", json!([]), timeout).await?; - - match response_result_or_error(&response_body) { - Ok(chain_id) => { - hex_to_u64_be(chain_id.as_str().ok_or("Data was not string")?).map(|id| id.into()) - } - // Geth returns this error when it's syncing lower blocks. Simply map this into `0` since - // Lighthouse does not raise errors for `0`, it simply waits for it to change. - Err(RpcError::Eip155Error) => Ok(Eth1Id::Custom(0)), - Err(e) => Err(e.to_string()), - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Block { - pub hash: Hash256, - pub timestamp: u64, - pub number: u64, -} - -/// Returns the current block number. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -pub async fn get_block_number(endpoint: &SensitiveUrl, timeout: Duration) -> Result { - let response_body = send_rpc_request(endpoint, "eth_blockNumber", json!([]), timeout).await?; - hex_to_u64_be( - response_result_or_error(&response_body) - .map_err(|e| format!("eth_blockNumber failed: {}", e))? - .as_str() - .ok_or("Data was not string")?, - ) - .map_err(|e| format!("Failed to get block number: {}", e)) -} - -/// Gets a block hash by block number. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -pub async fn get_block( - endpoint: &SensitiveUrl, - query: BlockQuery, - timeout: Duration, -) -> Result { - let query_param = match query { - BlockQuery::Number(block_number) => format!("0x{:x}", block_number), - BlockQuery::Latest => "latest".to_string(), - }; - let params = json!([ - query_param, - false // do not return full tx objects. - ]); - - let response_body = send_rpc_request(endpoint, "eth_getBlockByNumber", params, timeout).await?; - let response = response_result_or_error(&response_body) - .map_err(|e| format!("eth_getBlockByNumber failed: {}", e))?; - - let hash: Vec = hex_to_bytes( - response - .get("hash") - .ok_or("No hash for block")? - .as_str() - .ok_or("Block hash was not string")?, - )?; - let hash: Hash256 = if hash.len() == 32 { - Hash256::from_slice(&hash) - } else { - return Err(format!("Block has was not 32 bytes: {:?}", hash)); - }; - - let timestamp = hex_to_u64_be( - response - .get("timestamp") - .ok_or("No timestamp for block")? - .as_str() - .ok_or("Block timestamp was not string")?, - )?; - - let number = hex_to_u64_be( - response - .get("number") - .ok_or("No number for block")? - .as_str() - .ok_or("Block number was not string")?, - )?; - - if number <= usize::max_value() as u64 { - Ok(Block { - hash, - timestamp, - number, - }) - } else { - Err(format!("Block number {} is larger than a usize", number)) - } - .map_err(|e| format!("Failed to get block number: {}", e)) -} - -/// Returns the value of the `get_deposit_count()` call at the given `address` for the given -/// `block_number`. -/// -/// Assumes that the `address` has the same ABI as the eth2 deposit contract. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -pub async fn get_deposit_count( - endpoint: &SensitiveUrl, - address: &str, - block_number: u64, - timeout: Duration, -) -> Result, String> { - let result = call( - endpoint, - address, - DEPOSIT_COUNT_FN_SIGNATURE, - block_number, - timeout, - ) - .await?; - match result { - None => Err("Deposit root response was none".to_string()), - Some(bytes) => { - if bytes.is_empty() { - Ok(None) - } else if bytes.len() == DEPOSIT_COUNT_RESPONSE_BYTES { - let mut array = [0; 8]; - array.copy_from_slice(&bytes[32 + 32..32 + 32 + 8]); - Ok(Some(u64::from_le_bytes(array))) - } else { - Err(format!( - "Deposit count response was not {} bytes: {:?}", - DEPOSIT_COUNT_RESPONSE_BYTES, bytes - )) - } - } - } -} - -/// Returns the value of the `get_hash_tree_root()` call at the given `block_number`. -/// -/// Assumes that the `address` has the same ABI as the eth2 deposit contract. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -pub async fn get_deposit_root( - endpoint: &SensitiveUrl, - address: &str, - block_number: u64, - timeout: Duration, -) -> Result, String> { - let result = call( - endpoint, - address, - DEPOSIT_ROOT_FN_SIGNATURE, - block_number, - timeout, - ) - .await?; - match result { - None => Err("Deposit root response was none".to_string()), - Some(bytes) => { - if bytes.is_empty() { - Ok(None) - } else if bytes.len() == DEPOSIT_ROOT_BYTES { - Ok(Some(Hash256::from_slice(&bytes))) - } else { - Err(format!( - "Deposit root response was not {} bytes: {:?}", - DEPOSIT_ROOT_BYTES, bytes - )) - } - } - } -} - -/// Performs a instant, no-transaction call to the contract `address` with the given `0x`-prefixed -/// `hex_data`. -/// -/// Returns bytes, if any. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -async fn call( - endpoint: &SensitiveUrl, - address: &str, - hex_data: &str, - block_number: u64, - timeout: Duration, -) -> Result>, String> { - let params = json! ([ - { - "to": address, - "data": hex_data, - }, - format!("0x{:x}", block_number) - ]); - - let response_body = send_rpc_request(endpoint, "eth_call", params, timeout).await?; - - match response_result_or_error(&response_body) { - Ok(result) => { - let hex = result - .as_str() - .map(|s| s.to_string()) - .ok_or("'result' value was not a string")?; - - Ok(Some(hex_to_bytes(&hex)?)) - } - // It's valid for `eth_call` to return without a result. - Err(RpcError::NoResultField) => Ok(None), - Err(e) => Err(format!("eth_call failed: {}", e)), - } -} - -/// A reduced set of fields from an Eth1 contract log. -#[derive(Debug, PartialEq, Clone)] -pub struct Log { - pub(crate) block_number: u64, - pub(crate) data: Vec, -} - -/// Returns logs for the `DEPOSIT_EVENT_TOPIC`, for the given `address` in the given -/// `block_height_range`. -/// -/// It's not clear from the Ethereum JSON-RPC docs if this range is inclusive or not. -/// -/// Uses HTTP JSON RPC at `endpoint`. E.g., `http://localhost:8545`. -pub async fn get_deposit_logs_in_range( - endpoint: &SensitiveUrl, - address: &str, - block_height_range: Range, - timeout: Duration, -) -> Result, String> { - let params = json! ([{ - "address": address, - "topics": [DEPOSIT_EVENT_TOPIC], - "fromBlock": format!("0x{:x}", block_height_range.start), - "toBlock": format!("0x{:x}", block_height_range.end), - }]); - - let response_body = send_rpc_request(endpoint, "eth_getLogs", params, timeout).await?; - response_result_or_error(&response_body) - .map_err(|e| format!("eth_getLogs failed: {}", e))? - .as_array() - .cloned() - .ok_or("'result' value was not an array")? - .into_iter() - .map(|value| { - let block_number = value - .get("blockNumber") - .ok_or("No block number field in log")? - .as_str() - .ok_or("Block number was not string")?; - - let data = value - .get("data") - .ok_or("No block number field in log")? - .as_str() - .ok_or("Data was not string")?; - - Ok(Log { - block_number: hex_to_u64_be(block_number)?, - data: hex_to_bytes(data)?, - }) - }) - .collect::, String>>() - .map_err(|e| format!("Failed to get logs in range: {}", e)) -} - -/// Sends an RPC request to `endpoint`, using a POST with the given `body`. -/// -/// Tries to receive the response and parse the body as a `String`. -pub async fn send_rpc_request( - endpoint: &SensitiveUrl, - method: &str, - params: Value, - timeout: Duration, -) -> Result { - let body = json! ({ - "jsonrpc": "2.0", - "method": method, - "params": params, - "id": 1 - }) - .to_string(); - - // Note: it is not ideal to create a new client for each request. - // - // A better solution would be to create some struct that contains a built client and pass it - // around (similar to the `web3` crate's `Transport` structs). - let response = ClientBuilder::new() - .timeout(timeout) - .build() - .expect("The builder should always build a client") - .post(endpoint.full.clone()) - .header(CONTENT_TYPE, "application/json") - .body(body) - .send() - .map_err(|e| format!("Request failed: {:?}", e)) - .await?; - if response.status() != StatusCode::OK { - return Err(format!( - "Response HTTP status was not 200 OK: {}.", - response.status() - )); - }; - let encoding = response - .headers() - .get(CONTENT_TYPE) - .ok_or("No content-type header in response")? - .to_str() - .map(|s| s.to_string()) - .map_err(|e| format!("Failed to parse content-type header: {}", e))?; - - response - .bytes() - .map_err(|e| format!("Failed to receive body: {:?}", e)) - .await - .and_then(move |bytes| match encoding.as_str() { - "application/json" => Ok(bytes), - "application/json; charset=utf-8" => Ok(bytes), - other => Err(format!("Unsupported encoding: {}", other)), - }) - .map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) - .map_err(|e| format!("Failed to receive body: {:?}", e)) -} - -/// Accepts an entire HTTP body (as a string) and returns either the `result` field or the `error['message']` field, as a serde `Value`. -fn response_result_or_error(response: &str) -> Result { - let json = serde_json::from_str::(response) - .map_err(|e| RpcError::InvalidJson(e.to_string()))?; - - if let Some(error) = json.get("error").and_then(|e| e.get("message")) { - let error = error.to_string(); - if error.contains(EIP155_ERROR_STR) { - Err(RpcError::Eip155Error) - } else { - Err(RpcError::Error(error)) - } - } else { - json.get("result").cloned().ok_or(RpcError::NoResultField) - } -} - -/// Parses a `0x`-prefixed, **big-endian** hex string as a u64. -/// -/// Note: the JSON-RPC encodes integers as big-endian. The deposit contract uses little-endian. -/// Therefore, this function is only useful for numbers encoded by the JSON RPC. -/// -/// E.g., `0x01 == 1` -fn hex_to_u64_be(hex: &str) -> Result { - u64::from_str_radix(strip_prefix(hex)?, 16) - .map_err(|e| format!("Failed to parse hex as u64: {:?}", e)) -} - -/// Parses a `0x`-prefixed, big-endian hex string as bytes. -/// -/// E.g., `0x0102 == vec![1, 2]` -fn hex_to_bytes(hex: &str) -> Result, String> { - hex::decode(strip_prefix(hex)?).map_err(|e| format!("Failed to parse hex as bytes: {:?}", e)) -} - -/// Removes the `0x` prefix from some bytes. Returns an error if the prefix is not present. -fn strip_prefix(hex: &str) -> Result<&str, String> { - if let Some(stripped) = hex.strip_prefix("0x") { - Ok(stripped) - } else { - Err("Hex string did not start with `0x`".to_string()) - } -} diff --git a/beacon_node/eth1/src/lib.rs b/beacon_node/eth1/src/lib.rs index cf724201a4..f99d085250 100644 --- a/beacon_node/eth1/src/lib.rs +++ b/beacon_node/eth1/src/lib.rs @@ -3,17 +3,15 @@ extern crate lazy_static; mod block_cache; mod deposit_cache; -mod deposit_log; -pub mod http; mod inner; mod metrics; mod service; pub use block_cache::{BlockCache, Eth1Block}; pub use deposit_cache::DepositCache; -pub use deposit_log::DepositLog; +pub use execution_layer::http::deposit_log::DepositLog; pub use inner::SszEth1Cache; pub use service::{ - BlockCacheUpdateOutcome, Config, DepositCacheUpdateOutcome, Error, Service, DEFAULT_CHAIN_ID, - DEFAULT_NETWORK_ID, + BlockCacheUpdateOutcome, Config, DepositCacheUpdateOutcome, Error, Eth1Endpoint, Service, + DEFAULT_CHAIN_ID, }; diff --git a/beacon_node/eth1/src/service.rs b/beacon_node/eth1/src/service.rs index 15e2123e8a..36a637d2ae 100644 --- a/beacon_node/eth1/src/service.rs +++ b/beacon_node/eth1/src/service.rs @@ -2,12 +2,13 @@ use crate::metrics; use crate::{ block_cache::{BlockCache, Error as BlockCacheError, Eth1Block}, deposit_cache::{DepositCacheInsertOutcome, Error as DepositCacheError}, - http::{ - get_block, get_block_number, get_chain_id, get_deposit_logs_in_range, get_network_id, - BlockQuery, Eth1Id, - }, inner::{DepositUpdater, Inner}, }; +use execution_layer::auth::Auth; +use execution_layer::http::{ + deposit_methods::{BlockQuery, Eth1Id}, + HttpJsonRpc, +}; use fallback::{Fallback, FallbackError}; use futures::future::TryFutureExt; use parking_lot::{RwLock, RwLockReadGuard}; @@ -17,14 +18,13 @@ use slog::{crit, debug, error, info, trace, warn, Logger}; use std::fmt::Debug; use std::future::Future; use std::ops::{Range, RangeInclusive}; +use std::path::PathBuf; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::RwLock as TRwLock; use tokio::time::{interval_at, Duration, Instant}; use types::{ChainSpec, EthSpec, Unsigned}; -/// Indicates the default eth1 network id we use for the deposit contract. -pub const DEFAULT_NETWORK_ID: Eth1Id = Eth1Id::Goerli; /// Indicates the default eth1 chain id we use for the deposit contract. pub const DEFAULT_CHAIN_ID: Eth1Id = Eth1Id::Goerli; /// Indicates the default eth1 endpoint. @@ -63,14 +63,14 @@ pub enum EndpointError { type EndpointState = Result<(), EndpointError>; pub struct EndpointWithState { - endpoint: SensitiveUrl, + client: HttpJsonRpc, state: TRwLock>, } impl EndpointWithState { - pub fn new(endpoint: SensitiveUrl) -> Self { + pub fn new(client: HttpJsonRpc) -> Self { Self { - endpoint, + client, state: TRwLock::new(None), } } @@ -89,7 +89,6 @@ async fn get_state(endpoint: &EndpointWithState) -> Option { /// is not usable. pub struct EndpointsCache { pub fallback: Fallback, - pub config_network_id: Eth1Id, pub config_chain_id: Eth1Id, pub log: Logger, } @@ -107,20 +106,14 @@ impl EndpointsCache { } crate::metrics::inc_counter_vec( &crate::metrics::ENDPOINT_REQUESTS, - &[&endpoint.endpoint.to_string()], + &[&endpoint.client.to_string()], ); - let state = endpoint_state( - &endpoint.endpoint, - &self.config_network_id, - &self.config_chain_id, - &self.log, - ) - .await; + let state = endpoint_state(&endpoint.client, &self.config_chain_id, &self.log).await; *value = Some(state.clone()); if state.is_err() { crate::metrics::inc_counter_vec( &crate::metrics::ENDPOINT_ERRORS, - &[&endpoint.endpoint.to_string()], + &[&endpoint.client.to_string()], ); crate::metrics::set_gauge(&metrics::ETH1_CONNECTED, 0); } else { @@ -136,7 +129,7 @@ impl EndpointsCache { func: F, ) -> Result<(O, usize), FallbackError> where - F: Fn(&'a SensitiveUrl) -> R, + F: Fn(&'a HttpJsonRpc) -> R, R: Future>, { let func = &func; @@ -144,12 +137,12 @@ impl EndpointsCache { .first_success(|endpoint| async move { match self.state(endpoint).await { Ok(()) => { - let endpoint_str = &endpoint.endpoint.to_string(); + let endpoint_str = &endpoint.client.to_string(); crate::metrics::inc_counter_vec( &crate::metrics::ENDPOINT_REQUESTS, &[endpoint_str], ); - match func(&endpoint.endpoint).await { + match func(&endpoint.client).await { Ok(t) => Ok(t), Err(t) => { crate::metrics::inc_counter_vec( @@ -186,8 +179,7 @@ impl EndpointsCache { /// Returns `Ok` if the endpoint is usable, i.e. is reachable and has a correct network id and /// chain id. Otherwise it returns `Err`. async fn endpoint_state( - endpoint: &SensitiveUrl, - config_network_id: &Eth1Id, + endpoint: &HttpJsonRpc, config_chain_id: &Eth1Id, log: &Logger, ) -> EndpointState { @@ -200,21 +192,9 @@ async fn endpoint_state( ); EndpointError::RequestFailed(e) }; - let network_id = get_network_id(endpoint, Duration::from_millis(STANDARD_TIMEOUT_MILLIS)) - .await - .map_err(error_connecting)?; - if &network_id != config_network_id { - warn!( - log, - "Invalid eth1 network id on endpoint. Please switch to correct network id"; - "endpoint" => %endpoint, - "action" => "trying fallbacks", - "expected" => format!("{:?}",config_network_id), - "received" => format!("{:?}",network_id), - ); - return Err(EndpointError::WrongNetworkId); - } - let chain_id = get_chain_id(endpoint, Duration::from_millis(STANDARD_TIMEOUT_MILLIS)) + + let chain_id = endpoint + .get_chain_id(Duration::from_millis(STANDARD_TIMEOUT_MILLIS)) .await .map_err(error_connecting)?; // Eth1 nodes return chain_id = 0 if the node is not synced @@ -253,7 +233,7 @@ pub enum HeadType { /// Returns the head block and the new block ranges relevant for deposits and the block cache /// from the given endpoint. async fn get_remote_head_and_new_block_ranges( - endpoint: &SensitiveUrl, + endpoint: &HttpJsonRpc, service: &Service, node_far_behind_seconds: u64, ) -> Result< @@ -315,14 +295,14 @@ async fn get_remote_head_and_new_block_ranges( /// Returns the range of new block numbers to be considered for the given head type from the given /// endpoint. async fn relevant_new_block_numbers_from_endpoint( - endpoint: &SensitiveUrl, + endpoint: &HttpJsonRpc, service: &Service, head_type: HeadType, ) -> Result>, SingleEndpointError> { - let remote_highest_block = - get_block_number(endpoint, Duration::from_millis(BLOCK_NUMBER_TIMEOUT_MILLIS)) - .map_err(SingleEndpointError::GetBlockNumberFailed) - .await?; + let remote_highest_block = endpoint + .get_block_number(Duration::from_millis(BLOCK_NUMBER_TIMEOUT_MILLIS)) + .map_err(SingleEndpointError::GetBlockNumberFailed) + .await?; service.relevant_new_block_numbers(remote_highest_block, None, head_type) } @@ -379,14 +359,41 @@ pub struct DepositCacheUpdateOutcome { pub logs_imported: usize, } +/// Supports either one authenticated jwt JSON-RPC endpoint **or** +/// multiple non-authenticated endpoints with fallback. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Eth1Endpoint { + Auth { + endpoint: SensitiveUrl, + jwt_path: PathBuf, + jwt_id: Option, + jwt_version: Option, + }, + NoAuth(Vec), +} + +impl Eth1Endpoint { + fn len(&self) -> usize { + match &self { + Self::Auth { .. } => 1, + Self::NoAuth(urls) => urls.len(), + } + } + + pub fn get_endpoints(&self) -> Vec { + match &self { + Self::Auth { endpoint, .. } => vec![endpoint.clone()], + Self::NoAuth(endpoints) => endpoints.clone(), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { /// An Eth1 node (e.g., Geth) running a HTTP JSON-RPC endpoint. - pub endpoints: Vec, + pub endpoints: Eth1Endpoint, /// The address the `BlockCache` and `DepositCache` should assume is the canonical deposit contract. pub deposit_contract_address: String, - /// The eth1 network id where the deposit contract is deployed (Goerli/Mainnet). - pub network_id: Eth1Id, /// The eth1 chain id where the deposit contract is deployed (Goerli/Mainnet). pub chain_id: Eth1Id, /// Defines the first block that the `DepositCache` will start searching for deposit logs. @@ -461,10 +468,9 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - endpoints: vec![SensitiveUrl::parse(DEFAULT_ETH1_ENDPOINT) - .expect("The default Eth1 endpoint must always be a valid URL.")], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse(DEFAULT_ETH1_ENDPOINT) + .expect("The default Eth1 endpoint must always be a valid URL.")]), deposit_contract_address: "0x0000000000000000000000000000000000000000".into(), - network_id: DEFAULT_NETWORK_ID, chain_id: DEFAULT_CHAIN_ID, deposit_contract_deploy_block: 1, lowest_cached_block_number: 1, @@ -673,27 +679,45 @@ impl Service { } /// Builds a new `EndpointsCache` with empty states. - pub fn init_endpoints(&self) -> Arc { + pub fn init_endpoints(&self) -> Result, String> { let endpoints = self.config().endpoints.clone(); - let config_network_id = self.config().network_id.clone(); let config_chain_id = self.config().chain_id.clone(); + + let servers = match endpoints { + Eth1Endpoint::Auth { + jwt_path, + endpoint, + jwt_id, + jwt_version, + } => { + let auth = Auth::new_with_path(jwt_path, jwt_id, jwt_version) + .map_err(|e| format!("Failed to initialize jwt auth: {:?}", e))?; + vec![HttpJsonRpc::new_with_auth(endpoint, auth) + .map_err(|e| format!("Failed to build auth enabled json rpc {:?}", e))?] + } + Eth1Endpoint::NoAuth(urls) => urls + .into_iter() + .map(|url| { + HttpJsonRpc::new(url).map_err(|e| format!("Failed to build json rpc {:?}", e)) + }) + .collect::>()?, + }; let new_cache = Arc::new(EndpointsCache { - fallback: Fallback::new(endpoints.into_iter().map(EndpointWithState::new).collect()), - config_network_id, + fallback: Fallback::new(servers.into_iter().map(EndpointWithState::new).collect()), config_chain_id, log: self.log.clone(), }); let mut endpoints_cache = self.inner.endpoints_cache.write(); *endpoints_cache = Some(new_cache.clone()); - new_cache + Ok(new_cache) } /// Returns the cached `EndpointsCache` if it exists or builds a new one. - pub fn get_endpoints(&self) -> Arc { + pub fn get_endpoints(&self) -> Result, String> { let endpoints_cache = self.inner.endpoints_cache.read(); if let Some(cache) = endpoints_cache.clone() { - cache + Ok(cache) } else { drop(endpoints_cache); self.init_endpoints() @@ -711,7 +735,7 @@ impl Service { pub async fn update( &self, ) -> Result<(DepositCacheUpdateOutcome, BlockCacheUpdateOutcome), String> { - let endpoints = self.get_endpoints(); + let endpoints = self.get_endpoints()?; // Reset the state of any endpoints which have errored so their state can be redetermined. endpoints.reset_errorred_endpoints().await; @@ -738,7 +762,7 @@ impl Service { } } } - endpoints.fallback.map_format_error(|s| &s.endpoint, e) + endpoints.fallback.map_format_error(|s| &s.client, e) }; let process_err = |e: Error| match &e { @@ -988,15 +1012,15 @@ impl Service { */ let block_range_ref = &block_range; let logs = endpoints - .first_success(|e| async move { - get_deposit_logs_in_range( - e, - deposit_contract_address_ref, - block_range_ref.clone(), - Duration::from_millis(GET_DEPOSIT_LOG_TIMEOUT_MILLIS), - ) - .await - .map_err(SingleEndpointError::GetDepositLogsFailed) + .first_success(|endpoint| async move { + endpoint + .get_deposit_logs_in_range( + deposit_contract_address_ref, + block_range_ref.clone(), + Duration::from_millis(GET_DEPOSIT_LOG_TIMEOUT_MILLIS), + ) + .await + .map_err(SingleEndpointError::GetDepositLogsFailed) }) .await .map(|(res, _)| res) @@ -1305,7 +1329,7 @@ fn relevant_block_range( /// /// Performs three async calls to an Eth1 HTTP JSON RPC endpoint. async fn download_eth1_block( - endpoint: &SensitiveUrl, + endpoint: &HttpJsonRpc, cache: Arc, block_number_opt: Option, ) -> Result { @@ -1326,15 +1350,15 @@ async fn download_eth1_block( }); // Performs a `get_blockByNumber` call to an eth1 node. - let http_block = get_block( - endpoint, - block_number_opt - .map(BlockQuery::Number) - .unwrap_or_else(|| BlockQuery::Latest), - Duration::from_millis(GET_BLOCK_TIMEOUT_MILLIS), - ) - .map_err(SingleEndpointError::BlockDownloadFailed) - .await?; + let http_block = endpoint + .get_block( + block_number_opt + .map(BlockQuery::Number) + .unwrap_or_else(|| BlockQuery::Latest), + Duration::from_millis(GET_BLOCK_TIMEOUT_MILLIS), + ) + .map_err(SingleEndpointError::BlockDownloadFailed) + .await?; Ok(Eth1Block { hash: http_block.hash, @@ -1359,8 +1383,8 @@ mod tests { #[test] fn serde_serialize() { let serialized = - toml::to_string(&Config::default()).expect("Should serde encode default config"); - toml::from_str::(&serialized).expect("Should serde decode default config"); + serde_yaml::to_string(&Config::default()).expect("Should serde encode default config"); + serde_yaml::from_str::(&serialized).expect("Should serde decode default config"); } #[test] diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index 3fe3b3ca52..f7f3b6e703 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -1,9 +1,9 @@ #![cfg(test)] use environment::{Environment, EnvironmentBuilder}; -use eth1::http::{get_deposit_count, get_deposit_logs_in_range, get_deposit_root, Block, Log}; -use eth1::{Config, Service}; -use eth1::{DepositCache, DEFAULT_CHAIN_ID, DEFAULT_NETWORK_ID}; +use eth1::{Config, Eth1Endpoint, Service}; +use eth1::{DepositCache, DEFAULT_CHAIN_ID}; use eth1_test_rig::GanacheEth1Instance; +use execution_layer::http::{deposit_methods::*, HttpJsonRpc, Log}; use merkle_proof::verify_merkle_proof; use sensitive_url::SensitiveUrl; use slog::Logger; @@ -51,39 +51,39 @@ fn random_deposit_data() -> DepositData { } /// Blocking operation to get the deposit logs from the `deposit_contract`. -async fn blocking_deposit_logs(eth1: &GanacheEth1Instance, range: Range) -> Vec { - get_deposit_logs_in_range( - &SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), - ð1.deposit_contract.address(), - range, - timeout(), - ) - .await - .expect("should get logs") +async fn blocking_deposit_logs( + client: &HttpJsonRpc, + eth1: &GanacheEth1Instance, + range: Range, +) -> Vec { + client + .get_deposit_logs_in_range(ð1.deposit_contract.address(), range, timeout()) + .await + .expect("should get logs") } /// Blocking operation to get the deposit root from the `deposit_contract`. -async fn blocking_deposit_root(eth1: &GanacheEth1Instance, block_number: u64) -> Option { - get_deposit_root( - &SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), - ð1.deposit_contract.address(), - block_number, - timeout(), - ) - .await - .expect("should get deposit root") +async fn blocking_deposit_root( + client: &HttpJsonRpc, + eth1: &GanacheEth1Instance, + block_number: u64, +) -> Option { + client + .get_deposit_root(ð1.deposit_contract.address(), block_number, timeout()) + .await + .expect("should get deposit root") } /// Blocking operation to get the deposit count from the `deposit_contract`. -async fn blocking_deposit_count(eth1: &GanacheEth1Instance, block_number: u64) -> Option { - get_deposit_count( - &SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), - ð1.deposit_contract.address(), - block_number, - timeout(), - ) - .await - .expect("should get deposit count") +async fn blocking_deposit_count( + client: &HttpJsonRpc, + eth1: &GanacheEth1Instance, + block_number: u64, +) -> Option { + client + .get_deposit_count(ð1.deposit_contract.address(), block_number, timeout()) + .await + .expect("should get deposit count") } async fn get_block_number(web3: &Web3) -> u64 { @@ -95,7 +95,7 @@ async fn get_block_number(web3: &Web3) -> u64 { } async fn new_ganache_instance() -> Result { - GanacheEth1Instance::new(DEFAULT_NETWORK_ID.into(), DEFAULT_CHAIN_ID.into()).await + GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()).await } mod eth1_cache { @@ -117,7 +117,10 @@ mod eth1_cache { let initial_block_number = get_block_number(&web3).await; let config = Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: initial_block_number, follow_distance, @@ -146,7 +149,7 @@ mod eth1_cache { eth1.ganache.evm_mine().await.expect("should mine block"); } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) @@ -198,7 +201,10 @@ mod eth1_cache { let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: get_block_number(&web3).await, follow_distance: 0, @@ -215,7 +221,7 @@ mod eth1_cache { eth1.ganache.evm_mine().await.expect("should mine block") } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) @@ -252,7 +258,10 @@ mod eth1_cache { let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: get_block_number(&web3).await, follow_distance: 0, @@ -267,7 +276,7 @@ mod eth1_cache { for _ in 0..cache_len / 2 { eth1.ganache.evm_mine().await.expect("should mine block") } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) .await @@ -302,7 +311,10 @@ mod eth1_cache { let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: get_block_number(&web3).await, follow_distance: 0, @@ -316,7 +328,7 @@ mod eth1_cache { eth1.ganache.evm_mine().await.expect("should mine block") } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); futures::try_join!( service.update_deposit_cache(None, &endpoints), service.update_deposit_cache(None, &endpoints) @@ -354,7 +366,10 @@ mod deposit_tree { let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), deposit_contract_deploy_block: start_block, follow_distance: 0, @@ -374,7 +389,7 @@ mod deposit_tree { .expect("should perform a deposit"); } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) @@ -434,7 +449,10 @@ mod deposit_tree { let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), deposit_contract_deploy_block: start_block, lowest_cached_block_number: start_block, @@ -454,7 +472,7 @@ mod deposit_tree { .expect("should perform a deposit"); } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); futures::try_join!( service.update_deposit_cache(None, &endpoints), service.update_deposit_cache(None, &endpoints) @@ -484,6 +502,8 @@ mod deposit_tree { let mut deposit_roots = vec![]; let mut deposit_counts = vec![]; + let client = HttpJsonRpc::new(SensitiveUrl::parse(ð1.endpoint()).unwrap()).unwrap(); + // Perform deposits to the smart contract, recording it's state along the way. for deposit in &deposits { deposit_contract @@ -492,12 +512,12 @@ mod deposit_tree { .expect("should perform a deposit"); let block_number = get_block_number(&web3).await; deposit_roots.push( - blocking_deposit_root(ð1, block_number) + blocking_deposit_root(&client, ð1, block_number) .await .expect("should get root if contract exists"), ); deposit_counts.push( - blocking_deposit_count(ð1, block_number) + blocking_deposit_count(&client, ð1, block_number) .await .expect("should get count if contract exists"), ); @@ -507,7 +527,7 @@ mod deposit_tree { // Pull all the deposit logs from the contract. let block_number = get_block_number(&web3).await; - let logs: Vec<_> = blocking_deposit_logs(ð1, 0..block_number) + let logs: Vec<_> = blocking_deposit_logs(&client, ð1, 0..block_number) .await .iter() .map(|raw| raw.to_deposit_log(spec).expect("should parse deposit log")) @@ -570,16 +590,12 @@ mod deposit_tree { /// Tests for the base HTTP requests and response handlers. mod http { use super::*; - use eth1::http::BlockQuery; - async fn get_block(eth1: &GanacheEth1Instance, block_number: u64) -> Block { - eth1::http::get_block( - &SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), - BlockQuery::Number(block_number), - timeout(), - ) - .await - .expect("should get block number") + async fn get_block(client: &HttpJsonRpc, block_number: u64) -> Block { + client + .get_block(BlockQuery::Number(block_number), timeout()) + .await + .expect("should get block number") } #[tokio::test] @@ -590,17 +606,18 @@ mod http { .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; let web3 = eth1.web3(); + let client = HttpJsonRpc::new(SensitiveUrl::parse(ð1.endpoint()).unwrap()).unwrap(); let block_number = get_block_number(&web3).await; - let logs = blocking_deposit_logs(ð1, 0..block_number).await; + let logs = blocking_deposit_logs(&client, ð1, 0..block_number).await; assert_eq!(logs.len(), 0); - let mut old_root = blocking_deposit_root(ð1, block_number).await; - let mut old_block = get_block(ð1, block_number).await; + let mut old_root = blocking_deposit_root(&client, ð1, block_number).await; + let mut old_block = get_block(&client, block_number).await; let mut old_block_number = block_number; assert_eq!( - blocking_deposit_count(ð1, block_number).await, + blocking_deposit_count(&client, ð1, block_number).await, Some(0), "should have deposit count zero" ); @@ -618,18 +635,18 @@ mod http { // Check the logs. let block_number = get_block_number(&web3).await; - let logs = blocking_deposit_logs(ð1, 0..block_number).await; + let logs = blocking_deposit_logs(&client, ð1, 0..block_number).await; assert_eq!(logs.len(), i, "the number of logs should be as expected"); // Check the deposit count. assert_eq!( - blocking_deposit_count(ð1, block_number).await, + blocking_deposit_count(&client, ð1, block_number).await, Some(i as u64), "should have a correct deposit count" ); // Check the deposit root. - let new_root = blocking_deposit_root(ð1, block_number).await; + let new_root = blocking_deposit_root(&client, ð1, block_number).await; assert_ne!( new_root, old_root, "deposit root should change with each deposit" @@ -637,7 +654,7 @@ mod http { old_root = new_root; // Check the block hash. - let new_block = get_block(ð1, block_number).await; + let new_block = get_block(&client, block_number).await; assert_ne!( new_block.hash, old_block.hash, "block hash should change with each deposit" @@ -689,7 +706,10 @@ mod fast { let now = get_block_number(&web3).await; let service = Service::new( Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), deposit_contract_deploy_block: now, lowest_cached_block_number: now, @@ -700,6 +720,7 @@ mod fast { log, MainnetEthSpec::default_spec(), ); + let client = HttpJsonRpc::new(SensitiveUrl::parse(ð1.endpoint()).unwrap()).unwrap(); let n = 10; let deposits: Vec<_> = (0..n).map(|_| random_deposit_data()).collect(); for deposit in &deposits { @@ -711,7 +732,7 @@ mod fast { eth1.ganache.evm_mine().await.expect("should mine block"); } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) .await @@ -723,8 +744,9 @@ mod fast { ); for block_num in 0..=get_block_number(&web3).await { - let expected_deposit_count = blocking_deposit_count(ð1, block_num).await; - let expected_deposit_root = blocking_deposit_root(ð1, block_num).await; + let expected_deposit_count = + blocking_deposit_count(&client, ð1, block_num).await; + let expected_deposit_root = blocking_deposit_root(&client, ð1, block_num).await; let deposit_count = service .deposits() @@ -765,7 +787,10 @@ mod persist { let now = get_block_number(&web3).await; let config = Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), deposit_contract_deploy_block: now, lowest_cached_block_number: now, @@ -783,7 +808,7 @@ mod persist { .expect("should perform a deposit"); } - let endpoints = service.init_endpoints(); + let endpoints = service.init_endpoints().unwrap(); service .update_deposit_cache(None, &endpoints) .await @@ -874,10 +899,10 @@ mod fallbacks { let service = Service::new( Config { - endpoints: vec![ + endpoints: Eth1Endpoint::NoAuth(vec![ SensitiveUrl::parse(endpoint1.endpoint().as_str()).unwrap(), SensitiveUrl::parse(endpoint2.endpoint().as_str()).unwrap(), - ], + ]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: initial_block_number, follow_distance: 0, @@ -909,82 +934,13 @@ mod fallbacks { .await; } - #[tokio::test] - async fn test_fallback_when_wrong_network_id() { - async { - let log = null_logger(); - let correct_network_id: u64 = DEFAULT_NETWORK_ID.into(); - let wrong_network_id = correct_network_id + 1; - let endpoint1 = GanacheEth1Instance::new(wrong_network_id, DEFAULT_CHAIN_ID.into()) - .await - .expect("should start eth1 environment"); - let endpoint2 = new_ganache_instance() - .await - .expect("should start eth1 environment"); - let deposit_contract = &endpoint2.deposit_contract; - - let initial_block_number = get_block_number(&endpoint2.web3()).await; - - // Create some blocks and then consume them, performing the test `rounds` times. - let new_blocks = 4; - - for _ in 0..new_blocks { - endpoint1 - .ganache - .evm_mine() - .await - .expect("should mine block"); - endpoint2 - .ganache - .evm_mine() - .await - .expect("should mine block"); - } - - //additional blocks for endpoint1 to be able to distinguish - for _ in 0..new_blocks { - endpoint1 - .ganache - .evm_mine() - .await - .expect("should mine block"); - } - - let service = Service::new( - Config { - endpoints: vec![ - SensitiveUrl::parse(endpoint2.endpoint().as_str()).unwrap(), - SensitiveUrl::parse(endpoint1.endpoint().as_str()).unwrap(), - ], - deposit_contract_address: deposit_contract.address(), - lowest_cached_block_number: initial_block_number, - follow_distance: 0, - ..Config::default() - }, - log.clone(), - MainnetEthSpec::default_spec(), - ); - - let endpoint1_block_number = get_block_number(&endpoint1.web3()).await; - let endpoint2_block_number = get_block_number(&endpoint2.web3()).await; - assert!(endpoint2_block_number < endpoint1_block_number); - //the call will fallback to endpoint2 - service.update().await.expect("should update deposit cache"); - assert_eq!( - service.deposits().read().last_processed_block.unwrap(), - endpoint2_block_number - ); - } - .await; - } - #[tokio::test] async fn test_fallback_when_wrong_chain_id() { async { let log = null_logger(); let correct_chain_id: u64 = DEFAULT_CHAIN_ID.into(); let wrong_chain_id = correct_chain_id + 1; - let endpoint1 = GanacheEth1Instance::new(DEFAULT_NETWORK_ID.into(), wrong_chain_id) + let endpoint1 = GanacheEth1Instance::new(wrong_chain_id) .await .expect("should start eth1 environment"); let endpoint2 = new_ganache_instance() @@ -1021,10 +977,10 @@ mod fallbacks { let service = Service::new( Config { - endpoints: vec![ + endpoints: Eth1Endpoint::NoAuth(vec![ SensitiveUrl::parse(endpoint2.endpoint().as_str()).unwrap(), SensitiveUrl::parse(endpoint1.endpoint().as_str()).unwrap(), - ], + ]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: initial_block_number, follow_distance: 0, @@ -1076,10 +1032,10 @@ mod fallbacks { let service = Service::new( Config { - endpoints: vec![ + endpoints: Eth1Endpoint::NoAuth(vec![ SensitiveUrl::parse(endpoint1.endpoint().as_str()).unwrap(), SensitiveUrl::parse(endpoint2.endpoint().as_str()).unwrap(), - ], + ]), deposit_contract_address: deposit_contract.address(), lowest_cached_block_number: initial_block_number, follow_distance: 0, diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 0351b5e433..dbd6324680 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -16,14 +16,16 @@ reqwest = { version = "0.11.0", features = ["json","stream"] } eth2_serde_utils = "0.1.1" serde_json = "1.0.58" serde = { version = "1.0.116", features = ["derive"] } -eth1 = { path = "../eth1" } warp = { version = "0.3.2", features = ["tls"] } jsonwebtoken = "8" environment = { path = "../../lighthouse/environment" } bytes = "1.1.0" task_executor = { path = "../../common/task_executor" } hex = "0.4.2" +eth2_ssz = "0.4.1" eth2_ssz_types = "0.2.2" +eth2 = { path = "../../common/eth2" } +state_processing = { path = "../../consensus/state_processing" } lru = "0.7.1" exit-future = "0.2.0" tree_hash = "0.4.1" diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 9eb98cecb9..5f3edb78bf 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -1,7 +1,7 @@ use crate::engines::ForkChoiceState; use async_trait::async_trait; -use eth1::http::RpcError; pub use ethers_core::types::Transaction; +use http::deposit_methods::RpcError; pub use json_structures::TransitionConfigurationV1; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/beacon_node/execution_layer/src/engine_api/auth.rs b/beacon_node/execution_layer/src/engine_api/auth.rs index a4050a25c0..560e43585b 100644 --- a/beacon_node/execution_layer/src/engine_api/auth.rs +++ b/beacon_node/execution_layer/src/engine_api/auth.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use jsonwebtoken::{encode, get_current_timestamp, Algorithm, EncodingKey, Header}; use rand::Rng; use serde::{Deserialize, Serialize}; @@ -13,6 +15,7 @@ pub const JWT_SECRET_LENGTH: usize = 32; pub enum Error { JWT(jsonwebtoken::errors::Error), InvalidToken, + InvalidKey(String), } impl From for Error { @@ -57,6 +60,14 @@ impl JwtKey { } } +pub fn strip_prefix(s: &str) -> &str { + if let Some(stripped) = s.strip_prefix("0x") { + stripped + } else { + s + } +} + /// Contains the JWT secret and claims parameters. pub struct Auth { key: EncodingKey, @@ -73,6 +84,28 @@ impl Auth { } } + /// Create a new `Auth` struct given the path to the file containing the hex + /// encoded jwt key. + pub fn new_with_path( + jwt_path: PathBuf, + id: Option, + clv: Option, + ) -> Result { + std::fs::read_to_string(&jwt_path) + .map_err(|e| { + Error::InvalidKey(format!( + "Failed to read JWT secret file {:?}, error: {:?}", + jwt_path, e + )) + }) + .and_then(|ref s| { + let secret_bytes = hex::decode(strip_prefix(s.trim_end())) + .map_err(|e| Error::InvalidKey(format!("Invalid hex string: {:?}", e)))?; + let secret = JwtKey::from_slice(&secret_bytes).map_err(Error::InvalidKey)?; + Ok(Self::new(secret, id, clv)) + }) + } + /// Generate a JWT token with `claims.iat` set to current time. pub fn generate_token(&self) -> Result { let claims = self.generate_claims_at_timestamp(); diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 179045ccf8..157f9a3054 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -3,15 +3,16 @@ use super::*; use crate::auth::Auth; use crate::json_structures::*; -use eth1::http::EIP155_ERROR_STR; use reqwest::header::CONTENT_TYPE; use sensitive_url::SensitiveUrl; use serde::de::DeserializeOwned; use serde_json::json; use std::marker::PhantomData; + use std::time::Duration; use types::{BlindedPayload, EthSpec, ExecutionPayloadHeader, SignedBeaconBlock}; +pub use deposit_log::{DepositLog, Log}; pub use reqwest::Client; const STATIC_ID: u32 = 1; @@ -48,6 +49,480 @@ pub const BUILDER_GET_PAYLOAD_HEADER_TIMEOUT: Duration = Duration::from_secs(2); pub const BUILDER_PROPOSE_BLINDED_BLOCK_V1: &str = "builder_proposeBlindedBlockV1"; pub const BUILDER_PROPOSE_BLINDED_BLOCK_TIMEOUT: Duration = Duration::from_secs(2); +/// This error is returned during a `chainId` call by Geth. +pub const EIP155_ERROR_STR: &str = "chain not synced beyond EIP-155 replay-protection fork block"; + +/// Contains methods to convert arbitary bytes to an ETH2 deposit contract object. +pub mod deposit_log { + use ssz::Decode; + use state_processing::per_block_processing::signature_sets::deposit_pubkey_signature_message; + use types::{ChainSpec, DepositData, Hash256, PublicKeyBytes, SignatureBytes}; + + pub use eth2::lighthouse::DepositLog; + + /// The following constants define the layout of bytes in the deposit contract `DepositEvent`. The + /// event bytes are formatted according to the Ethereum ABI. + const PUBKEY_START: usize = 192; + const PUBKEY_LEN: usize = 48; + const CREDS_START: usize = PUBKEY_START + 64 + 32; + const CREDS_LEN: usize = 32; + const AMOUNT_START: usize = CREDS_START + 32 + 32; + const AMOUNT_LEN: usize = 8; + const SIG_START: usize = AMOUNT_START + 32 + 32; + const SIG_LEN: usize = 96; + const INDEX_START: usize = SIG_START + 96 + 32; + const INDEX_LEN: usize = 8; + + /// A reduced set of fields from an Eth1 contract log. + #[derive(Debug, PartialEq, Clone)] + pub struct Log { + pub block_number: u64, + pub data: Vec, + } + + impl Log { + /// Attempts to parse a raw `Log` from the deposit contract into a `DepositLog`. + pub fn to_deposit_log(&self, spec: &ChainSpec) -> Result { + let bytes = &self.data; + + let pubkey = bytes + .get(PUBKEY_START..PUBKEY_START + PUBKEY_LEN) + .ok_or("Insufficient bytes for pubkey")?; + let withdrawal_credentials = bytes + .get(CREDS_START..CREDS_START + CREDS_LEN) + .ok_or("Insufficient bytes for withdrawal credential")?; + let amount = bytes + .get(AMOUNT_START..AMOUNT_START + AMOUNT_LEN) + .ok_or("Insufficient bytes for amount")?; + let signature = bytes + .get(SIG_START..SIG_START + SIG_LEN) + .ok_or("Insufficient bytes for signature")?; + let index = bytes + .get(INDEX_START..INDEX_START + INDEX_LEN) + .ok_or("Insufficient bytes for index")?; + + let deposit_data = DepositData { + pubkey: PublicKeyBytes::from_ssz_bytes(pubkey) + .map_err(|e| format!("Invalid pubkey ssz: {:?}", e))?, + withdrawal_credentials: Hash256::from_ssz_bytes(withdrawal_credentials) + .map_err(|e| format!("Invalid withdrawal_credentials ssz: {:?}", e))?, + amount: u64::from_ssz_bytes(amount) + .map_err(|e| format!("Invalid amount ssz: {:?}", e))?, + signature: SignatureBytes::from_ssz_bytes(signature) + .map_err(|e| format!("Invalid signature ssz: {:?}", e))?, + }; + + let signature_is_valid = deposit_pubkey_signature_message(&deposit_data, spec) + .map_or(false, |(public_key, signature, msg)| { + signature.verify(&public_key, msg) + }); + + Ok(DepositLog { + deposit_data, + block_number: self.block_number, + index: u64::from_ssz_bytes(index) + .map_err(|e| format!("Invalid index ssz: {:?}", e))?, + signature_is_valid, + }) + } + } + + #[cfg(test)] + pub mod tests { + use super::*; + use types::{EthSpec, MainnetEthSpec}; + + /// The data from a deposit event, using the v0.8.3 version of the deposit contract. + pub const EXAMPLE_LOG: &[u8] = &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 167, 108, 6, 69, 88, 17, + 3, 51, 6, 4, 158, 232, 82, 248, 218, 2, 71, 219, 55, 102, 86, 125, 136, 203, 36, 77, + 64, 213, 43, 52, 175, 154, 239, 50, 142, 52, 201, 77, 54, 239, 0, 229, 22, 46, 139, + 120, 62, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8, 0, 64, 89, 115, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 96, 140, 74, 175, 158, 209, 20, 206, 30, 63, 215, 238, 113, 60, + 132, 216, 211, 100, 186, 202, 71, 34, 200, 160, 225, 212, 213, 119, 88, 51, 80, 101, + 74, 2, 45, 78, 153, 12, 192, 44, 51, 77, 40, 10, 72, 246, 34, 193, 187, 22, 95, 4, 211, + 245, 224, 13, 162, 21, 163, 54, 225, 22, 124, 3, 56, 14, 81, 122, 189, 149, 250, 251, + 159, 22, 77, 94, 157, 197, 196, 253, 110, 201, 88, 193, 246, 136, 226, 221, 18, 113, + 232, 105, 100, 114, 103, 237, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + #[test] + fn can_parse_example_log() { + let log = Log { + block_number: 42, + data: EXAMPLE_LOG.to_vec(), + }; + log.to_deposit_log(&MainnetEthSpec::default_spec()) + .expect("should decode log"); + } + } +} + +/// Contains subset of the HTTP JSON-RPC methods used to query an execution node for +/// state of the deposit contract. +pub mod deposit_methods { + use super::Log; + use crate::{EngineApi, HttpJsonRpc}; + use serde::{Deserialize, Serialize}; + use serde_json::{json, Value}; + use std::fmt; + use std::ops::Range; + use std::str::FromStr; + use std::time::Duration; + use types::Hash256; + + /// `keccak("DepositEvent(bytes,bytes,bytes,bytes,bytes)")` + pub const DEPOSIT_EVENT_TOPIC: &str = + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"; + /// `keccak("get_deposit_root()")[0..4]` + pub const DEPOSIT_ROOT_FN_SIGNATURE: &str = "0xc5f2892f"; + /// `keccak("get_deposit_count()")[0..4]` + pub const DEPOSIT_COUNT_FN_SIGNATURE: &str = "0x621fd130"; + + /// Number of bytes in deposit contract deposit root response. + pub const DEPOSIT_COUNT_RESPONSE_BYTES: usize = 96; + /// Number of bytes in deposit contract deposit root (value only). + pub const DEPOSIT_ROOT_BYTES: usize = 32; + + /// Represents an eth1 chain/network id. + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] + pub enum Eth1Id { + Goerli, + Mainnet, + Custom(u64), + } + + #[derive(Debug, PartialEq, Clone)] + pub struct Block { + pub hash: Hash256, + pub timestamp: u64, + pub number: u64, + } + + /// Used to identify a block when querying the Eth1 node. + #[derive(Clone, Copy)] + pub enum BlockQuery { + Number(u64), + Latest, + } + + impl Into for Eth1Id { + fn into(self) -> u64 { + match self { + Eth1Id::Mainnet => 1, + Eth1Id::Goerli => 5, + Eth1Id::Custom(id) => id, + } + } + } + + impl From for Eth1Id { + fn from(id: u64) -> Self { + let into = |x: Eth1Id| -> u64 { x.into() }; + match id { + id if id == into(Eth1Id::Mainnet) => Eth1Id::Mainnet, + id if id == into(Eth1Id::Goerli) => Eth1Id::Goerli, + id => Eth1Id::Custom(id), + } + } + } + + impl FromStr for Eth1Id { + type Err = String; + + fn from_str(s: &str) -> Result { + s.parse::() + .map(Into::into) + .map_err(|e| format!("Failed to parse eth1 network id {}", e)) + } + } + + /// Represents an error received from a remote procecdure call. + #[derive(Debug, Serialize, Deserialize)] + pub enum RpcError { + NoResultField, + Eip155Error, + InvalidJson(String), + Error(String), + } + + impl fmt::Display for RpcError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RpcError::NoResultField => write!(f, "No result field in response"), + RpcError::Eip155Error => write!(f, "Not synced past EIP-155"), + RpcError::InvalidJson(e) => write!(f, "Malformed JSON received: {}", e), + RpcError::Error(s) => write!(f, "{}", s), + } + } + } + + impl From for String { + fn from(e: RpcError) -> String { + e.to_string() + } + } + + /// Parses a `0x`-prefixed, **big-endian** hex string as a u64. + /// + /// Note: the JSON-RPC encodes integers as big-endian. The deposit contract uses little-endian. + /// Therefore, this function is only useful for numbers encoded by the JSON RPC. + /// + /// E.g., `0x01 == 1` + fn hex_to_u64_be(hex: &str) -> Result { + u64::from_str_radix(strip_prefix(hex)?, 16) + .map_err(|e| format!("Failed to parse hex as u64: {:?}", e)) + } + + /// Parses a `0x`-prefixed, big-endian hex string as bytes. + /// + /// E.g., `0x0102 == vec![1, 2]` + fn hex_to_bytes(hex: &str) -> Result, String> { + hex::decode(strip_prefix(hex)?) + .map_err(|e| format!("Failed to parse hex as bytes: {:?}", e)) + } + + /// Removes the `0x` prefix from some bytes. Returns an error if the prefix is not present. + fn strip_prefix(hex: &str) -> Result<&str, String> { + if let Some(stripped) = hex.strip_prefix("0x") { + Ok(stripped) + } else { + Err("Hex string did not start with `0x`".to_string()) + } + } + + impl HttpJsonRpc { + /// Get the eth1 chain id of the given endpoint. + pub async fn get_chain_id(&self, timeout: Duration) -> Result { + let chain_id: String = self + .rpc_request("eth_chainId", json!([]), timeout) + .await + .map_err(|e| format!("eth_chainId call failed {:?}", e))?; + hex_to_u64_be(chain_id.as_str()).map(|id| id.into()) + } + + /// Returns the current block number. + pub async fn get_block_number(&self, timeout: Duration) -> Result { + let response: String = self + .rpc_request("eth_blockNumber", json!([]), timeout) + .await + .map_err(|e| format!("eth_blockNumber call failed {:?}", e))?; + hex_to_u64_be(response.as_str()) + .map_err(|e| format!("Failed to get block number: {}", e)) + } + + /// Gets a block hash by block number. + pub async fn get_block( + &self, + query: BlockQuery, + timeout: Duration, + ) -> Result { + let query_param = match query { + BlockQuery::Number(block_number) => format!("0x{:x}", block_number), + BlockQuery::Latest => "latest".to_string(), + }; + let params = json!([ + query_param, + false // do not return full tx objects. + ]); + + let response: Value = self + .rpc_request("eth_getBlockByNumber", params, timeout) + .await + .map_err(|e| format!("eth_getBlockByNumber call failed {:?}", e))?; + + let hash: Vec = hex_to_bytes( + response + .get("hash") + .ok_or("No hash for block")? + .as_str() + .ok_or("Block hash was not string")?, + )?; + let hash: Hash256 = if hash.len() == 32 { + Hash256::from_slice(&hash) + } else { + return Err(format!("Block hash was not 32 bytes: {:?}", hash)); + }; + + let timestamp = hex_to_u64_be( + response + .get("timestamp") + .ok_or("No timestamp for block")? + .as_str() + .ok_or("Block timestamp was not string")?, + )?; + + let number = hex_to_u64_be( + response + .get("number") + .ok_or("No number for block")? + .as_str() + .ok_or("Block number was not string")?, + )?; + + if number <= usize::max_value() as u64 { + Ok(Block { + hash, + timestamp, + number, + }) + } else { + Err(format!("Block number {} is larger than a usize", number)) + } + .map_err(|e| format!("Failed to get block number: {}", e)) + } + + /// Returns the value of the `get_deposit_count()` call at the given `address` for the given + /// `block_number`. + /// + /// Assumes that the `address` has the same ABI as the eth2 deposit contract. + pub async fn get_deposit_count( + &self, + address: &str, + block_number: u64, + timeout: Duration, + ) -> Result, String> { + let result = self + .call(address, DEPOSIT_COUNT_FN_SIGNATURE, block_number, timeout) + .await?; + match result { + None => Err("Deposit root response was none".to_string()), + Some(bytes) => { + if bytes.is_empty() { + Ok(None) + } else if bytes.len() == DEPOSIT_COUNT_RESPONSE_BYTES { + let mut array = [0; 8]; + array.copy_from_slice(&bytes[32 + 32..32 + 32 + 8]); + Ok(Some(u64::from_le_bytes(array))) + } else { + Err(format!( + "Deposit count response was not {} bytes: {:?}", + DEPOSIT_COUNT_RESPONSE_BYTES, bytes + )) + } + } + } + } + + /// Returns the value of the `get_hash_tree_root()` call at the given `block_number`. + /// + /// Assumes that the `address` has the same ABI as the eth2 deposit contract. + pub async fn get_deposit_root( + &self, + address: &str, + block_number: u64, + timeout: Duration, + ) -> Result, String> { + let result = self + .call(address, DEPOSIT_ROOT_FN_SIGNATURE, block_number, timeout) + .await?; + match result { + None => Err("Deposit root response was none".to_string()), + Some(bytes) => { + if bytes.is_empty() { + Ok(None) + } else if bytes.len() == DEPOSIT_ROOT_BYTES { + Ok(Some(Hash256::from_slice(&bytes))) + } else { + Err(format!( + "Deposit root response was not {} bytes: {:?}", + DEPOSIT_ROOT_BYTES, bytes + )) + } + } + } + } + + /// Performs a instant, no-transaction call to the contract `address` with the given `0x`-prefixed + /// `hex_data`. + /// + /// Returns bytes, if any. + async fn call( + &self, + address: &str, + hex_data: &str, + block_number: u64, + timeout: Duration, + ) -> Result>, String> { + let params = json! ([ + { + "to": address, + "data": hex_data, + }, + format!("0x{:x}", block_number) + ]); + + let response: Option = self + .rpc_request("eth_call", params, timeout) + .await + .map_err(|e| format!("eth_call call failed {:?}", e))?; + + response.map(|s| hex_to_bytes(&s)).transpose() + } + + /// Returns logs for the `DEPOSIT_EVENT_TOPIC`, for the given `address` in the given + /// `block_height_range`. + /// + /// It's not clear from the Ethereum JSON-RPC docs if this range is inclusive or not. + pub async fn get_deposit_logs_in_range( + &self, + address: &str, + block_height_range: Range, + timeout: Duration, + ) -> Result, String> { + let params = json! ([{ + "address": address, + "topics": [DEPOSIT_EVENT_TOPIC], + "fromBlock": format!("0x{:x}", block_height_range.start), + "toBlock": format!("0x{:x}", block_height_range.end), + }]); + + let response: Value = self + .rpc_request("eth_getLogs", params, timeout) + .await + .map_err(|e| format!("eth_getLogs call failed {:?}", e))?; + response + .as_array() + .cloned() + .ok_or("'result' value was not an array")? + .into_iter() + .map(|value| { + let block_number = value + .get("blockNumber") + .ok_or("No block number field in log")? + .as_str() + .ok_or("Block number was not string")?; + + let data = value + .get("data") + .ok_or("No block number field in log")? + .as_str() + .ok_or("Data was not string")?; + + Ok(Log { + block_number: hex_to_u64_be(block_number)?, + data: hex_to_bytes(data)?, + }) + }) + .collect::, String>>() + .map_err(|e| format!("Failed to get logs in range: {}", e)) + } + } +} + pub struct HttpJsonRpc { pub client: Client, pub url: SensitiveUrl, @@ -117,6 +592,12 @@ impl HttpJsonRpc { } } +impl std::fmt::Display for HttpJsonRpc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}, auth={}", self.url, self.auth.is_some()) + } +} + impl HttpJsonRpc { pub async fn upcheck(&self) -> Result<(), Error> { let result: serde_json::Value = self @@ -289,6 +770,7 @@ impl HttpJsonRpc { Ok(response.into()) } } + #[cfg(test)] mod test { use super::auth::JwtKey; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index cff2190272..4b29887675 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -6,10 +6,10 @@ use crate::engine_api::Builder; use crate::engines::Builders; -use auth::{Auth, JwtKey}; +use auth::{strip_prefix, Auth, JwtKey}; use engine_api::Error as ApiError; pub use engine_api::*; -pub use engine_api::{http, http::HttpJsonRpc}; +pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc}; pub use engines::ForkChoiceState; use engines::{Engine, EngineError, Engines, Logging}; use lru::LruCache; @@ -42,6 +42,9 @@ mod metrics; mod payload_status; pub mod test_utils; +/// Indicates the default jwt authenticated execution endpoint. +pub const DEFAULT_EXECUTION_ENDPOINT: &str = "http://localhost:8551/"; + /// Name for the default file used for the jwt secret. pub const DEFAULT_JWT_FILE: &str = "jwt.hex"; @@ -130,14 +133,6 @@ pub struct Config { pub default_datadir: PathBuf, } -fn strip_prefix(s: &str) -> &str { - if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else { - s - } -} - /// Provides access to one or more execution engines and provides a neat interface for consumption /// by the `BeaconChain`. /// diff --git a/beacon_node/genesis/src/eth1_genesis_service.rs b/beacon_node/genesis/src/eth1_genesis_service.rs index aac13a324f..089f79aa11 100644 --- a/beacon_node/genesis/src/eth1_genesis_service.rs +++ b/beacon_node/genesis/src/eth1_genesis_service.rs @@ -112,7 +112,7 @@ impl Eth1GenesisService { "Importing eth1 deposit logs"; ); - let endpoints = eth1_service.init_endpoints(); + let endpoints = eth1_service.init_endpoints()?; loop { let update_result = eth1_service diff --git a/beacon_node/genesis/src/lib.rs b/beacon_node/genesis/src/lib.rs index ccf8fe10c9..1233d99fd3 100644 --- a/beacon_node/genesis/src/lib.rs +++ b/beacon_node/genesis/src/lib.rs @@ -3,6 +3,7 @@ mod eth1_genesis_service; mod interop; pub use eth1::Config as Eth1Config; +pub use eth1::Eth1Endpoint; pub use eth1_genesis_service::{Eth1GenesisService, Statistics}; pub use interop::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; pub use types::test_utils::generate_deterministic_keypairs; diff --git a/beacon_node/genesis/tests/tests.rs b/beacon_node/genesis/tests/tests.rs index 8b77c89471..74a054fcc0 100644 --- a/beacon_node/genesis/tests/tests.rs +++ b/beacon_node/genesis/tests/tests.rs @@ -4,7 +4,7 @@ //! dir in the root of the `lighthouse` repo. #![cfg(test)] use environment::{Environment, EnvironmentBuilder}; -use eth1::{DEFAULT_CHAIN_ID, DEFAULT_NETWORK_ID}; +use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; use eth1_test_rig::{DelayThenDeposit, GanacheEth1Instance}; use genesis::{Eth1Config, Eth1GenesisService}; use sensitive_url::SensitiveUrl; @@ -29,7 +29,7 @@ fn basic() { let mut spec = env.eth2_config().spec.clone(); env.runtime().block_on(async { - let eth1 = GanacheEth1Instance::new(DEFAULT_NETWORK_ID.into(), DEFAULT_CHAIN_ID.into()) + let eth1 = GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()) .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; @@ -44,7 +44,10 @@ fn basic() { let service = Eth1GenesisService::new( Eth1Config { - endpoints: vec![SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap()], + endpoints: Eth1Endpoint::NoAuth(vec![SensitiveUrl::parse( + eth1.endpoint().as_str(), + ) + .unwrap()]), deposit_contract_address: deposit_contract.address(), deposit_contract_deploy_block: now, lowest_cached_block_number: now, diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 3102018e3e..a0cc124d47 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -409,45 +409,46 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .arg( Arg::with_name("merge") .long("merge") - .help("Enable the features necessary to run merge testnets. This feature \ - is unstable and is for developers only.") - .takes_value(false), + .help("Deprecated. The feature activates automatically when --execution-endpoint \ + is supplied.") + .takes_value(false) ) .arg( - Arg::with_name("execution-endpoints") - .long("execution-endpoints") - .value_name("EXECUTION-ENDPOINTS") - .help("One or more comma-delimited server endpoints for HTTP JSON-RPC connection. \ - If multiple endpoints are given the endpoints are used as fallback in the \ - given order. Also enables the --merge flag. \ - If this flag is omitted and the --eth1-endpoints is supplied, those values \ - will be used. Defaults to http://127.0.0.1:8545.") + Arg::with_name("execution-endpoint") + .long("execution-endpoint") + .value_name("EXECUTION-ENDPOINT") + .alias("execution-endpoints") + .help("Server endpoint for an execution layer jwt authenticated HTTP \ + JSON-RPC connection. Uses the same endpoint to populate the \ + deposit cache. Also enables the --merge flag.\ + If not provided, uses the default value of http://127.0.0.1:8551") + .takes_value(true) + .requires("execution-jwt") + ) + .arg( + Arg::with_name("execution-jwt") + .long("execution-jwt") + .value_name("EXECUTION-JWT") + .alias("jwt-secrets") + .help("File path which contains the hex-encoded JWT secret for the \ + execution endpoint provided in the --execution-endpoint flag.") .takes_value(true) ) .arg( - Arg::with_name("jwt-secrets") - .long("jwt-secrets") - .value_name("JWT-SECRETS") - .help("One or more comma-delimited file paths which contain the corresponding hex-encoded \ - JWT secrets for each execution endpoint provided in the --execution-endpoints flag. \ - The number of paths should be in the same order and strictly equal to the number \ - of execution endpoints provided.") - .takes_value(true) - .requires("execution-endpoints") - ) - .arg( - Arg::with_name("jwt-id") - .long("jwt-id") - .value_name("JWT-ID") + Arg::with_name("execution-jwt-id") + .long("execution-jwt-id") + .value_name("EXECUTION-JWT-ID") + .alias("jwt-id") .help("Used by the beacon node to communicate a unique identifier to execution nodes \ during JWT authentication. It corresponds to the 'id' field in the JWT claims object.\ Set to empty by deafult") .takes_value(true) ) .arg( - Arg::with_name("jwt-version") - .long("jwt-version") - .value_name("JWT-VERSION") + Arg::with_name("execution-jwt-version") + .long("execution-jwt-version") + .value_name("EXECUTION-JWT-VERSION") + .alias("jwt-version") .help("Used by the beacon node to communicate a client version to execution nodes \ during JWT authentication. It corresponds to the 'clv' field in the JWT claims object.\ Set to empty by deafult") @@ -461,14 +462,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { collected from any blocks produced by this node. Defaults to a junk \ address whilst the merge is in development stages. THE DEFAULT VALUE \ WILL BE REMOVED BEFORE THE MERGE ENTERS PRODUCTION") - .requires("merge") + .requires("execution-endpoint") .takes_value(true) ) .arg( - Arg::with_name("payload-builders") - .long("payload-builders") + Arg::with_name("payload-builder") + .long("payload-builder") + .alias("payload-builders") .help("The URL of a service compatible with the MEV-boost API.") - .requires("merge") + .requires("execution-endpoint") .takes_value(true) ) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index db765100c3..0421df3429 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -3,12 +3,14 @@ use clap_utils::flags::DISABLE_MALLOC_TUNING_FLAG; use client::{ClientConfig, ClientGenesis}; use directory::{DEFAULT_BEACON_NODE_DIR, DEFAULT_NETWORK_DIR, DEFAULT_ROOT_DIR}; use environment::RuntimeContext; +use genesis::Eth1Endpoint; use http_api::TlsConfig; use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, PeerIdSerialized}; use sensitive_url::SensitiveUrl; use slog::{info, warn, Logger}; use std::cmp; use std::cmp::max; +use std::fmt::Debug; use std::fs; use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs}; use std::path::{Path, PathBuf}; @@ -215,15 +217,18 @@ pub fn get_config( "msg" => "please use --eth1-endpoints instead" ); client_config.sync_eth1_chain = true; - client_config.eth1.endpoints = vec![SensitiveUrl::parse(endpoint) + + let endpoints = vec![SensitiveUrl::parse(endpoint) .map_err(|e| format!("eth1-endpoint was an invalid URL: {:?}", e))?]; + client_config.eth1.endpoints = Eth1Endpoint::NoAuth(endpoints); } else if let Some(endpoints) = cli_args.value_of("eth1-endpoints") { client_config.sync_eth1_chain = true; - client_config.eth1.endpoints = endpoints + let endpoints = endpoints .split(',') .map(SensitiveUrl::parse) .collect::>() .map_err(|e| format!("eth1-endpoints contains an invalid URL {:?}", e))?; + client_config.eth1.endpoints = Eth1Endpoint::NoAuth(endpoints); } if let Some(val) = cli_args.value_of("eth1-blocks-per-log-query") { @@ -242,47 +247,79 @@ pub fn get_config( client_config.eth1.cache_follow_distance = Some(follow_distance); } - if cli_args.is_present("merge") || cli_args.is_present("execution-endpoints") { + if cli_args.is_present("merge") { + if cli_args.is_present("execution-endpoint") { + warn!( + log, + "The --merge flag is deprecated"; + "info" => "the --execution-endpoint flag automatically enables this feature" + ) + } else { + return Err("The --merge flag is deprecated. \ + Supply a value to --execution-endpoint instead." + .into()); + } + } + + if let Some(endpoints) = cli_args.value_of("execution-endpoint") { let mut el_config = execution_layer::Config::default(); - if let Some(endpoints) = cli_args.value_of("execution-endpoints") { - client_config.sync_eth1_chain = true; - el_config.execution_endpoints = endpoints - .split(',') - .map(SensitiveUrl::parse) - .collect::>() - .map_err(|e| format!("execution-endpoints contains an invalid URL {:?}", e))?; - } else if cli_args.is_present("merge") { - el_config.execution_endpoints = client_config.eth1.endpoints.clone(); - } - - if let Some(endpoints) = cli_args.value_of("payload-builders") { - el_config.builder_endpoints = endpoints - .split(',') - .map(SensitiveUrl::parse) - .collect::>() - .map_err(|e| format!("payload-builders contains an invalid URL {:?}", e))?; - } - - if let Some(secrets) = cli_args.value_of("jwt-secrets") { - let secret_files: Vec<_> = secrets.split(',').map(PathBuf::from).collect(); - if !secret_files.is_empty() && secret_files.len() != el_config.execution_endpoints.len() - { - return Err(format!( - "{} execution-endpoints supplied with {} jwt-secrets. Lengths \ - must match or jwt-secrets must be empty.", - el_config.execution_endpoints.len(), - secret_files.len(), - )); - } - el_config.secret_files = secret_files; + // Always follow the deposit contract when there is an execution endpoint. + // + // This is wasteful for non-staking nodes as they have no need to process deposit contract + // logs and build an "eth1" cache. The alternative is to explicitly require the `--eth1` or + // `--staking` flags, however that poses a risk to stakers since they cannot produce blocks + // without "eth1". + // + // The waste for non-staking nodes is relatively small so we err on the side of safety for + // stakers. The merge is already complicated enough. + client_config.sync_eth1_chain = true; + + // Parse a single execution endpoint, logging warnings if multiple endpoints are supplied. + let execution_endpoint = + parse_only_one_value(endpoints, SensitiveUrl::parse, "--execution-endpoint", log)?; + + // Parse a single JWT secret, logging warnings if multiple are supplied. + // + // JWTs are required if `--execution-endpoint` is supplied. + let secret_files: String = clap_utils::parse_required(cli_args, "execution-jwt")?; + let secret_file = + parse_only_one_value(&secret_files, PathBuf::from_str, "--execution-jwt", log)?; + + // Parse and set the payload builder, if any. + if let Some(endpoints) = cli_args.value_of("payload-builder") { + let payload_builder = + parse_only_one_value(endpoints, SensitiveUrl::parse, "--payload-builder", log)?; + el_config.builder_endpoints = vec![payload_builder]; } + // Set config values from parse values. + el_config.secret_files = vec![secret_file.clone()]; + el_config.execution_endpoints = vec![execution_endpoint.clone()]; el_config.suggested_fee_recipient = clap_utils::parse_optional(cli_args, "suggested-fee-recipient")?; - el_config.jwt_id = clap_utils::parse_optional(cli_args, "jwt-id")?; - el_config.jwt_version = clap_utils::parse_optional(cli_args, "jwt-version")?; + el_config.jwt_id = clap_utils::parse_optional(cli_args, "execution-jwt-id")?; + el_config.jwt_version = clap_utils::parse_optional(cli_args, "execution-jwt-version")?; el_config.default_datadir = client_config.data_dir.clone(); + + // If `--execution-endpoint` is provided, we should ignore any `--eth1-endpoints` values and + // use `--execution-endpoint` instead. Also, log a deprecation warning. + if cli_args.is_present("eth1-endpoints") || cli_args.is_present("eth1-endpoint") { + warn!( + log, + "Ignoring --eth1-endpoints flag"; + "info" => "the value for --execution-endpoint will be used instead. \ + --eth1-endpoints has been deprecated for post-merge configurations" + ); + } + client_config.eth1.endpoints = Eth1Endpoint::Auth { + endpoint: execution_endpoint, + jwt_path: secret_file, + jwt_id: el_config.jwt_id.clone(), + jwt_version: el_config.jwt_version.clone(), + }; + + // Store the EL config in the client config. client_config.execution_layer = Some(el_config); } @@ -344,7 +381,6 @@ pub fn get_config( client_config.eth1.follow_distance = spec.eth1_follow_distance; client_config.eth1.node_far_behind_seconds = max(5, spec.eth1_follow_distance / 2) * spec.seconds_per_eth1_block; - client_config.eth1.network_id = spec.deposit_network_id.into(); client_config.eth1.chain_id = spec.deposit_chain_id.into(); client_config.eth1.set_block_cache_truncation::(spec); @@ -844,3 +880,38 @@ pub fn get_slots_per_restore_point( Ok((default, false)) } } + +/// Parses the `cli_value` as a comma-separated string of values to be parsed with `parser`. +/// +/// If there is more than one value, log a warning. If there are no values, return an error. +pub fn parse_only_one_value( + cli_value: &str, + parser: F, + flag_name: &str, + log: &Logger, +) -> Result +where + F: Fn(&str) -> Result, + E: Debug, +{ + let values = cli_value + .split(',') + .map(parser) + .collect::, _>>() + .map_err(|e| format!("{} contains an invalid value {:?}", flag_name, e))?; + + if values.len() > 1 { + warn!( + log, + "Multiple values provided"; + "info" => "multiple values are deprecated, only the first value will be used", + "count" => values.len(), + "flag" => flag_name + ); + } + + values + .into_iter() + .next() + .ok_or(format!("Must provide at least one value to {}", flag_name)) +} diff --git a/lcli/src/eth1_genesis.rs b/lcli/src/eth1_genesis.rs index 689107228e..1046241953 100644 --- a/lcli/src/eth1_genesis.rs +++ b/lcli/src/eth1_genesis.rs @@ -1,7 +1,7 @@ use clap::ArgMatches; use environment::Environment; use eth2_network_config::Eth2NetworkConfig; -use genesis::{Eth1Config, Eth1GenesisService}; +use genesis::{Eth1Config, Eth1Endpoint, Eth1GenesisService}; use sensitive_url::SensitiveUrl; use ssz::Encode; use std::cmp::max; @@ -35,11 +35,12 @@ pub fn run( let mut config = Eth1Config::default(); if let Some(v) = endpoints.clone() { - config.endpoints = v + let endpoints = v .iter() .map(|s| SensitiveUrl::parse(s)) .collect::>() .map_err(|e| format!("Unable to parse eth1 endpoint URL: {:?}", e))?; + config.endpoints = Eth1Endpoint::NoAuth(endpoints); } config.deposit_contract_address = format!("{:?}", spec.deposit_contract_address); config.deposit_contract_deploy_block = eth2_network_config.deposit_contract_deploy_block; diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index d9bd4334cf..f7742ef0b9 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -55,6 +55,7 @@ validator_dir = { path = "../common/validator_dir" } slashing_protection = { path = "../validator_client/slashing_protection" } lighthouse_network = { path = "../beacon_node/lighthouse_network" } sensitive_url = { path = "../common/sensitive_url" } +eth1 = { path = "../beacon_node/eth1" } [[test]] name = "lighthouse_tests" diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index effccbbd66..443c442027 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1,6 +1,7 @@ use beacon_node::ClientConfig as Config; use crate::exec::{CommandLineTestExec, CompletedTest}; +use eth1::Eth1Endpoint; use lighthouse_network::PeerId; use std::fs::File; use std::io::Write; @@ -66,7 +67,10 @@ fn staking_flag() { .with_config(|config| { assert!(config.http_api.enabled); assert!(config.sync_eth1_chain); - assert_eq!(config.eth1.endpoints[0].to_string(), DEFAULT_ETH1_ENDPOINT); + assert_eq!( + config.eth1.endpoints.get_endpoints()[0].to_string(), + DEFAULT_ETH1_ENDPOINT + ); }); } @@ -196,18 +200,21 @@ fn eth1_endpoints_flag() { .run_with_zero_port() .with_config(|config| { assert_eq!( - config.eth1.endpoints[0].full.to_string(), + config.eth1.endpoints.get_endpoints()[0].full.to_string(), "http://localhost:9545/" ); assert_eq!( - config.eth1.endpoints[0].to_string(), + config.eth1.endpoints.get_endpoints()[0].to_string(), "http://localhost:9545/" ); assert_eq!( - config.eth1.endpoints[1].full.to_string(), + config.eth1.endpoints.get_endpoints()[1].full.to_string(), "https://infura.io/secret" ); - assert_eq!(config.eth1.endpoints[1].to_string(), "https://infura.io/"); + assert_eq!( + config.eth1.endpoints.get_endpoints()[1].to_string(), + "https://infura.io/" + ); assert!(config.sync_eth1_chain); }); } @@ -246,45 +253,107 @@ fn eth1_cache_follow_distance_manual() { } // Tests for Bellatrix flags. -#[test] -fn merge_flag() { - CommandLineTest::new() - .flag("merge", None) - .run_with_zero_port() - .with_config(|config| assert!(config.execution_layer.is_some())); -} -#[test] -fn merge_execution_endpoints_flag() { +fn run_merge_execution_endpoints_flag_test(flag: &str) { use sensitive_url::SensitiveUrl; let urls = vec!["http://sigp.io/no-way:1337", "http://infura.not_real:4242"]; - let endpoints = urls - .iter() - .map(|s| SensitiveUrl::parse(s).unwrap()) - .collect::>(); + // we don't support redundancy for execution-endpoints + // only the first provided endpoint is parsed. + let mut endpoint_arg = urls[0].to_string(); - for url in urls.into_iter().skip(1) { + for url in urls.iter().skip(1) { endpoint_arg.push(','); endpoint_arg.push_str(url); } + + let (_dirs, jwts): (Vec<_>, Vec<_>) = (0..2) + .map(|i| { + let dir = TempDir::new().expect("Unable to create temporary directory"); + let path = dir.path().join(format!("jwt-{}", i)); + (dir, path) + }) + .unzip(); + + let mut jwts_arg = jwts[0].as_os_str().to_str().unwrap().to_string(); + for jwt in jwts.iter().skip(1) { + jwts_arg.push(','); + jwts_arg.push_str(jwt.as_os_str().to_str().unwrap()); + } + // this is way better but intersperse is still a nightly feature :/ // let endpoint_arg: String = urls.into_iter().intersperse(",").collect(); CommandLineTest::new() - .flag("merge", None) - .flag("execution-endpoints", Some(&endpoint_arg)) + .flag(flag, Some(&endpoint_arg)) + .flag("execution-jwt", Some(&jwts_arg)) .run_with_zero_port() .with_config(|config| { let config = config.execution_layer.as_ref().unwrap(); - assert_eq!(config.execution_endpoints, endpoints) + assert_eq!(config.execution_endpoints.len(), 1); + assert_eq!( + config.execution_endpoints[0], + SensitiveUrl::parse(&urls[0]).unwrap() + ); + // Only the first secret file should be used. + assert_eq!(config.secret_files, vec![jwts[0].clone()]); }); } #[test] +fn merge_execution_endpoints_flag() { + run_merge_execution_endpoints_flag_test("execution-endpoints") +} +#[test] +fn merge_execution_endpoint_flag() { + run_merge_execution_endpoints_flag_test("execution-endpoint") +} +fn run_execution_endpoints_overrides_eth1_endpoints_test(eth1_flag: &str, execution_flag: &str) { + use sensitive_url::SensitiveUrl; + + let eth1_endpoint = "http://bad.bad"; + let execution_endpoint = "http://good.good"; + + assert!(eth1_endpoint != execution_endpoint); + + let dir = TempDir::new().expect("Unable to create temporary directory"); + let jwt_path = dir.path().join("jwt-file"); + + CommandLineTest::new() + .flag(eth1_flag, Some(ð1_endpoint)) + .flag(execution_flag, Some(&execution_endpoint)) + .flag("execution-jwt", jwt_path.as_os_str().to_str()) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.execution_layer.as_ref().unwrap().execution_endpoints, + vec![SensitiveUrl::parse(execution_endpoint).unwrap()] + ); + + // The eth1 endpoint should have been set to the --execution-endpoint value in defiance + // of --eth1-endpoints. + assert_eq!( + config.eth1.endpoints, + Eth1Endpoint::Auth { + endpoint: SensitiveUrl::parse(execution_endpoint).unwrap(), + jwt_path: jwt_path.clone(), + jwt_id: None, + jwt_version: None, + } + ); + }); +} +#[test] +fn execution_endpoints_overrides_eth1_endpoints() { + run_execution_endpoints_overrides_eth1_endpoints_test("eth1-endpoints", "execution-endpoints"); +} +#[test] +fn execution_endpoint_overrides_eth1_endpoint() { + run_execution_endpoints_overrides_eth1_endpoints_test("eth1-endpoint", "execution-endpoint"); +} +#[test] fn merge_jwt_secrets_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); let mut file = File::create(dir.path().join("jwtsecrets")).expect("Unable to create file"); file.write_all(b"0x3cbc11b0d8fa16f3344eacfd6ff6430b9d30734450e8adcf5400f88d327dcb33") .expect("Unable to write to file"); CommandLineTest::new() - .flag("merge", None) .flag("execution-endpoints", Some("http://localhost:8551/")) .flag( "jwt-secrets", @@ -302,8 +371,13 @@ fn merge_jwt_secrets_flag() { } #[test] fn merge_fee_recipient_flag() { + let dir = TempDir::new().expect("Unable to create temporary directory"); CommandLineTest::new() - .flag("merge", None) + .flag("execution-endpoint", Some("http://meow.cats")) + .flag( + "execution-jwt", + dir.path().join("jwt-file").as_os_str().to_str(), + ) .flag( "suggested-fee-recipient", Some("0x00000000219ab540356cbb839cbe05303d7705fa"), @@ -317,19 +391,74 @@ fn merge_fee_recipient_flag() { ); }); } -#[test] -fn jwt_optional_flags() { +fn run_payload_builder_flag_test(flag: &str, builders: &str) { + use sensitive_url::SensitiveUrl; + + let dir = TempDir::new().expect("Unable to create temporary directory"); + let all_builders: Vec<_> = builders + .split(",") + .map(|builder| SensitiveUrl::parse(builder).expect("valid builder url")) + .collect(); CommandLineTest::new() - .flag("merge", None) - .flag("jwt-id", Some("bn-1")) - .flag("jwt-version", Some("Lighthouse-v2.1.3")) + .flag("execution-endpoint", Some("http://meow.cats")) + .flag( + "execution-jwt", + dir.path().join("jwt-file").as_os_str().to_str(), + ) + .flag(flag, Some(builders)) .run_with_zero_port() .with_config(|config| { let config = config.execution_layer.as_ref().unwrap(); - assert_eq!(config.jwt_id, Some("bn-1".to_string())); - assert_eq!(config.jwt_version, Some("Lighthouse-v2.1.3".to_string())); + // Only first provided endpoint is parsed as we don't support + // redundancy. + assert_eq!(&config.builder_endpoints, &all_builders[..1]); }); } + +#[test] +fn payload_builder_flags() { + run_payload_builder_flag_test("payload-builder", "http://meow.cats"); + run_payload_builder_flag_test("payload-builders", "http://meow.cats,http://woof.dogs"); + run_payload_builder_flag_test("payload-builders", "http://meow.cats,http://woof.dogs"); +} + +fn run_jwt_optional_flags_test(jwt_flag: &str, jwt_id_flag: &str, jwt_version_flag: &str) { + use sensitive_url::SensitiveUrl; + + let dir = TempDir::new().expect("Unable to create temporary directory"); + let execution_endpoint = "http://meow.cats"; + let jwt_file = "jwt-file"; + let id = "bn-1"; + let version = "Lighthouse-v2.1.3"; + CommandLineTest::new() + .flag("execution-endpoint", Some(execution_endpoint.clone())) + .flag(jwt_flag, dir.path().join(jwt_file).as_os_str().to_str()) + .flag(jwt_id_flag, Some(id)) + .flag(jwt_version_flag, Some(version)) + .run_with_zero_port() + .with_config(|config| { + let el_config = config.execution_layer.as_ref().unwrap(); + assert_eq!(el_config.jwt_id, Some(id.to_string())); + assert_eq!(el_config.jwt_version, Some(version.to_string())); + assert_eq!( + config.eth1.endpoints, + Eth1Endpoint::Auth { + endpoint: SensitiveUrl::parse(execution_endpoint).unwrap(), + jwt_path: dir.path().join(jwt_file), + jwt_id: Some(id.to_string()), + jwt_version: Some(version.to_string()), + } + ); + }); +} +#[test] +fn jwt_optional_flags() { + run_jwt_optional_flags_test("execution-jwt", "execution-jwt-id", "execution-jwt-version"); +} +#[test] +fn jwt_optional_alias_flags() { + run_jwt_optional_flags_test("jwt-secrets", "jwt-id", "jwt-version"); +} #[test] fn terminal_total_difficulty_override_flag() { use beacon_node::beacon_chain::types::Uint256; diff --git a/scripts/local_testnet/ganache_test_node.sh b/scripts/local_testnet/ganache_test_node.sh index 7d97f2196a..a489c33224 100755 --- a/scripts/local_testnet/ganache_test_node.sh +++ b/scripts/local_testnet/ganache_test_node.sh @@ -11,5 +11,4 @@ exec ganache \ --mnemonic "$ETH1_NETWORK_MNEMONIC" \ --port 8545 \ --blockTime $SECONDS_PER_ETH1_BLOCK \ - --networkId "$NETWORK_ID" \ - --chain.chainId "$NETWORK_ID" + --chain.chainId "$CHAIN_ID" diff --git a/scripts/local_testnet/setup.sh b/scripts/local_testnet/setup.sh index 6f0b070915..a1348363a9 100755 --- a/scripts/local_testnet/setup.sh +++ b/scripts/local_testnet/setup.sh @@ -32,7 +32,7 @@ lcli \ --genesis-delay $GENESIS_DELAY \ --genesis-fork-version $GENESIS_FORK_VERSION \ --altair-fork-epoch $ALTAIR_FORK_EPOCH \ - --eth1-id $NETWORK_ID \ + --eth1-id $CHAIN_ID \ --eth1-follow-distance 1 \ --seconds-per-slot $SECONDS_PER_SLOT \ --seconds-per-eth1-block $SECONDS_PER_ETH1_BLOCK \ diff --git a/scripts/local_testnet/vars.env b/scripts/local_testnet/vars.env index 208fbb6d85..efb1046452 100644 --- a/scripts/local_testnet/vars.env +++ b/scripts/local_testnet/vars.env @@ -30,7 +30,7 @@ GENESIS_DELAY=0 BOOTNODE_PORT=4242 # Network ID and Chain ID of local eth1 test network -NETWORK_ID=4242 +CHAIN_ID=4242 # Hard fork configuration ALTAIR_FORK_EPOCH=18446744073709551615 diff --git a/scripts/tests/vars.env b/scripts/tests/vars.env index 6cc0dd3b8a..d51fe2aef2 100644 --- a/scripts/tests/vars.env +++ b/scripts/tests/vars.env @@ -30,7 +30,7 @@ GENESIS_DELAY=0 BOOTNODE_PORT=4242 # Network ID and Chain ID of local eth1 test network -NETWORK_ID=4242 +CHAIN_ID=4242 # Hard fork configuration ALTAIR_FORK_EPOCH=18446744073709551615 diff --git a/testing/eth1_test_rig/src/ganache.rs b/testing/eth1_test_rig/src/ganache.rs index c82277dc75..9b6a33ff59 100644 --- a/testing/eth1_test_rig/src/ganache.rs +++ b/testing/eth1_test_rig/src/ganache.rs @@ -16,17 +16,11 @@ pub struct GanacheInstance { pub port: u16, child: Child, pub web3: Web3, - network_id: u64, chain_id: u64, } impl GanacheInstance { - fn new_from_child( - mut child: Child, - port: u16, - network_id: u64, - chain_id: u64, - ) -> Result { + fn new_from_child(mut child: Child, port: u16, chain_id: u64) -> Result { let stdout = child .stdout .ok_or("Unable to get stdout for ganache child process")?; @@ -64,14 +58,13 @@ impl GanacheInstance { port, child, web3, - network_id, chain_id, }) } /// Start a new `ganache` process, waiting until it indicates that it is ready to accept /// RPC connections. - pub fn new(network_id: u64, chain_id: u64) -> Result { + pub fn new(chain_id: u64) -> Result { let port = unused_tcp_port()?; let binary = match cfg!(windows) { true => "ganache.cmd", @@ -89,8 +82,6 @@ impl GanacheInstance { .arg(format!("{}", port)) .arg("--mnemonic") .arg("\"vast thought differ pull jewel broom cook wrist tribe word before omit\"") - .arg("--networkId") - .arg(format!("{}", network_id)) .arg("--chain.chainId") .arg(format!("{}", chain_id)) .spawn() @@ -102,7 +93,7 @@ impl GanacheInstance { ) })?; - Self::new_from_child(child, port, network_id, chain_id) + Self::new_from_child(child, port, chain_id) } pub fn fork(&self) -> Result { @@ -128,7 +119,7 @@ impl GanacheInstance { ) })?; - Self::new_from_child(child, port, self.network_id, self.chain_id) + Self::new_from_child(child, port, self.chain_id) } /// Returns the endpoint that this instance is listening on. @@ -136,11 +127,6 @@ impl GanacheInstance { endpoint(self.port) } - /// Returns the network id of the ganache instance - pub fn network_id(&self) -> u64 { - self.network_id - } - /// Returns the chain id of the ganache instance pub fn chain_id(&self) -> u64 { self.chain_id diff --git a/testing/eth1_test_rig/src/lib.rs b/testing/eth1_test_rig/src/lib.rs index 52ae3922bc..42081a60e7 100644 --- a/testing/eth1_test_rig/src/lib.rs +++ b/testing/eth1_test_rig/src/lib.rs @@ -30,8 +30,8 @@ pub struct GanacheEth1Instance { } impl GanacheEth1Instance { - pub async fn new(network_id: u64, chain_id: u64) -> Result { - let ganache = GanacheInstance::new(network_id, chain_id)?; + pub async fn new(chain_id: u64) -> Result { + let ganache = GanacheInstance::new(chain_id)?; DepositContract::deploy(ganache.web3.clone(), 0, None) .await .map(|deposit_contract| Self { diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index 6770508435..a01c133fd9 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] node_test_rig = { path = "../node_test_rig" } eth1 = {path = "../../beacon_node/eth1"} +execution_layer = {path = "../../beacon_node/execution_layer"} types = { path = "../../consensus/types" } parking_lot = "0.12.0" futures = "0.3.7" diff --git a/testing/simulator/src/eth1_sim.rs b/testing/simulator/src/eth1_sim.rs index 80fc755d52..4c773c70bf 100644 --- a/testing/simulator/src/eth1_sim.rs +++ b/testing/simulator/src/eth1_sim.rs @@ -1,9 +1,10 @@ use crate::local_network::INVALID_ADDRESS; use crate::{checks, LocalNetwork, E}; use clap::ArgMatches; -use eth1::http::Eth1Id; -use eth1::{DEFAULT_CHAIN_ID, DEFAULT_NETWORK_ID}; +use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; use eth1_test_rig::GanacheEth1Instance; + +use execution_layer::http::deposit_methods::Eth1Id; use futures::prelude::*; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, @@ -92,10 +93,8 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { * Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit * validators. */ - let ganache_eth1_instance = - GanacheEth1Instance::new(DEFAULT_NETWORK_ID.into(), DEFAULT_CHAIN_ID.into()).await?; + let ganache_eth1_instance = GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?; let deposit_contract = ganache_eth1_instance.deposit_contract; - let network_id = ganache_eth1_instance.ganache.network_id(); let chain_id = ganache_eth1_instance.ganache.chain_id(); let ganache = ganache_eth1_instance.ganache; let eth1_endpoint = SensitiveUrl::parse(ganache.endpoint().as_str()) @@ -124,7 +123,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { let mut beacon_config = testing_client_config(); beacon_config.genesis = ClientGenesis::DepositContract; - beacon_config.eth1.endpoints = vec![eth1_endpoint]; + beacon_config.eth1.endpoints = Eth1Endpoint::NoAuth(vec![eth1_endpoint]); beacon_config.eth1.deposit_contract_address = deposit_contract_address; beacon_config.eth1.deposit_contract_deploy_block = 0; beacon_config.eth1.lowest_cached_block_number = 0; @@ -133,7 +132,6 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { beacon_config.dummy_eth1_backend = false; beacon_config.sync_eth1_chain = true; beacon_config.eth1.auto_update_interval_millis = eth1_block_time.as_millis() as u64; - beacon_config.eth1.network_id = Eth1Id::from(network_id); beacon_config.eth1.chain_id = Eth1Id::from(chain_id); beacon_config.network.target_peers = node_count - 1; @@ -150,10 +148,13 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { for i in 0..node_count - 1 { let mut config = beacon_config.clone(); if i % 2 == 0 { - config.eth1.endpoints.insert( - 0, - SensitiveUrl::parse(INVALID_ADDRESS).expect("Unable to parse invalid address"), - ); + if let Eth1Endpoint::NoAuth(endpoints) = &mut config.eth1.endpoints { + endpoints.insert( + 0, + SensitiveUrl::parse(INVALID_ADDRESS) + .expect("Unable to parse invalid address"), + ) + } } network.add_beacon_node(config).await?; }