Eth1 network exit on wrong network id (#1563)

## Issue Addressed

Fixes #1509 

## Proposed Changes

Exit the beacon node if the eth1 endpoint points to an invalid eth1 network. Check the network id before every eth1 cache update and display an error log if the network id has changed to an invalid one.
This commit is contained in:
Pawan Dhananjay 2020-08-31 02:36:17 +00:00
parent c18d37c202
commit adea7992f8
8 changed files with 97 additions and 3 deletions

1
Cargo.lock generated
View File

@ -4782,6 +4782,7 @@ version = "0.2.0"
dependencies = [
"clap",
"env_logger",
"eth1",
"eth1_test_rig",
"futures 0.3.5",
"node_test_rig",

View File

@ -584,7 +584,7 @@ where
/// Specifies that the `BeaconChain` should cache eth1 blocks/logs from a remote eth1 node
/// (e.g., Parity/Geth) and refer to that cache when collecting deposits or eth1 votes during
/// block production.
pub fn caching_eth1_backend(mut self, config: Eth1Config) -> Result<Self, String> {
pub async fn caching_eth1_backend(mut self, config: Eth1Config) -> Result<Self, String> {
let context = self
.runtime_context
.as_ref()
@ -598,6 +598,17 @@ where
.clone()
.ok_or_else(|| "caching_eth1_backend requires a chain spec".to_string())?;
// Check if the eth1 endpoint we connect to is on the correct network id.
let network_id =
eth1::http::get_network_id(&config.endpoint, Duration::from_millis(15_000)).await?;
if network_id != config.network_id {
return Err(format!(
"Invalid eth1 network id. Expected {:?}, got {:?}",
config.network_id, network_id
));
}
let backend = if let Some(eth1_service_from_genesis) = self.eth1_service {
eth1_service_from_genesis.update_config(config)?;

View File

@ -12,8 +12,10 @@
use futures::future::TryFutureExt;
use reqwest::{header::CONTENT_TYPE, ClientBuilder, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::ops::Range;
use std::str::FromStr;
use std::time::Duration;
use types::Hash256;
@ -30,6 +32,40 @@ 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;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Eth1NetworkId {
Goerli,
Mainnet,
Custom(u64),
}
impl FromStr for Eth1NetworkId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1" => Ok(Eth1NetworkId::Mainnet),
"5" => Ok(Eth1NetworkId::Goerli),
custom => {
let network_id = u64::from_str_radix(custom, 10)
.map_err(|e| format!("Failed to parse eth1 network id {}", e))?;
Ok(Eth1NetworkId::Custom(network_id))
}
}
}
}
/// Get the eth1 network id of the given endpoint.
pub async fn get_network_id(endpoint: &str, timeout: Duration) -> Result<Eth1NetworkId, String> {
let response_body = send_rpc_request(endpoint, "net_version", json!([]), timeout).await?;
Eth1NetworkId::from_str(
response_result(&response_body)?
.ok_or_else(|| "No result was returned for block number".to_string())?
.as_str()
.ok_or_else(|| "Data was not string")?,
)
}
#[derive(Debug, PartialEq, Clone)]
pub struct Block {
pub hash: Hash256,

View File

@ -2,7 +2,9 @@ use crate::metrics;
use crate::{
block_cache::{BlockCache, Error as BlockCacheError, Eth1Block},
deposit_cache::Error as DepositCacheError,
http::{get_block, get_block_number, get_deposit_logs_in_range, Log},
http::{
get_block, get_block_number, get_deposit_logs_in_range, get_network_id, Eth1NetworkId, Log,
},
inner::{DepositUpdater, Inner},
DepositLog,
};
@ -16,6 +18,9 @@ use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::{interval_at, Duration, Instant};
use types::ChainSpec;
/// Indicates the default eth1 network we use for the deposit contract.
pub const DEFAULT_NETWORK_ID: Eth1NetworkId = Eth1NetworkId::Goerli;
const STANDARD_TIMEOUT_MILLIS: u64 = 15_000;
/// Timeout when doing a eth_blockNumber call.
@ -76,6 +81,8 @@ pub struct Config {
pub endpoint: String,
/// 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: Eth1NetworkId,
/// Defines the first block that the `DepositCache` will start searching for deposit logs.
///
/// Setting too high can result in missed logs. Setting too low will result in unnecessary
@ -105,6 +112,7 @@ impl Default for Config {
Self {
endpoint: "http://localhost:8545".into(),
deposit_contract_address: "0x0000000000000000000000000000000000000000".into(),
network_id: DEFAULT_NETWORK_ID,
deposit_contract_deploy_block: 1,
lowest_cached_block_number: 1,
follow_distance: 128,
@ -350,6 +358,29 @@ impl Service {
}
async fn do_update(&self, update_interval: Duration) -> Result<(), ()> {
let endpoint = self.config().endpoint.clone();
let config_network = self.config().network_id.clone();
let result =
get_network_id(&endpoint, Duration::from_millis(STANDARD_TIMEOUT_MILLIS)).await;
match result {
Ok(network_id) => {
if network_id != config_network {
error!(
self.log,
"Failed to update eth1 cache";
"reason" => "Invalid eth1 network id",
"expected" => format!("{:?}",DEFAULT_NETWORK_ID),
"got" => format!("{:?}",network_id),
);
return Ok(());
}
}
Err(e) => {
error!(self.log, "Failed to get eth1 network id"; "error" => e);
return Ok(());
}
}
let update_result = self.update().await;
match update_result {
Err(e) => error!(

View File

@ -100,7 +100,9 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
"endpoint" => &client_config.eth1.endpoint,
"method" => "json rpc via http"
);
builder.caching_eth1_backend(client_config.eth1.clone())?
builder
.caching_eth1_backend(client_config.eth1.clone())
.await?
} else if client_config.dummy_eth1_backend {
warn!(
log,

View File

@ -14,6 +14,8 @@ use web3::{
/// How long we will wait for ganache to indicate that it is ready.
const GANACHE_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
const NETWORK_ID: u64 = 42;
/// Provides a dedicated `ganachi-cli` instance with a connected `Web3` instance.
///
/// Requires that `ganachi-cli` is installed and available on `PATH`.
@ -42,6 +44,8 @@ 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))
.spawn()
.map_err(|e| {
format!(
@ -97,6 +101,11 @@ impl GanacheInstance {
endpoint(self.port)
}
/// Returns the network id of the ganache instance
pub fn network_id(&self) -> u64 {
NETWORK_ID
}
/// Increase the timestamp on future blocks by `increase_by` seconds.
pub async fn increase_time(&self, increase_by: u64) -> Result<(), String> {
self.web3

View File

@ -8,6 +8,7 @@ edition = "2018"
[dependencies]
node_test_rig = { path = "../node_test_rig" }
eth1 = {path = "../../beacon_node/eth1"}
types = { path = "../../consensus/types" }
validator_client = { path = "../../validator_client" }
parking_lot = "0.11.0"

View File

@ -1,5 +1,6 @@
use crate::{checks, LocalNetwork, E};
use clap::ArgMatches;
use eth1::http::Eth1NetworkId;
use eth1_test_rig::GanacheEth1Instance;
use futures::prelude::*;
use node_test_rig::{
@ -73,6 +74,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
*/
let ganache_eth1_instance = GanacheEth1Instance::new().await?;
let deposit_contract = ganache_eth1_instance.deposit_contract;
let network_id = ganache_eth1_instance.ganache.network_id();
let ganache = ganache_eth1_instance.ganache;
let eth1_endpoint = ganache.endpoint();
let deposit_contract_address = deposit_contract.address();
@ -105,6 +107,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
beacon_config.eth1.follow_distance = 1;
beacon_config.dummy_eth1_backend = false;
beacon_config.sync_eth1_chain = true;
beacon_config.eth1.network_id = Eth1NetworkId::Custom(network_id);
beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));