From 4c71fab395e1c581b3a93ed9dca9e21093f2427b Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Thu, 30 Jan 2025 17:46:28 +0100 Subject: [PATCH 1/4] implement ssv_network_config # Conflicts: # Cargo.toml --- Cargo.lock | 12 ++- Cargo.toml | 4 + anchor/client/Cargo.toml | 2 +- anchor/client/src/config.rs | 34 ++++--- anchor/client/src/lib.rs | 2 +- anchor/common/ssv_network_config/Cargo.toml | 11 +++ .../holesky/ssv_boot_enr.yaml | 3 + .../holesky/ssv_contract_address.txt | 1 + .../holesky/ssv_contract_block.txt | 1 + .../mainnet/ssv_boot_enr.yaml | 9 ++ .../mainnet/ssv_contract_address.txt | 1 + .../mainnet/ssv_contract_block.txt | 1 + anchor/common/ssv_network_config/src/lib.rs | 89 +++++++++++++++++++ anchor/src/main.rs | 7 +- 14 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 anchor/common/ssv_network_config/Cargo.toml create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/holesky/ssv_boot_enr.yaml create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/holesky/ssv_contract_address.txt create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/holesky/ssv_contract_block.txt create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/mainnet/ssv_boot_enr.yaml create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/mainnet/ssv_contract_address.txt create mode 100644 anchor/common/ssv_network_config/built_in_network_configs/mainnet/ssv_contract_block.txt create mode 100644 anchor/common/ssv_network_config/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ecbc5068..3891d5e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1582,7 +1582,6 @@ dependencies = [ "eth", "eth2", "eth2_config", - "eth2_network_config", "ethereum_hashing", "fdlimit", "http_api", @@ -1598,6 +1597,7 @@ dependencies = [ "signature_collector", "slashing_protection", "slot_clock", + "ssv_network_config", "ssv_types", "strum 0.24.1", "task_executor", @@ -7109,6 +7109,16 @@ dependencies = [ "der", ] +[[package]] +name = "ssv_network_config" +version = "0.1.0" +dependencies = [ + "alloy", + "enr", + "eth2_network_config", + "serde_yaml", +] + [[package]] name = "ssv_types" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7edeab2c..fef606b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "anchor/client", "anchor/common/bls_lagrange", "anchor/common/qbft", + "anchor/common/ssv_network_config", "anchor/common/ssv_types", "anchor/common/version", "anchor/database", @@ -36,6 +37,7 @@ processor = { path = "anchor/processor" } qbft = { path = "anchor/common/qbft" } qbft_manager = { path = "anchor/qbft_manager" } signature_collector = { path = "anchor/signature_collector" } +ssv_network_config = { path = "anchor/common/ssv_network_config" } ssv_types = { path = "anchor/common/ssv_types" } beacon_node_fallback = { git = "https://github.com/sigp/lighthouse", rev = "1a77f7a0" } @@ -81,6 +83,7 @@ derive_more = { version = "1.0.0", features = ["full"] } dirs = "5.0.1" discv5 = "0.9.0" either = "1.13.0" +enr = "0.13.0" ethereum_ssz = "0.7" ethereum_ssz_derive = "0.7.0" futures = "0.3.30" @@ -94,6 +97,7 @@ rand = "0.8.5" reqwest = "0.12.12" rusqlite = "0.28.0" serde = { version = "1.0.208", features = ["derive"] } +serde_yaml = "0.9" sha2 = "0.10.8" strum = { version = "0.24", features = ["derive"] } tokio = { version = "1.39.2", features = [ diff --git a/anchor/client/Cargo.toml b/anchor/client/Cargo.toml index f73872cb..e660ad55 100644 --- a/anchor/client/Cargo.toml +++ b/anchor/client/Cargo.toml @@ -17,7 +17,6 @@ dirs = { workspace = true } eth = { workspace = true } eth2 = { workspace = true } eth2_config = { workspace = true } -eth2_network_config = { workspace = true } ethereum_hashing = "0.7.0" fdlimit = "0.3" http_api = { workspace = true } @@ -33,6 +32,7 @@ serde = { workspace = true } signature_collector = { workspace = true } slashing_protection = { workspace = true } slot_clock = { workspace = true } +ssv_network_config = { workspace = true } ssv_types = { workspace = true } strum = { workspace = true } task_executor = { workspace = true } diff --git a/anchor/client/src/config.rs b/anchor/client/src/config.rs index b759f198..cc6f8b89 100644 --- a/anchor/client/src/config.rs +++ b/anchor/client/src/config.rs @@ -2,13 +2,13 @@ // use clap_utils::{flags::DISABLE_MALLOC_TUNING_FLAG, parse_optional, parse_required}; use crate::cli::Anchor; -use eth2_network_config::Eth2NetworkConfig; use network::{ListenAddr, ListenAddress}; use sensitive_url::SensitiveUrl; +use ssv_network_config::SsvNetworkConfig; use std::fs; use std::net::IpAddr; use std::path::PathBuf; -use tracing::warn; +use tracing::{error, warn}; pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/"; pub const DEFAULT_EXECUTION_NODE: &str = "http://localhost:8545/"; @@ -25,8 +25,8 @@ pub const CUSTOM_TESTNET_DIR: &str = "custom"; pub struct Config { /// The data directory, which stores all validator databases pub data_dir: PathBuf, - /// The Eth2 Network to use - pub eth2_network: Eth2NetworkConfig, + /// The SSV Network to use + pub ssv_network: SsvNetworkConfig, /// The http endpoints of the beacon node APIs. /// /// Should be similar to `["http://localhost:8080"]` @@ -59,12 +59,13 @@ impl Config { /// Build a new configuration from defaults. /// /// eth2_network: We pass this because it would be expensive to uselessly get a default eagerly. - fn new(eth2_network: Eth2NetworkConfig) -> Self { + fn new(ssv_network: SsvNetworkConfig) -> Self { let data_dir = dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) .join(DEFAULT_ROOT_DIR) .join( - eth2_network + ssv_network + .eth2_network .config .config_name .as_deref() @@ -82,7 +83,7 @@ impl Config { Self { data_dir, - eth2_network, + ssv_network, beacon_nodes, proposer_nodes: vec![], execution_nodes, @@ -101,11 +102,10 @@ impl Config { /// Returns a `Default` implementation of `Self` with some parameters modified by the supplied /// `cli_args`. pub fn from_cli(cli_args: &Anchor) -> Result { - let eth2_network = if let Some(_testnet_dir) = &cli_args.testnet_dir { - // todo - return Err("testnet dir not yet supported".into()); + let eth2_network = if let Some(testnet_dir) = &cli_args.testnet_dir { + SsvNetworkConfig::load(testnet_dir.clone()) } else { - Eth2NetworkConfig::constant(&cli_args.network) + SsvNetworkConfig::constant(&cli_args.network) .and_then(|net| net.ok_or_else(|| format!("Unknown network {}", cli_args.network))) }?; @@ -144,7 +144,8 @@ pub fn from_cli(cli_args: &Anchor) -> Result { for addr in cli_args.boot_nodes_enr.clone() { match addr.parse() { Ok(enr) => config.network.boot_nodes_enr.push(enr), - Err(_) => { + Err(err) => { + error!(enr = addr, err, "Failed to parse boot node ENR, skipping"); // parsing as ENR failed, try as Multiaddr // let multi: Multiaddr = addr // .parse() @@ -159,6 +160,13 @@ pub fn from_cli(cli_args: &Anchor) -> Result { } } } + if cli_args.boot_nodes_enr.is_empty() { + config.network.boot_nodes_enr = config + .ssv_network + .ssv_boot_nodes + .clone() + .unwrap_or_default(); + } config.beacon_nodes_tls_certs = cli_args.beacon_nodes_tls_certs.clone(); config.execution_nodes_tls_certs = cli_args.execution_nodes_tls_certs.clone(); @@ -395,7 +403,7 @@ mod tests { // Ensures the default config does not panic. fn default_config() { Config::new( - Eth2NetworkConfig::constant(DEFAULT_HARDCODED_NETWORK) + SsvNetworkConfig::constant(DEFAULT_HARDCODED_NETWORK) .unwrap() .unwrap(), ); diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index 138859c8..f432c177 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -95,7 +95,7 @@ impl Client { "Starting the Anchor client" ); - let spec = Arc::new(config.eth2_network.chain_spec::()?); + let spec = Arc::new(config.ssv_network.eth2_network.chain_spec::()?); let key = read_or_generate_private_key(&config.data_dir.join("key.pem"))?; let err = |e| format!("Unable to derive public key: {e:?}"); diff --git a/anchor/common/ssv_network_config/Cargo.toml b/anchor/common/ssv_network_config/Cargo.toml new file mode 100644 index 00000000..e3d9a6c3 --- /dev/null +++ b/anchor/common/ssv_network_config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ssv_network_config" +version = "0.1.0" +authors = ["Sigma Prime { + include_str!(concat!( + "../built_in_network_configs/", + stringify!($network), + "/", + $file + )) + }; +} + +macro_rules! get_hardcoded { + ($network:ident) => { + ( + include_str_for_net!($network, "ssv_boot_enr.yaml"), + include_str_for_net!($network, "ssv_contract_address.txt"), + include_str_for_net!($network, "ssv_contract_block.txt"), + ) + }; +} + +#[derive(Clone, Debug)] +pub struct SsvNetworkConfig { + pub eth2_network: Eth2NetworkConfig, + pub ssv_boot_nodes: Option>>, + pub ssv_contract: Address, + pub ssv_contract_block: u64, +} + +impl SsvNetworkConfig { + pub fn constant(name: &str) -> Result, String> { + let (enr_yaml, address, block) = match name { + "mainnet" => get_hardcoded!(mainnet), + "holesky" => get_hardcoded!(holesky), + _ => return Ok(None), + }; + let Some(eth2_network) = Eth2NetworkConfig::constant(name)? else { + return Ok(None); + }; + Ok(Some(Self { + eth2_network, + ssv_boot_nodes: Some( + serde_yaml::from_str(enr_yaml).map_err(|_| "Unable to parse built-in yaml!")?, + ), + ssv_contract: address + .parse() + .map_err(|_| "Unable to parse built-in address!")?, + ssv_contract_block: block + .parse() + .map_err(|_| "Unable to parse built-in block!")?, + })) + } + + pub fn load(base_dir: PathBuf) -> Result { + let ssv_boot_nodes_path = base_dir.join("ssv_boot_enr.yaml"); + let ssv_boot_nodes = ssv_boot_nodes_path + .exists() + .then(|| { + File::open(&ssv_boot_nodes_path) + .map_err(|e| format!("Unable to read {ssv_boot_nodes_path:?}: {e}")) + .and_then(|f| { + serde_yaml::from_reader(f) + .map_err(|e| format!("Unable to parse {ssv_boot_nodes_path:?}: {e}")) + }) + }) + .transpose()?; + + Ok(Self { + ssv_boot_nodes, + ssv_contract: read(&base_dir.join("ssv_contract_address.txt"))?, + ssv_contract_block: read(&base_dir.join("ssv_contract_block.txt"))?, + eth2_network: Eth2NetworkConfig::load(base_dir)?, + }) + } +} + +fn read(file: &Path) -> Result { + std::fs::read_to_string(file) + .map_err(|e| format!("Unable to read {file:?}: {e}"))? + .parse() + .map_err(|_| format!("Unable to parse {file:?}")) +} diff --git a/anchor/src/main.rs b/anchor/src/main.rs index 11a497cc..dd4cb826 100644 --- a/anchor/src/main.rs +++ b/anchor/src/main.rs @@ -13,6 +13,9 @@ fn main() { std::env::set_var("RUST_BACKTRACE", "1"); } + // Construct the logging, task executor and exit signals + let mut environment = Environment::default(); + // Obtain the CLI and build the config let anchor_config: Anchor = Anchor::parse(); @@ -27,8 +30,6 @@ fn main() { } }; - // Construct the task executor and exit signals - let mut environment = Environment::default(); // Build the core task executor let core_executor = environment.executor(); @@ -37,7 +38,7 @@ fn main() { let anchor_executor = core_executor.clone(); let shutdown_executor = core_executor.clone(); - let eth_spec_id = match config.eth2_network.eth_spec_id() { + let eth_spec_id = match config.ssv_network.eth2_network.eth_spec_id() { Ok(eth_spec_id) => eth_spec_id, Err(e) => { error!(e, "Unable to get eth spec id"); From c5ffb7d2cd2da8c0b83b086b5319848735f0ce64 Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Mon, 3 Feb 2025 13:37:19 +0100 Subject: [PATCH 2/4] use ssv_network_config in sync --- Cargo.lock | 1 + anchor/client/src/lib.rs | 6 +----- anchor/eth/Cargo.toml | 1 + anchor/eth/execution.rs | 5 +++-- anchor/eth/src/lib.rs | 2 +- anchor/eth/src/sync.rs | 37 ++++++------------------------------- 6 files changed, 13 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3891d5e5..c189ab71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2462,6 +2462,7 @@ dependencies = [ "rand", "reqwest 0.12.12", "serde", + "ssv_network_config", "ssv_types", "tokio", "tracing", diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index f432c177..8c3aa566 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -319,11 +319,7 @@ impl Client { .full .to_string(), beacon_url: "".to_string(), // this one is not actually needed :) - network: match spec.config_name.as_deref() { - Some("mainnet") => eth::Network::Mainnet, - Some("holesky") => eth::Network::Holesky, - _ => return Err(format!("Unsupported network {:?}", spec.config_name)), - }, + network: config.ssv_network, historic_finished_notify: Some(historic_finished_tx), }, ) diff --git a/anchor/eth/Cargo.toml b/anchor/eth/Cargo.toml index 5c9c2ab6..553099e9 100644 --- a/anchor/eth/Cargo.toml +++ b/anchor/eth/Cargo.toml @@ -18,6 +18,7 @@ openssl = { workspace = true } rand = "0.8.5" reqwest = { workspace = true } serde = { workspace = true } +ssv_network_config = { workspace = true } ssv_types = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/anchor/eth/execution.rs b/anchor/eth/execution.rs index 6ce6a8e4..ccc8ed88 100644 --- a/anchor/eth/execution.rs +++ b/anchor/eth/execution.rs @@ -1,7 +1,8 @@ use base64::prelude::*; use database::NetworkDatabase; -use eth::{Config, Network, SsvEventSyncer}; +use eth::{Config, SsvEventSyncer}; use openssl::rsa::Rsa; +use ssv_network_config::SsvNetworkConfig; use std::path::Path; use std::sync::Arc; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; @@ -27,7 +28,7 @@ async fn main() { http_url: String::from(rpc_endpoint), ws_url: String::from(ws_endpoint), beacon_url: String::from(beacon_endpoint), - network: Network::Mainnet, + network: SsvNetworkConfig::constant("mainnet").unwrap().unwrap(), historic_finished_notify: None, }; diff --git a/anchor/eth/src/lib.rs b/anchor/eth/src/lib.rs index f8df5fab..d698f54f 100644 --- a/anchor/eth/src/lib.rs +++ b/anchor/eth/src/lib.rs @@ -1,4 +1,4 @@ -pub use sync::{Config, Network, SsvEventSyncer}; +pub use sync::{Config, SsvEventSyncer}; mod error; mod event_parser; mod event_processor; diff --git a/anchor/eth/src/sync.rs b/anchor/eth/src/sync.rs index 60c8b5b5..ba47ab3e 100644 --- a/anchor/eth/src/sync.rs +++ b/anchor/eth/src/sync.rs @@ -1,7 +1,7 @@ use crate::error::ExecutionError; use crate::event_processor::EventProcessor; use crate::gen::SSVContract; -use alloy::primitives::{address, Address}; +use alloy::primitives::Address; use alloy::providers::{Provider, ProviderBuilder, RootProvider, WsConnect}; use alloy::pubsub::PubSubFrontend; use alloy::rpc::types::{Filter, Log}; @@ -11,6 +11,7 @@ use database::NetworkDatabase; use futures::future::{try_join_all, Future}; use futures::StreamExt; use rand::Rng; +use ssv_network_config::SsvNetworkConfig; use std::collections::BTreeMap; use std::sync::{Arc, LazyLock}; use tokio::sync::oneshot::Sender; @@ -39,23 +40,6 @@ static SSV_EVENTS: LazyLock> = LazyLock::new(|| { ] }); -/// Contract deployment addresses -/// Mainnet: https://etherscan.io/address/0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1 -static MAINNET_DEPLOYMENT_ADDRESS: LazyLock
= - LazyLock::new(|| address!("DD9BC35aE942eF0cFa76930954a156B3fF30a4E1")); - -/// Holesky: https://holesky.etherscan.io/address/0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA -static HOLESKY_DEPLOYMENT_ADDRESS: LazyLock
= - LazyLock::new(|| address!("38A4794cCEd47d3baf7370CcC43B560D3a1beEFA")); - -/// Contract deployment block on Ethereum Mainnet -/// Mainnet: https://etherscan.io/tx/0x4a11a560d3c2f693e96f98abb1feb447646b01b36203ecab0a96a1cf45fd650b -const MAINNET_DEPLOYMENT_BLOCK: u64 = 17507487; - -/// Contract deployment block on the Holesky Network -/// Holesky: https://holesky.etherscan.io/tx/0x998c38ff37b47e69e23c21a8079168b7e0e0ade7244781587b00be3f08a725c6 -const HOLESKY_DEPLOYMENT_BLOCK: u64 = 181612; - /// Batch size for log fetching const BATCH_SIZE: u64 = 10000; @@ -76,20 +60,13 @@ const FOLLOW_DISTANCE: u64 = 8; /// https://github.com/ssvlabs/ssv/blob/07095fe31e3ded288af722a9c521117980585d95/eth/eventhandler/validation.go#L15 pub const MAX_OPERATORS: usize = 13; -/// Network that is being connected to -#[derive(Debug)] -pub enum Network { - Mainnet, - Holesky, -} - // TODO!() Dummy config struct that will be replaced #[derive(Debug)] pub struct Config { pub http_url: String, pub ws_url: String, pub beacon_url: String, - pub network: Network, + pub network: SsvNetworkConfig, pub historic_finished_notify: Option>, } @@ -107,7 +84,7 @@ pub struct SsvEventSyncer { /// Event processor for logs event_processor: EventProcessor, /// The network the node is connected to - network: Network, + network: SsvNetworkConfig, /// Notify a channel as soon as the historical sync is done historic_finished_notify: Option>, } @@ -154,10 +131,8 @@ impl SsvEventSyncer { info!("Starting SSV event sync"); // Get network specific contract information - let (contract_address, deployment_block) = match self.network { - Network::Mainnet => (*MAINNET_DEPLOYMENT_ADDRESS, MAINNET_DEPLOYMENT_BLOCK), - Network::Holesky => (*HOLESKY_DEPLOYMENT_ADDRESS, HOLESKY_DEPLOYMENT_BLOCK), - }; + let contract_address = self.network.ssv_contract; + let deployment_block = self.network.ssv_contract_block; info!( ?contract_address, From 240c8b107caad8ff9dbcaee7480a37fc9458f6f1 Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Mon, 3 Feb 2025 13:41:19 +0100 Subject: [PATCH 3/4] add tests --- anchor/common/ssv_network_config/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/anchor/common/ssv_network_config/src/lib.rs b/anchor/common/ssv_network_config/src/lib.rs index bcd754c1..7ef0463a 100644 --- a/anchor/common/ssv_network_config/src/lib.rs +++ b/anchor/common/ssv_network_config/src/lib.rs @@ -87,3 +87,18 @@ fn read(file: &Path) -> Result { .parse() .map_err(|_| format!("Unable to parse {file:?}")) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_holesky() { + SsvNetworkConfig::constant("holesky").unwrap().unwrap(); + } + + #[test] + fn test_mainnet() { + SsvNetworkConfig::constant("mainnet").unwrap().unwrap(); + } +} From 1455624691af46aab1871ca0162e301cacf075b1 Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Wed, 5 Feb 2025 14:45:30 +0100 Subject: [PATCH 4/4] adjust comment --- anchor/client/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anchor/client/src/config.rs b/anchor/client/src/config.rs index cc6f8b89..29c81163 100644 --- a/anchor/client/src/config.rs +++ b/anchor/client/src/config.rs @@ -58,7 +58,7 @@ pub struct Config { impl Config { /// Build a new configuration from defaults. /// - /// eth2_network: We pass this because it would be expensive to uselessly get a default eagerly. + /// ssv_network: We pass this because it would be expensive to uselessly get a default eagerly. fn new(ssv_network: SsvNetworkConfig) -> Self { let data_dir = dirs::home_dir() .unwrap_or_else(|| PathBuf::from("."))