Skip to content

Commit

Permalink
Add status endpoint to V2 API.
Browse files Browse the repository at this point in the history
  • Loading branch information
aterentic-ethernal committed Aug 16, 2023
1 parent 88cae4c commit 5fb0f95
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 40 deletions.
12 changes: 10 additions & 2 deletions src/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use crate::api::v2;
use crate::{
api::v1::{self},
rpc::Node,
types::{RuntimeConfig, State},
};
use anyhow::Context;
Expand All @@ -31,6 +32,7 @@ pub struct Server {
pub state: Arc<Mutex<State>>,
pub version: String,
pub network_version: String,
pub node: Node,
}

impl Server {
Expand All @@ -41,15 +43,21 @@ 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))
.unwrap_or(port.0);

let v1_api = v1::routes(self.db.clone(), app_id, self.state.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.node,
self.state.clone(),
self.cfg,
);

let cors = warp::cors()
.allow_any_origin()
Expand Down
59 changes: 59 additions & 0 deletions src/api/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,65 @@ 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.

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}",
"blocks": {
"synced": false, // Optional
"latest": {latest},
"confidence": [
{
"from": {from},
"to": {to}
} // Optional
], // Optional
"data": [
{
"from": {from},
"to": {to}
} // Optional
] // 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
- **blocks** - state of processed blocks
- **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

### Blocks

- **synced** - `true` if light client is synced past blocks, ommited if sync is not configured
- **latest** - number of latest block received from the node
- **confidence** - ranges of block which confidence has been achieved
- **data** - ranges of blocks which data has been retrieved and verified

# 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.
Expand Down
67 changes: 65 additions & 2 deletions src/api/v2/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
use super::{
types::{Client, Clients, Subscription, SubscriptionId, Version},
types::{BlockRange, Blocks, Client, Clients, Status, Subscription, SubscriptionId, Version},
ws,
};
use std::convert::Infallible;
use crate::{
api::v2::types::InternalServerError,
rpc::Node,
types::{RuntimeConfig, State},
};
use hyper::StatusCode;
use std::{
convert::Infallible,
sync::{Arc, Mutex},
};
use tracing::info;
use uuid::Uuid;
use warp::{ws::Ws, Rejection, Reply};

Expand All @@ -28,3 +38,56 @@ 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,
node: Node,
state: Arc<Mutex<State>>,
) -> Result<impl Reply, impl Reply> {
let state = match state.lock() {
Ok(state) => state,
Err(error) => {
info!("Cannot acquire lock for last_block: {error}");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
},
};
let mut confidence: Vec<BlockRange> = Vec::with_capacity(2);
if let Some(range) = state.sync_confidence_achieved.as_ref().map(From::from) {
confidence.push(range);
};
if let Some(range) = state.confidence_achieved.as_ref().map(From::from) {
confidence.push(range);
};

let mut data: Vec<BlockRange> = Vec::with_capacity(2);
if let Some(range) = state.sync_data_verified.as_ref().map(From::from) {
data.push(range);
};
if let Some(range) = state.data_verified.as_ref().map(From::from) {
data.push(range);
};

let blocks = Blocks {
synced: state.synced.unwrap_or(false),
latest: state.latest,
confidence,
data,
};

let status = Status {
modes: (&config).into(),
app_id: config.app_id,
genesis_hash: format!("{:?}", node.genesis_hash),
network: node.network(),
blocks,
partition: config.block_matrix_partition,
};
Ok(status)
}

pub async fn handle_rejection(error: Rejection) -> Result<impl Reply, Rejection> {
if error.find::<InternalServerError>().is_some() {
return Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response());
}
Err(error)
}
112 changes: 105 additions & 7 deletions src/api/v2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
use self::types::{Clients, Version};
use std::{collections::HashMap, convert::Infallible, sync::Arc};
use crate::{
rpc::Node,
types::{RuntimeConfig, State},
};

use self::{
handlers::handle_rejection,
types::{Clients, Version},
};
use std::{
collections::HashMap,
convert::Infallible,
sync::{Arc, Mutex},
};
use tokio::sync::RwLock;
use warp::{Filter, Rejection, Reply};

