mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-04-22 23:08:04 +00:00
Rust backend (#185)
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
use std::time::{Duration, Instant};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use bytes::Bytes;
|
||||
use actix::prelude::*;
|
||||
use actix_web_actors::ws;
|
||||
use crate::aggregator::{Aggregator, AddNode};
|
||||
use crate::chain::{Chain, UpdateNode, RemoveNode};
|
||||
use crate::node::NodeId;
|
||||
use crate::node::message::{NodeMessage, Details, SystemConnected};
|
||||
use crate::util::LocateRequest;
|
||||
|
||||
/// How often heartbeat pings are sent
|
||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(20);
|
||||
/// How long before lack of client response causes a timeout
|
||||
const CLIENT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
pub struct NodeConnector {
|
||||
/// Id of the node this connector is responsible for handling
|
||||
nid: NodeId,
|
||||
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
|
||||
hb: Instant,
|
||||
/// Aggregator actor address
|
||||
aggregator: Addr<Aggregator>,
|
||||
/// Chain actor address
|
||||
chain: Option<Addr<Chain>>,
|
||||
/// Backlog of messages to be sent once we get a recipient handle to the chain
|
||||
backlog: Vec<NodeMessage>,
|
||||
/// IP address of the node this connector is responsible for
|
||||
ip: Option<Ipv4Addr>,
|
||||
/// Actix address of location services
|
||||
locator: Recipient<LocateRequest>,
|
||||
}
|
||||
|
||||
impl Actor for NodeConnector {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.heartbeat(ctx);
|
||||
}
|
||||
|
||||
fn stopped(&mut self, _: &mut Self::Context) {
|
||||
if let Some(chain) = self.chain.as_ref() {
|
||||
chain.do_send(RemoveNode(self.nid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeConnector {
|
||||
pub fn new(aggregator: Addr<Aggregator>, locator: Recipient<LocateRequest>, ip: Option<Ipv4Addr>) -> Self {
|
||||
Self {
|
||||
// Garbage id, will be replaced by the Initialize message
|
||||
nid: !0,
|
||||
hb: Instant::now(),
|
||||
aggregator,
|
||||
chain: None,
|
||||
backlog: Vec::new(),
|
||||
ip,
|
||||
locator,
|
||||
}
|
||||
}
|
||||
|
||||
fn heartbeat(&self, ctx: &mut <Self as Actor>::Context) {
|
||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||
// check client heartbeats
|
||||
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
|
||||
// stop actor
|
||||
ctx.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, msg: NodeMessage, data: Bytes, ctx: &mut <Self as Actor>::Context) {
|
||||
if let Some(chain) = self.chain.as_ref() {
|
||||
chain.do_send(UpdateNode {
|
||||
nid: self.nid,
|
||||
msg,
|
||||
raw: Some(data)
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Details::SystemConnected(connected) = msg.details {
|
||||
let SystemConnected { chain, node } = connected;
|
||||
let rec = ctx.address().recipient();
|
||||
let chain = chain.into();
|
||||
|
||||
self.aggregator.do_send(AddNode { rec, chain, node });
|
||||
} else {
|
||||
if self.backlog.len() >= 10 {
|
||||
self.backlog.remove(0);
|
||||
}
|
||||
|
||||
self.backlog.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
pub struct Initialize(pub NodeId, pub Addr<Chain>);
|
||||
|
||||
impl Handler<Initialize> for NodeConnector {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Initialize, _: &mut Self::Context) {
|
||||
let Initialize(nid, chain) = msg;
|
||||
let backlog = std::mem::replace(&mut self.backlog, Vec::new());
|
||||
|
||||
for msg in backlog {
|
||||
chain.do_send(UpdateNode { nid, msg, raw: None });
|
||||
}
|
||||
|
||||
self.nid = nid;
|
||||
self.chain = Some(chain.clone());
|
||||
|
||||
// Acquire the node's physical location
|
||||
if let Some(ip) = self.ip {
|
||||
let _ = self.locator.do_send(LocateRequest { ip, nid, chain });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<ws::Message, ws::ProtocolError> for NodeConnector {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
self.hb = Instant::now();
|
||||
|
||||
let data = match msg {
|
||||
ws::Message::Ping(msg) => {
|
||||
ctx.pong(&msg);
|
||||
return;
|
||||
}
|
||||
ws::Message::Pong(_) => return,
|
||||
ws::Message::Text(text) => text.into(),
|
||||
ws::Message::Binary(data) => data,
|
||||
ws::Message::Close(_) => {
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
ws::Message::Nop => return,
|
||||
};
|
||||
|
||||
match serde_json::from_slice(&data) {
|
||||
Ok(msg) => self.handle_message(msg, data, ctx),
|
||||
Err(err) => {
|
||||
let data: &[u8] = data.get(..256).unwrap_or_else(|| &data);
|
||||
warn!("Failed to parse node message: {} {}", err, std::str::from_utf8(data).unwrap_or_else(|_| "INVALID UTF8"))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
use actix::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use serde::de::IgnoredAny;
|
||||
use crate::node::{NodeDetails, NodeStats};
|
||||
use crate::types::{Block, BlockNumber, BlockHash};
|
||||
|
||||
#[derive(Deserialize, Debug, Message)]
|
||||
pub struct NodeMessage {
|
||||
pub level: Level,
|
||||
pub ts: DateTime<Utc>,
|
||||
#[serde(flatten)]
|
||||
pub details: Details,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub enum Level {
|
||||
#[serde(rename = "INFO")]
|
||||
Info,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(tag = "msg")]
|
||||
pub enum Details {
|
||||
#[serde(rename = "node.start")]
|
||||
NodeStart(Block),
|
||||
#[serde(rename = "system.connected")]
|
||||
SystemConnected(SystemConnected),
|
||||
#[serde(rename = "system.interval")]
|
||||
SystemInterval(SystemInterval),
|
||||
#[serde(rename = "system.network_state")]
|
||||
SystemNetworkState(IgnoredAny),
|
||||
#[serde(rename = "block.import")]
|
||||
BlockImport(Block),
|
||||
#[serde(rename = "notify.finalized")]
|
||||
NotifyFinalized(Finalized),
|
||||
#[serde(rename = "txpool.import")]
|
||||
TxPoolImport(IgnoredAny),
|
||||
#[serde(rename = "afg.finalized")]
|
||||
AfgFinalized(IgnoredAny),
|
||||
#[serde(rename = "afg.received_precommit")]
|
||||
AfgReceivedPrecommit(IgnoredAny),
|
||||
#[serde(rename = "afg.received_prevote")]
|
||||
AfgReceivedPrevote(IgnoredAny),
|
||||
#[serde(rename = "afg.received_commit")]
|
||||
AfgReceivedCommit(IgnoredAny),
|
||||
#[serde(rename = "afg.authority_set")]
|
||||
AfgAuthoritySet(IgnoredAny),
|
||||
#[serde(rename = "aura.pre_sealed_block")]
|
||||
AuraPreSealedBlock(IgnoredAny),
|
||||
#[serde(rename = "prepared_block_for_proposing")]
|
||||
PreparedBlockForProposing(IgnoredAny),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SystemConnected {
|
||||
pub chain: Box<str>,
|
||||
#[serde(flatten)]
|
||||
pub node: NodeDetails,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SystemInterval {
|
||||
#[serde(flatten)]
|
||||
pub stats: NodeStats,
|
||||
pub memory: Option<f32>,
|
||||
pub cpu: Option<f32>,
|
||||
pub bandwidth_upload: Option<f64>,
|
||||
pub bandwidth_download: Option<f64>,
|
||||
pub finalized_height: Option<BlockNumber>,
|
||||
pub finalized_hash: Option<BlockHash>,
|
||||
#[serde(flatten)]
|
||||
pub block: Block,
|
||||
pub network_state: Option<IgnoredAny>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Finalized {
|
||||
#[serde(rename = "best")]
|
||||
pub hash: BlockHash,
|
||||
pub height: Box<str>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn zero() -> Self {
|
||||
Block {
|
||||
hash: BlockHash::from([0; 32]),
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Details {
|
||||
pub fn best_block(&self) -> Option<&Block> {
|
||||
match self {
|
||||
Details::BlockImport(block) | Details::SystemInterval(SystemInterval { block, .. }) => {
|
||||
Some(block)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalized_block(&self) -> Option<Block> {
|
||||
match self {
|
||||
Details::SystemInterval(ref interval) => {
|
||||
Some(Block {
|
||||
hash: interval.finalized_hash?,
|
||||
height: interval.finalized_height?,
|
||||
})
|
||||
},
|
||||
Details::NotifyFinalized(ref finalized) => {
|
||||
Some(Block {
|
||||
hash: finalized.hash,
|
||||
height: finalized.height.parse().ok()?
|
||||
})
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user