Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 28 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions anchor/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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))?;
Expand Down
9 changes: 9 additions & 0 deletions anchor/common/api_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,
pub graffiti: String,
}

#[derive(Serialize)]
pub struct GenericResponse<T> {
pub data: T,
Expand Down
6 changes: 6 additions & 0 deletions anchor/http_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
15 changes: 10 additions & 5 deletions anchor/http_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -26,15 +27,19 @@ pub struct Context<T: SlotClock> {
pub slot_clock: T,
}

pub struct Shared {
pub database_state: Option<watch::Receiver<NetworkState>>,
}

/// Runs the HTTP API server
pub async fn run(config: Config) -> Result<(), String> {
pub async fn run(config: Config, shared_state: Arc<RwLock<Shared>>) -> 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

Expand Down
35 changes: 32 additions & 3 deletions anchor/http_api/src/router.rs
Original file line number Diff line number Diff line change
@@ -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<RwLock<Shared>>) -> 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.
Expand All @@ -21,3 +28,25 @@ async fn get_version() -> Json<GenericResponse<VersionData>> {
version: version_with_platform(),
}))
}

async fn get_validators(
State(shared_state): State<Arc<RwLock<Shared>>>,
) -> Json<GenericResponse<Vec<ValidatorData>>> {
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::<Vec<_>>();

Json(GenericResponse::from(validators))
} else {
Json(GenericResponse::from(Vec::new()))
}
}