From f47208cc8ad6723f371e3d5e9126ad0fcf3f3150 Mon Sep 17 00:00:00 2001 From: ThreeHrSleep Date: Wed, 16 Apr 2025 19:03:28 +0530 Subject: [PATCH 1/3] add validators endpoint --- Cargo.lock | 56 ++++++++++++++++++------------ anchor/client/src/lib.rs | 18 +++++++--- anchor/common/api_types/src/lib.rs | 9 +++++ anchor/http_api/Cargo.toml | 12 +++++++ anchor/http_api/src/lib.rs | 13 +++++-- anchor/http_api/src/router.rs | 35 +++++++++++++++++-- 6 files changed, 111 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 831f017b8..2f514eac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -834,9 +834,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "api_types" @@ -1706,9 +1706,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -1788,9 +1788,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", "clap_derive", @@ -1798,9 +1798,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", @@ -2321,15 +2321,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-encoding-macro" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2337,9 +2337,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", "syn 2.0.100", @@ -3459,9 +3459,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", @@ -3758,13 +3758,25 @@ dependencies = [ name = "http_api" version = "0.1.0" dependencies = [ + "anchor_validator_store", "api_types", "axum", + "database", + "eth2", + "hex", + "http_metrics", + "message_receiver", + "network", + "parking_lot", "serde", + "serde_json", "slot_clock", + "ssv_types", "task_executor", "tokio", "tracing", + "types", + "validator_services", "version", ] @@ -3832,7 +3844,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.8", + "h2 0.4.9", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -4413,9 +4425,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" @@ -6233,9 +6245,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -6766,7 +6778,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.8", + "h2 0.4.9", "http 1.3.1", "http-body 1.0.1", "http-body-util", diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index 261be321e..e5aace767 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -152,10 +152,19 @@ impl Client { }; // Optionally run the http_api server - if let Err(error) = http_api::run(config.http_api).await { - error!(error, "Failed to run HTTP API"); - return Err("HTTP API Failed".to_string()); - } + let http_api_shared_state = Arc::new(RwLock::new(http_api::Shared { + database_state: None, + })); + let state = http_api_shared_state.clone(); + + executor.spawn( + async { + if let Err(error) = http_api::run(config.http_api, state).await { + error!(error, "Failed to run HTTP API"); + } + }, + "http-api-server", + ); // Open database let database = Arc::new( @@ -542,6 +551,7 @@ impl Client { .start_update_service(&spec) .map_err(|e| format!("Unable to start preparation service: {}", e))?; + http_api_shared_state.write().database_state = Some(database.watch()); // TODO: reuse this from lighthouse as soon as tracing is merged // spawn_notifier(self).map_err(|e| format!("Failed to start notifier: {}", e))?; // diff --git a/anchor/common/api_types/src/lib.rs b/anchor/common/api_types/src/lib.rs index 347591724..115cbee35 100644 --- a/anchor/common/api_types/src/lib.rs +++ b/anchor/common/api_types/src/lib.rs @@ -4,6 +4,15 @@ use serde::Serialize; pub struct VersionData { pub version: String, } + +#[derive(Serialize)] +pub struct ValidatorData { + pub public_key: String, + pub cluster_id: String, + pub index: Option, + pub graffiti: String, +} + #[derive(Serialize)] pub struct GenericResponse { pub data: T, diff --git a/anchor/http_api/Cargo.toml b/anchor/http_api/Cargo.toml index cfac55234..f602be507 100644 --- a/anchor/http_api/Cargo.toml +++ b/anchor/http_api/Cargo.toml @@ -9,11 +9,23 @@ name = "http_api" path = "src/lib.rs" [dependencies] +anchor_validator_store = { workspace = true } api_types = { workspace = true } axum = { workspace = true } +database = { workspace = true } +eth2 = { workspace = true } +hex = { workspace = true } +http_metrics = { workspace = true } +message_receiver = { workspace = true } +network = { workspace = true } +parking_lot = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } slot_clock = { workspace = true } +ssv_types = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +types = { workspace = true } +validator_services = { workspace = true } version = { workspace = true } diff --git a/anchor/http_api/src/lib.rs b/anchor/http_api/src/lib.rs index 3c4f85716..5854996aa 100644 --- a/anchor/http_api/src/lib.rs +++ b/anchor/http_api/src/lib.rs @@ -4,14 +4,21 @@ mod router; use std::{net::SocketAddr, path::PathBuf}; pub use config::Config; +use database::NetworkState; +use parking_lot::RwLock; use slot_clock::SlotClock; +use std::sync::Arc; use task_executor::TaskExecutor; use tokio::net::TcpListener; +use tokio::sync::watch; use tracing::info; - /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. + +pub struct Shared { + pub database_state: Option>, +} pub struct Context { pub task_executor: TaskExecutor, // TODO: Protect the API endpoint @@ -27,14 +34,14 @@ pub struct Context { } /// Runs the HTTP API server -pub async fn run(config: Config) -> Result<(), String> { +pub async fn run(config: Config, shared_state: Arc>) -> Result<(), String> { if !config.enabled { info!("HTTP API Disabled"); return Ok(()); } // Generate the axum routes - let router = router::new(); + let router = router::new(shared_state); // Set up a listening address diff --git a/anchor/http_api/src/router.rs b/anchor/http_api/src/router.rs index 95b410fbc..198bfd678 100644 --- a/anchor/http_api/src/router.rs +++ b/anchor/http_api/src/router.rs @@ -1,14 +1,20 @@ //! The routes for the HTTP API -use api_types::{GenericResponse, VersionData}; -use axum::{routing::get, Json, Router}; +use crate::Shared; +use api_types::{GenericResponse, VersionData, ValidatorData}; +use axum::{extract::State, routing::get, Json, Router}; +use parking_lot::RwLock; +use std::sync::Arc; use version::version_with_platform; /// Creates all the routes for HTTP API -pub fn new() -> Router { + +pub fn new(shared_state: Arc>) -> Router { // Default route Router::new() .route("/", get(root)) .route("/anchor/version", get(get_version)) + .route("/anchor/validators", get(get_validators)) + .with_state(shared_state) } // Temporary return value. @@ -21,3 +27,26 @@ async fn get_version() -> Json> { version: version_with_platform(), })) } + +async fn get_validators( + State(shared_state): State>>, +) -> Json>> { + if let Some(database_state) = &shared_state.read().database_state { + let validators = database_state.borrow() + .metadata() + .values() + .map(|v| { + ValidatorData { + public_key: v.public_key.to_string(), + cluster_id: format!("{:?}", v.cluster_id), + index: v.index.map(|i| i.0), + graffiti: v.graffiti.as_utf8_lossy(), + } + }) + .collect::>(); + + Json(GenericResponse::from(validators)) + } else { + Json(GenericResponse::from(Vec::new())) + } +} From 16b4c022a06d78f64ff222764d4e9da2e48c37bc Mon Sep 17 00:00:00 2001 From: ThreeHrSleep Date: Thu, 17 Apr 2025 08:15:15 +0530 Subject: [PATCH 2/3] fmt+sort+lints --- Cargo.lock | 6 ------ anchor/client/src/lib.rs | 2 +- anchor/http_api/Cargo.toml | 6 ------ anchor/http_api/src/lib.rs | 7 ++----- anchor/http_api/src/router.rs | 24 ++++++++++++------------ 5 files changed, 15 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f514eac9..a78546a96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3758,15 +3758,10 @@ dependencies = [ name = "http_api" version = "0.1.0" dependencies = [ - "anchor_validator_store", "api_types", "axum", "database", - "eth2", "hex", - "http_metrics", - "message_receiver", - "network", "parking_lot", "serde", "serde_json", @@ -3776,7 +3771,6 @@ dependencies = [ "tokio", "tracing", "types", - "validator_services", "version", ] diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index e5aace767..e2aec6f34 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -158,7 +158,7 @@ impl Client { let state = http_api_shared_state.clone(); executor.spawn( - async { + async { if let Err(error) = http_api::run(config.http_api, state).await { error!(error, "Failed to run HTTP API"); } diff --git a/anchor/http_api/Cargo.toml b/anchor/http_api/Cargo.toml index f602be507..e82b755f2 100644 --- a/anchor/http_api/Cargo.toml +++ b/anchor/http_api/Cargo.toml @@ -9,15 +9,10 @@ name = "http_api" path = "src/lib.rs" [dependencies] -anchor_validator_store = { workspace = true } api_types = { workspace = true } axum = { workspace = true } database = { workspace = true } -eth2 = { workspace = true } hex = { workspace = true } -http_metrics = { workspace = true } -message_receiver = { workspace = true } -network = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -27,5 +22,4 @@ task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } types = { workspace = true } -validator_services = { workspace = true } version = { workspace = true } diff --git a/anchor/http_api/src/lib.rs b/anchor/http_api/src/lib.rs index 5854996aa..e3d068003 100644 --- a/anchor/http_api/src/lib.rs +++ b/anchor/http_api/src/lib.rs @@ -1,21 +1,18 @@ mod config; mod router; -use std::{net::SocketAddr, path::PathBuf}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; pub use config::Config; use database::NetworkState; use parking_lot::RwLock; use slot_clock::SlotClock; -use std::sync::Arc; use task_executor::TaskExecutor; -use tokio::net::TcpListener; -use tokio::sync::watch; +use tokio::{net::TcpListener, sync::watch}; use tracing::info; /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. - pub struct Shared { pub database_state: Option>, } diff --git a/anchor/http_api/src/router.rs b/anchor/http_api/src/router.rs index 198bfd678..2fe78c6b5 100644 --- a/anchor/http_api/src/router.rs +++ b/anchor/http_api/src/router.rs @@ -1,13 +1,14 @@ //! The routes for the HTTP API -use crate::Shared; -use api_types::{GenericResponse, VersionData, ValidatorData}; +use std::sync::Arc; + +use api_types::{GenericResponse, ValidatorData, VersionData}; use axum::{extract::State, routing::get, Json, Router}; use parking_lot::RwLock; -use std::sync::Arc; use version::version_with_platform; -/// Creates all the routes for HTTP API +use crate::Shared; +/// Creates all the routes for HTTP API pub fn new(shared_state: Arc>) -> Router { // Default route Router::new() @@ -32,16 +33,15 @@ async fn get_validators( State(shared_state): State>>, ) -> Json>> { if let Some(database_state) = &shared_state.read().database_state { - let validators = database_state.borrow() + let validators = database_state + .borrow() .metadata() .values() - .map(|v| { - ValidatorData { - public_key: v.public_key.to_string(), - cluster_id: format!("{:?}", v.cluster_id), - index: v.index.map(|i| i.0), - graffiti: v.graffiti.as_utf8_lossy(), - } + .map(|v| ValidatorData { + public_key: v.public_key.to_string(), + cluster_id: format!("{:?}", v.cluster_id), + index: v.index.map(|i| i.0), + graffiti: v.graffiti.as_utf8_lossy(), }) .collect::>(); From f57607a3c1cbc5a5549a918c048e9c0b307c84b2 Mon Sep 17 00:00:00 2001 From: ThreeHrSleep Date: Mon, 21 Apr 2025 17:18:56 +0530 Subject: [PATCH 3/3] suggestions from review --- anchor/client/src/lib.rs | 2 +- anchor/http_api/src/lib.rs | 7 ++++--- anchor/http_api/src/router.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index e2aec6f34..c2385a81c 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -163,7 +163,7 @@ impl Client { error!(error, "Failed to run HTTP API"); } }, - "http-api-server", + "http_api_server", ); // Open database diff --git a/anchor/http_api/src/lib.rs b/anchor/http_api/src/lib.rs index e3d068003..1b441e17b 100644 --- a/anchor/http_api/src/lib.rs +++ b/anchor/http_api/src/lib.rs @@ -13,9 +13,6 @@ use tracing::info; /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. -pub struct Shared { - pub database_state: Option>, -} pub struct Context { pub task_executor: TaskExecutor, // TODO: Protect the API endpoint @@ -30,6 +27,10 @@ pub struct Context { pub slot_clock: T, } +pub struct Shared { + pub database_state: Option>, +} + /// Runs the HTTP API server pub async fn run(config: Config, shared_state: Arc>) -> Result<(), String> { if !config.enabled { diff --git a/anchor/http_api/src/router.rs b/anchor/http_api/src/router.rs index 2fe78c6b5..17d18e501 100644 --- a/anchor/http_api/src/router.rs +++ b/anchor/http_api/src/router.rs @@ -41,7 +41,7 @@ async fn get_validators( public_key: v.public_key.to_string(), cluster_id: format!("{:?}", v.cluster_id), index: v.index.map(|i| i.0), - graffiti: v.graffiti.as_utf8_lossy(), + graffiti: hex::encode(v.graffiti.0), }) .collect::>();