use exonum::{
blockchain::{ApiSender, Blockchain, Schema},
crypto::PublicKey,
helpers::{exonum_version, os_info, rust_version},
};
use exonum_api::{self as api, ApiBackend, ApiScope};
use exonum_node::{ConnectInfo, ExternalMessage, SharedNodeState};
use futures::{future, prelude::*};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{sync::Arc, time::SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct NodeStats {
pub height: u64,
pub tx_pool_size: u64,
pub tx_count: u64,
pub tx_cache_size: usize,
pub uptime: u64,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ConsensusStatus {
Disabled,
Enabled,
Active,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ConnectDirection {
Incoming,
Outgoing,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct ConnectedPeerInfo {
pub address: String,
pub public_key: PublicKey,
pub direction: ConnectDirection,
}
impl ConnectedPeerInfo {
fn new(connect_info: &ConnectInfo, direction: ConnectDirection) -> Self {
Self {
address: connect_info.address.to_owned(),
public_key: connect_info.public_key,
direction,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[non_exhaustive]
pub struct NodeInfo {
pub consensus_status: ConsensusStatus,
pub connected_peers: Vec<ConnectedPeerInfo>,
pub exonum_version: Version,
pub rust_version: Version,
pub os_info: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[non_exhaustive]
pub struct ConsensusEnabledQuery {
pub enabled: bool,
}
impl ConsensusEnabledQuery {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
}
#[derive(Clone, Debug)]
pub(super) struct SystemApi {
blockchain: Blockchain,
shared_api_state: SharedNodeState,
sender: ApiSender<ExternalMessage>,
start_time: SystemTime,
}
impl SystemApi {
pub fn new(
blockchain: Blockchain,
sender: ApiSender<ExternalMessage>,
shared_api_state: SharedNodeState,
) -> Self {
Self {
blockchain,
sender,
shared_api_state,
start_time: SystemTime::now(),
}
}
pub fn wire(self, api_scope: &mut ApiScope) -> &mut ApiScope {
self.handle_info("v1/info", api_scope)
.handle_stats("v1/stats", api_scope)
.handle_peers("v1/peers", api_scope)
.handle_consensus_status("v1/consensus_status", api_scope)
.handle_shutdown("v1/shutdown", api_scope);
api_scope
}
fn handle_info(self, name: &'static str, api_scope: &mut ApiScope) -> Self {
let shared_api_state = self.shared_api_state.clone();
api_scope.endpoint(name, move |_query: ()| {
let mut connected_peers = Vec::new();
for connect_info in shared_api_state.outgoing_connections() {
connected_peers.push(ConnectedPeerInfo::new(
&connect_info,
ConnectDirection::Outgoing,
));
}
for connect_info in shared_api_state.incoming_connections() {
connected_peers.push(ConnectedPeerInfo::new(
&connect_info,
ConnectDirection::Incoming,
));
}
let info = NodeInfo {
consensus_status: Self::get_consensus_status(&shared_api_state),
connected_peers,
exonum_version: exonum_version().unwrap_or_else(|| Version::new(0, 0, 0)),
rust_version: rust_version().unwrap_or_else(|| Version::new(0, 0, 0)),
os_info: os_info(),
};
future::ok(info)
});
self
}
fn handle_stats(self, name: &'static str, api_scope: &mut ApiScope) -> Self {
let this = self.clone();
api_scope.endpoint(name, move |_query: ()| {
let snapshot = this.blockchain.snapshot();
let schema = Schema::new(&snapshot);
let uptime = SystemTime::now()
.duration_since(this.start_time)
.unwrap_or_default()
.as_secs();
let stats = NodeStats {
height: schema.height().into(),
tx_pool_size: schema.transactions_pool_len(),
tx_count: schema.transactions_len(),
tx_cache_size: this.shared_api_state.tx_cache_size(),
uptime,
};
future::ok(stats)
});
self
}
fn handle_peers(self, name: &'static str, api_scope: &mut ApiScope) -> Self {
let sender = self.sender.clone();
api_scope.endpoint_mut(name, move |connect_info: ConnectInfo| {
let mut sender = sender.clone();
async move {
sender
.send_message(ExternalMessage::PeerAdd(connect_info))
.await
.map_err(|e| api::Error::internal(e).title("Failed to add peer"))
}
});
self
}
fn handle_consensus_status(self, name: &'static str, api_scope: &mut ApiScope) -> Self {
let sender = self.sender.clone();
api_scope.endpoint_mut(name, move |query: ConsensusEnabledQuery| {
let mut sender = sender.clone();
async move {
sender
.send_message(ExternalMessage::Enable(query.enabled))
.await
.map_err(|e| api::Error::internal(e).title("Failed to set consensus enabled"))
}
});
self
}
fn handle_shutdown(self, name: &'static str, api_scope: &mut ApiScope) -> Self {
use actix_web::HttpResponse;
use exonum_api::backends::actix::{RawHandler, RequestHandler};
let sender = self.sender.clone();
let index = move |_, _| {
let mut sender = sender.clone();
async move {
sender
.send_message(ExternalMessage::Shutdown)
.await
.map(|_| HttpResponse::Ok().json(()))
.map_err(|e| {
api::Error::internal(e)
.title("Failed to handle shutdown")
.into()
})
}
.boxed_local()
};
let handler = RequestHandler {
name: name.to_owned(),
method: actix_web::http::Method::POST,
inner: Arc::new(index) as Arc<RawHandler>,
};
api_scope.web_backend().raw_handler(handler);
self
}
fn get_consensus_status(state: &SharedNodeState) -> ConsensusStatus {
if state.is_enabled() {
if state.consensus_status() {
ConsensusStatus::Active
} else {
ConsensusStatus::Enabled
}
} else {
ConsensusStatus::Disabled
}
}
}