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 17, 2023
1 parent 88cae4c commit b318b45
Show file tree
Hide file tree
Showing 8 changed files with 395 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
71 changes: 71 additions & 0 deletions src/api/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,77 @@ 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": {
"latest": {latest},
"available": { // Optional
"first": {first},
"last": {last}
},
"app_data": { // Optional
"first": {first},
"last": {last}
},
"historcal_sync": { // Optional
"synced": false,
"available": { // Optional
"first": {first},
"last": {last}
},
"app_data": { // Optional
"first": {first},
"last": {last}
}
}
},
"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

- **latest** - block number of the latest [finalized](https://docs.substrate.io/learn/consensus/) block received from the node
- **available** - range of blocks with verified data availability (configured confidence has been achieved)
- **app_data** - range of blocks with app data retrieved and verified
- **historical_sync** - state for historical blocks syncing up to configured block (ommited if historical sync is not configured)

### Historical sync

- **synced** - `true` if there are no historical blocks left to sync
- **available** - range of historical blocks with verified data availability (configured confidence has been achieved)
- **app_data** - range of historical blocks with app data 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
61 changes: 59 additions & 2 deletions src/api/v2/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
use super::{
types::{Client, Clients, Subscription, SubscriptionId, Version},
types::{
BlockRange, Blocks, Client, Clients, HistoricalSync, 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 +41,47 @@ 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 historical_sync = state.synced.map(|synced| HistoricalSync {
synced,
available: state.sync_confidence_achieved.as_ref().map(From::from),
app_data: state.sync_data_verified.as_ref().map(From::from),
});

let blocks = Blocks {
latest: state.latest,
available: state.confidence_achieved.as_ref().map(From::from),
app_data: state.data_verified.as_ref().map(From::from),
historical_sync,
};

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)
}
117 changes: 110 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,72 @@ 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":{{"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_data_verified(20);
state.set_data_verified(29);
state.set_synced(false);
state.set_sync_confidence_achieved(10);
state.set_sync_confidence_achieved(19);
state.set_sync_data_verified(10);
state.set_sync_data_verified(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":{{"latest":30,"available":{{"first":20,"last":29}},"app_data":{{"first":20,"last":29}},"historical_sync":{{"synced":false,"available":{{"first":10,"last":19}},"app_data":{{"first":10,"last":18}}}}}},"partition":"1/10"}}"#
);
assert_eq!(response.body(), &expected);
}

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

0 comments on commit b318b45

Please sign in to comment.