diff --git a/Cargo.lock b/Cargo.lock index b275172a7..60a19e63c 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", @@ -3760,11 +3760,17 @@ version = "0.1.0" dependencies = [ "api_types", "axum", + "database", + "hex", + "parking_lot", "serde", + "serde_json", "slot_clock", + "ssv_types", "task_executor", "tokio", "tracing", + "types", "version", ] @@ -3832,7 +3838,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 +4419,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" @@ -6234,9 +6240,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", ] @@ -6767,7 +6773,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 fe26b0157..fff0cbfc5 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( @@ -559,6 +568,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 // https://github.com/sigp/anchor/issues/251 // 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..e82b755f2 100644 --- a/anchor/http_api/Cargo.toml +++ b/anchor/http_api/Cargo.toml @@ -11,9 +11,15 @@ path = "src/lib.rs" [dependencies] api_types = { workspace = true } axum = { workspace = true } +database = { workspace = true } +hex = { 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 } version = { workspace = true } diff --git a/anchor/http_api/src/lib.rs b/anchor/http_api/src/lib.rs index 3c4f85716..1b441e17b 100644 --- a/anchor/http_api/src/lib.rs +++ b/anchor/http_api/src/lib.rs @@ -1,14 +1,15 @@ 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 task_executor::TaskExecutor; -use tokio::net::TcpListener; +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`. @@ -26,15 +27,19 @@ 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) -> 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..17d18e501 100644 --- a/anchor/http_api/src/router.rs +++ b/anchor/http_api/src/router.rs @@ -1,14 +1,21 @@ //! The routes for the HTTP API -use api_types::{GenericResponse, VersionData}; -use axum::{routing::get, Json, Router}; +use std::sync::Arc; + +use api_types::{GenericResponse, ValidatorData, VersionData}; +use axum::{extract::State, routing::get, Json, Router}; +use parking_lot::RwLock; use version::version_with_platform; + +use crate::Shared; /// 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 +28,25 @@ 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: hex::encode(v.graffiti.0), + }) + .collect::>(); + + Json(GenericResponse::from(validators)) + } else { + Json(GenericResponse::from(Vec::new())) + } +}