diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 1265276376..0ad4b5f8b2 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -282,6 +282,9 @@ pub enum BlockError { /// problems to worry about than losing peers, and we're doing the network a favour by /// disconnecting. ParentExecutionPayloadInvalid { parent_root: Hash256 }, + /// This is a known invalid block that was listed in Lighthouses configuration. + /// At the moment this error is only relevant as part of the Holesky network recovery efforts. + KnownInvalidExecutionPayload(Hash256), /// The block is a slashable equivocation from the proposer. /// /// ## Peer scoring @@ -1326,6 +1329,12 @@ impl ExecutionPendingBlock { chain: &Arc>, notify_execution_layer: NotifyExecutionLayer, ) -> Result { + if chain.config.invalid_block_roots.contains(&block_root) + && chain.spec.deposit_chain_id == 17000 + { + return Err(BlockError::KnownInvalidExecutionPayload(block_root)); + } + chain .observed_slashable .write() diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index fcdd57abbc..1f2b0478a9 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -1,7 +1,7 @@ pub use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use serde::{Deserialize, Serialize}; -use std::time::Duration; -use types::{Checkpoint, Epoch}; +use std::{collections::HashSet, time::Duration}; +use types::{Checkpoint, Epoch, Hash256}; pub const DEFAULT_RE_ORG_HEAD_THRESHOLD: ReOrgThreshold = ReOrgThreshold(20); pub const DEFAULT_RE_ORG_PARENT_THRESHOLD: ReOrgThreshold = ReOrgThreshold(160); @@ -94,6 +94,7 @@ pub struct ChainConfig { /// The delay in milliseconds applied by the node between sending each blob or data column batch. /// This doesn't apply if the node is the block proposer. pub blob_publication_batch_interval: Duration, + pub invalid_block_roots: HashSet, } impl Default for ChainConfig { @@ -129,6 +130,7 @@ impl Default for ChainConfig { enable_sampling: false, blob_publication_batches: 4, blob_publication_batch_interval: Duration::from_millis(300), + invalid_block_roots: HashSet::new(), } } } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 090b963cbc..0956c153a6 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -1457,6 +1457,7 @@ impl NetworkBeaconProcessor { | Err(e @ BlockError::InconsistentFork(_)) | Err(e @ BlockError::ExecutionPayloadError(_)) | Err(e @ BlockError::ParentExecutionPayloadInvalid { .. }) + | Err(e @ BlockError::KnownInvalidExecutionPayload(_)) | Err(e @ BlockError::GenesisBlock) => { warn!(self.log, "Could not verify block for gossip. Rejecting the block"; "error" => %e); diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index 338f2bc4c8..ef4d9cc4c1 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -800,6 +800,18 @@ impl NetworkBeaconProcessor { peer_action: Some(PeerAction::LowToleranceError), }) } + // Penalise peers for sending us banned blocks. + BlockError::KnownInvalidExecutionPayload(block_root) => { + warn!( + self.log, + "Received block known to be invalid"; + "block_root" => ?block_root, + ); + Err(ChainSegmentFailed { + message: format!("Banned block: {block_root:?}"), + peer_action: Some(PeerAction::LowToleranceError), + }) + } other => { debug!( self.log, "Invalid block received"; diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 2c8b271bd2..23c8f91bdc 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -1608,5 +1608,13 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("invalid-block-roots") + .long("invalid-block-roots") + .value_name("FILE") + .help("Path to a comma separated file containing block roots that should be treated as invalid during block verification.") + .action(ArgAction::Set) + .hide(true) + ) .group(ArgGroup::new("enable_http").args(["http", "gui", "staking"]).multiple(true)) } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 84320762d6..45092b31e8 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -20,9 +20,10 @@ use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, Pee use sensitive_url::SensitiveUrl; use slog::{info, warn, Logger}; use std::cmp::max; +use std::collections::HashSet; use std::fmt::Debug; use std::fs; -use std::io::IsTerminal; +use std::io::{IsTerminal, Read}; use std::net::Ipv6Addr; use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs}; use std::num::NonZeroU16; @@ -897,6 +898,40 @@ pub fn get_config( .max_gossip_aggregate_batch_size = clap_utils::parse_required(cli_args, "beacon-processor-aggregate-batch-size")?; + if let Some(invalid_block_roots_file_path) = + clap_utils::parse_optional::(cli_args, "invalid-block-roots")? + { + let mut file = std::fs::File::open(invalid_block_roots_file_path) + .map_err(|e| format!("Failed to open invalid-block-roots file: {}", e))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|e| format!("Failed to read invalid-block-roots file {}", e))?; + let mut invalid_block_roots: HashSet = contents + .split(',') + .filter_map( + |s| match Hash256::from_str(s.strip_prefix("0x").unwrap_or(s).trim()) { + Ok(block_root) => Some(block_root), + Err(e) => { + warn!( + log, + "Unable to parse invalid block root"; + "block_root" => s, + "error" => ?e, + ); + None + } + }, + ) + .collect(); + // Hardcode the known bad holesky block + if let Ok(invalid_block_root) = + Hash256::from_str("2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359") + { + invalid_block_roots.insert(invalid_block_root); + } + client_config.chain.invalid_block_roots = invalid_block_roots; + } + Ok(client_config) } diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index da10c2c4bd..4555273bfd 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2749,3 +2749,19 @@ fn beacon_node_backend_override() { assert_eq!(config.store.backend, BeaconNodeBackend::LevelDb); }); } + +#[test] +fn invalid_block_root_flag() { + let dir = TempDir::new().expect("Unable to create temporary directory"); + let mut file = + File::create(dir.path().join("invalid-block-roots")).expect("Unable to create file"); + file.write_all(b"2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359, 2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f358, 0x3db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f358") + .expect("Unable to write to file"); + CommandLineTest::new() + .flag( + "invalid-block-roots", + dir.path().join("invalid-block-roots").as_os_str().to_str(), + ) + .run_with_zero_port() + .with_config(|config| assert_eq!(config.chain.invalid_block_roots.len(), 3)) +}