diff --git a/src/api/server.rs b/src/api/server.rs index 1e326c303..066441d8f 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -41,7 +41,7 @@ impl Server { http_server_port: port, app_id, .. - } = self.cfg; + } = self.cfg.clone(); let port = (port.1 > 0) .then(|| thread_rng().gen_range(port.0..=port.1)) @@ -49,7 +49,11 @@ impl Server { let v1_api = v1::routes(self.db.clone(), app_id, self.counter.clone()); #[cfg(feature = "api-v2")] - let v2_api = v2::routes(self.version.clone(), self.network_version.clone()); + let v2_api = v2::routes( + self.version.clone(), + self.network_version.clone(), + self.cfg, + ); let cors = warp::cors() .allow_any_origin() diff --git a/src/api/v2/README.md b/src/api/v2/README.md index 4af6c8f43..4fa38a80a 100644 --- a/src/api/v2/README.md +++ b/src/api/v2/README.md @@ -34,6 +34,48 @@ Content-Type: application/json - **version** - the Avail Light Client version - **network_version** - Avail network version supported by the Avail Light Client +## **GET** `/v2/status` + +Gets current status and active modes of the light client. + +- Use cases + - Monitoring of the active light clients + - Reconfiguration verification + - Development tooling + +Response: + +```yaml +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "modes": ["light", "app", "partition"], + "app_id": "{app-id}", // Optional + "genesis_hash": "{genesis-hash}", + "network": "{network}", + "latest_block": {latest-block}, // Optional + "latest_synced_block": {sync-block}, // Optional + "sync_depth": {sync-depth}, // Optional + "partition": "{partition}" // Optional +} +``` + +- **modes** - active modes +- **app_id** - if **app** mode is active, this field contains configured application ID +- **genesis_hash** - genesis hash of the network to which the light client is connected +- **network** - network host, version and spec version light client is currently con +- **latest_block** - latest processed block +- **latest_synced_block** - the latest processed block in the sync range +- **sync_depth** - number of blocks before the latest to sync on light client start +- **partition** - if configured, displays partition which light client distributes to the peer to peer network + +### Modes + +- **light** - data availability sampling mode, the light client performs random sampling and calculates confidence +- **app** - light client fetches, verifies, and stores application-related data +- **partition** - light client fetches configured block partition and publishes it to the DHT + # WebSocket API The Avail Light Client WebSocket API allows real-time communication between a client and a server over a persistent connection, enabling push notifications as an alternative to polling. Web socket API can be used on its own or in combination with HTTP API to enable different pull/push use cases. diff --git a/src/api/v2/handlers.rs b/src/api/v2/handlers.rs index 207b6fd86..c7a66ec71 100644 --- a/src/api/v2/handlers.rs +++ b/src/api/v2/handlers.rs @@ -1,5 +1,7 @@ +use crate::types::RuntimeConfig; + use super::{ - types::{Client, Clients, Subscription, SubscriptionId, Version}, + types::{Client, Clients, Status, Subscription, SubscriptionId, Version}, ws, }; use std::convert::Infallible; @@ -28,3 +30,9 @@ pub async fn ws( // Multiple connections to the same client are currently allowed Ok(ws.on_upgrade(move |web_socket| ws::connect(subscription_id, web_socket, clients, version))) } + +pub async fn status(config: RuntimeConfig) -> Result { + Ok(Status { + modes: config.into(), + }) +} diff --git a/src/api/v2/mod.rs b/src/api/v2/mod.rs index 1f57e759c..5fc4d79f5 100644 --- a/src/api/v2/mod.rs +++ b/src/api/v2/mod.rs @@ -1,3 +1,5 @@ +use crate::types::RuntimeConfig; + use self::types::{Clients, Version}; use std::{collections::HashMap, convert::Infallible, sync::Arc}; use tokio::sync::RwLock; @@ -19,6 +21,15 @@ fn version_route( .map(move || version.clone()) } +fn status_route( + config: RuntimeConfig, +) -> impl Filter + Clone { + warp::path!("v2" / "status") + .and(warp::get()) + .and(warp::any().map(move || config.clone())) + .and_then(handlers::status) +} + fn subscriptions_route( clients: Clients, ) -> impl Filter + Clone { @@ -43,6 +54,7 @@ fn ws_route( pub fn routes( version: String, network_version: String, + config: RuntimeConfig, ) -> impl Filter + Clone { let clients: Clients = Arc::new(RwLock::new(HashMap::new())); let version = Version { @@ -50,14 +62,16 @@ pub fn routes( network_version, }; version_route(version.clone()) + .or(status_route(config)) .or(subscriptions_route(clients.clone())) .or(ws_route(clients, version)) } #[cfg(test)] mod tests { - use crate::api::v2::types::{ - Clients, DataFields, Subscription, SubscriptionId, Topics, Version, + use crate::{ + api::v2::types::{Clients, DataFields, Subscription, SubscriptionId, Topics, Version}, + types::RuntimeConfig, }; use std::{ collections::{HashMap, HashSet}, @@ -91,6 +105,21 @@ mod tests { ); } + #[tokio::test] + async fn status_route() { + let route = super::status_route(RuntimeConfig::default()); + let response = warp::test::request() + .method("GET") + .path("/v2/status") + .reply(&route) + .await; + + assert_eq!( + response.body(), + r#"{"modes":["light","app","partition"],"app_id": "1","genesis_hash":"{genesis-hash}","network":"{network}","latest_block":0,"latest_synced_block":0,"sync_depth":1,"partition":"1/10"}"# + ); + } + fn all_topics() -> HashSet { vec![ Topics::HeaderVerified, diff --git a/src/api/v2/types.rs b/src/api/v2/types.rs index 58b639723..6b57ab6e6 100644 --- a/src/api/v2/types.rs +++ b/src/api/v2/types.rs @@ -7,6 +7,8 @@ use std::{ use tokio::sync::{mpsc::UnboundedSender, RwLock}; use warp::{ws, Reply}; +use crate::types::RuntimeConfig; + #[derive(Serialize, Clone)] pub struct Version { pub version: String, @@ -19,6 +21,39 @@ impl Reply for Version { } } +#[derive(Serialize)] +pub struct Status { + pub modes: Vec, +} + +#[derive(Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum Mode { + Light, + App, + Partition, +} + +impl From for Vec { + fn from(value: RuntimeConfig) -> Self { + let mut result: Vec = vec![]; + result.push(Mode::Light); + if value.app_id.is_some() { + result.push(Mode::App); + } + if value.block_matrix_partition.is_some() { + result.push(Mode::Partition) + } + result + } +} + +impl Reply for Status { + fn into_response(self) -> warp::reply::Response { + warp::reply::json(&self).into_response() + } +} + #[derive(Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "kebab-case")] pub enum Topics {