Expand All @@ -19,6 +31,20 @@ fn version_route(
.map(move || version.clone())
}

fn status_route(
config: RuntimeConfig,
node: Node,
state: Arc<Mutex<State>>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
warp::path!("v2" / "status")
.and(warp::get())
.and(warp::any().map(move || config.clone()))
.and(warp::any().map(move || node.clone()))
.and(warp::any().map(move || state.clone()))
.then(handlers::status)
.map(types::handle_result)
}

fn subscriptions_route(
clients: Clients,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
Expand All @@ -43,39 +69,50 @@ fn ws_route(
pub fn routes(
version: String,
network_version: String,
node: Node,
state: Arc<Mutex<State>>,
config: RuntimeConfig,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
let clients: Clients = Arc::new(RwLock::new(HashMap::new()));
let version = Version {
version,
network_version,
};
version_route(version.clone())
.or(status_route(config, node, state))
.or(subscriptions_route(clients.clone()))
.or(ws_route(clients, version))
.recover(handle_rejection)
}

#[cfg(test)]
mod tests {
use crate::api::v2::types::{
Clients, DataFields, Subscription, SubscriptionId, Topics, Version,
use super::types::Client;
use crate::{
api::v2::types::{Clients, DataFields, Subscription, SubscriptionId, Topics, Version},
rpc::Node,
types::{RuntimeConfig, State},
};
use kate_recovery::matrix::Partition;
use sp_core::H256;
use std::{
collections::{HashMap, HashSet},
str::FromStr,
sync::Arc,
sync::{Arc, Mutex},
};
use tokio::sync::RwLock;
use uuid::Uuid;

use super::types::Client;

fn v1() -> Version {
Version {
version: "v1.0.0".to_string(),
network_version: "nv1.0.0".to_string(),
}
}

const GENESIS_HASH: &str = "0xc590b3c924c35c2f241746522284e4709df490d73a38aaa7d6de4ed1eac2f500";
const NETWORK: &str = "{host}/{system_version}/data-avail/0";

#[tokio::test]
async fn version_route() {
let route = super::version_route(v1());
Expand All @@ -91,6 +128,67 @@ mod tests {
);
}

impl Default for Node {
fn default() -> Self {
Self {
host: "{host}".to_string(),
system_version: "{system_version}".to_string(),
spec_version: 0,
genesis_hash: H256::from_str(GENESIS_HASH).unwrap(),
}
}
}

#[tokio::test]
async fn status_route_defaults() {
let state = Arc::new(Mutex::new(State::default()));
let route = super::status_route(RuntimeConfig::default(), Node::default(), state);
let response = warp::test::request()
.method("GET")
.path("/v2/status")
.reply(&route)
.await;

let expected = format!(
r#"{{"modes":["light"],"genesis_hash":"{GENESIS_HASH}","network":"{NETWORK}","blocks":{{"synced":false,"latest":0}}}}"#
);
assert_eq!(response.body(), &expected);
}

#[tokio::test]
async fn status_route() {
let runtime_config = RuntimeConfig {
app_id: Some(1),
sync_start_block: Some(10),
block_matrix_partition: Some(Partition {
number: 1,
fraction: 10,
}),
..Default::default()
};
let state = Arc::new(Mutex::new(State::default()));
{
let mut state = state.lock().unwrap();
state.latest = 30;
state.set_confidence_achieved(20);
state.set_confidence_achieved(29);
state.set_sync_confidence_achieved(10);
state.set_sync_confidence_achieved(18);
}

let route = super::status_route(runtime_config, Node::default(), state);
let response = warp::test::request()
.method("GET")
.path("/v2/status")
.reply(&route)
.await;

let expected = format!(
r#"{{"modes":["light","app","partition"],"app_id":1,"genesis_hash":"{GENESIS_HASH}","network":"{NETWORK}","blocks":{{"synced":false,"latest":30,"confidence":[{{"first":10,"last":18}},{{"first":20,"last":29}}]}},"partition":"1/10"}}"#
);
assert_eq!(response.body(), &expected);
}

fn all_topics() -> HashSet<Topics> {
vec![
Topics::HeaderVerified,
Expand Down
Loading

0 comments on commit 5fb0f95

Please sign in to comment.