From 76e01341cc094872218a6cfe7c0d840fb2b043c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20I=C3=B1aki=20Bilbao?= Date: Thu, 9 Jan 2025 11:45:13 -0300 Subject: [PATCH] feat(mux): support SSV network (#223) Co-authored-by: ltitanb <163874448+ltitanb@users.noreply.github.com> --- config.example.toml | 2 + crates/common/src/config/mux.rs | 80 +++++++++++++++++++++++++++++++++ examples/configs/pbs_mux.toml | 10 ++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/config.example.toml b/config.example.toml index caffd1ea..253d35d1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -114,9 +114,11 @@ validator_pubkeys = [ # Path to a file containing a list of validator pubkeys or details of a registry to load keys from. # Supported registries: # - Lido: NodeOperatorsRegistry +# - SSV: SSV API # OPTIONAL loader = "./tests/data/mux_keys.example.json" # loader = { registry = "lido", node_operator_id = 8 } +# loader = { registry = "ssv", node_operator_id = 8 } timeout_get_header_ms = 900 late_in_slot_time_ms = 1500 # For each mux, one or more [[mux.relays]] can be defined, which will be used for the matching validator pubkeys diff --git a/crates/common/src/config/mux.rs b/crates/common/src/config/mux.rs index 7d06067d..32ff8e97 100644 --- a/crates/common/src/config/mux.rs +++ b/crates/common/src/config/mux.rs @@ -149,6 +149,8 @@ pub enum MuxKeysLoader { pub enum NORegistry { #[serde(alias = "lido")] Lido, + #[serde(alias = "ssv")] + SSV, } impl MuxKeysLoader { @@ -176,6 +178,7 @@ impl MuxKeysLoader { fetch_lido_registry_keys(rpc_url, chain, U256::from(*node_operator_id)).await } + NORegistry::SSV => fetch_ssv_pubkeys(chain, U256::from(*node_operator_id)).await, }, } } @@ -266,6 +269,71 @@ async fn fetch_lido_registry_keys( Ok(keys) } +async fn fetch_ssv_pubkeys( + chain: Chain, + node_operator_id: U256, +) -> eyre::Result> { + const MAX_PER_PAGE: usize = 100; + + let chain_name = match chain { + Chain::Mainnet => "mainnet", + Chain::Holesky => "holesky", + _ => bail!("SSV network is not supported for chain: {chain:?}"), + }; + + let client = reqwest::Client::new(); + let mut pubkeys: Vec = vec![]; + let mut page = 1; + + loop { + let response = client + .get(format!( + "https://api.ssv.network/api/v4/{}/validators/in_operator/{}?perPage={}&page={}", + chain_name, node_operator_id, MAX_PER_PAGE, page + )) + .send() + .await + .map_err(|e| eyre::eyre!("Error sending request to SSV network API: {e}"))? + .json::() + .await?; + + pubkeys.extend(response.validators.iter().map(|v| v.pubkey).collect::>()); + page += 1; + + if response.validators.len() < MAX_PER_PAGE { + ensure!( + pubkeys.len() == response.pagination.total, + "expected {} keys, got {}", + response.pagination.total, + pubkeys.len() + ); + break; + } + } + + let unique = pubkeys.iter().collect::>(); + ensure!(unique.len() == pubkeys.len(), "found duplicate keys in registry"); + + Ok(pubkeys) +} + +#[derive(Deserialize)] +struct SSVResponse { + validators: Vec, + pagination: SSVPagination, +} + +#[derive(Deserialize)] +struct SSVValidator { + #[serde(rename = "public_key")] + pubkey: BlsPublicKey, +} + +#[derive(Deserialize)] +struct SSVPagination { + total: usize, +} + #[cfg(test)] mod tests { use alloy::{primitives::U256, providers::ProviderBuilder}; @@ -304,4 +372,16 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_ssv_network_fetch() -> eyre::Result<()> { + let chain = Chain::Holesky; + let node_operator_id = U256::from(200); + + let pubkeys = fetch_ssv_pubkeys(chain, node_operator_id).await?; + + assert_eq!(pubkeys.len(), 3); + + Ok(()) + } } diff --git a/examples/configs/pbs_mux.toml b/examples/configs/pbs_mux.toml index 7cb5662e..3ea9f355 100644 --- a/examples/configs/pbs_mux.toml +++ b/examples/configs/pbs_mux.toml @@ -19,7 +19,7 @@ id = "timing-mux" validator_pubkeys = [ "0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745", ] -loader = "./mux_keys.example.json" +loader = "./tests/data/mux_keys.example.json" timeout_get_header_ms = 900 late_in_slot_time_ms = 1500 @@ -38,3 +38,11 @@ loader = { registry = "lido", node_operator_id = 8 } [[mux.relays]] id = "relay-3" url = "http://0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745@fgh.xyz" + +[[mux]] +id = "ssv-mux" +loader = { registry = "ssv", node_operator_id = 200 } + +[[mux.relays]] +id = "relay-4" +url = "http://0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745@fgh.xyz"