feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "pezsc-telemetry"
|
||||
version = "15.0.0"
|
||||
authors.workspace = true
|
||||
description = "Telemetry utils"
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezsc-telemetry"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
libp2p = { features = ["dns", "tcp", "tokio", "websocket"], workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
parking_lot = { workspace = true, default-features = true }
|
||||
pin-project = { workspace = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
pezsc-utils = { workspace = true, default-features = true }
|
||||
serde = { features = ["derive"], workspace = true, default-features = true }
|
||||
serde_json = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true }
|
||||
wasm-timer = { workspace = true }
|
||||
@@ -0,0 +1,21 @@
|
||||
# pezsc-telemetry
|
||||
|
||||
Bizinikiwi's client telemetry is a part of Bizinikiwi that allows ingesting telemetry data
|
||||
with for example [PezkuwiChain telemetry](https://github.com/paritytech/bizinikiwi-telemetry).
|
||||
|
||||
It works using Tokio's [tracing](https://github.com/tokio-rs/tracing/) library. The telemetry
|
||||
information uses tracing's logging to report the telemetry data which is then retrieved by a
|
||||
tracing `Layer`. This layer will then send the data through an asynchronous channel to a
|
||||
background task called [`TelemetryWorker`] which will send the information to the configured
|
||||
remote telemetry servers.
|
||||
|
||||
If multiple Bizinikiwi nodes are running in the same process, it uses a `tracing::Span` to
|
||||
identify which Bizinikiwi node is reporting the telemetry. Every task spawned using pezsc-service's
|
||||
`TaskManager` automatically inherit this span.
|
||||
|
||||
Bizinikiwi's nodes initialize/register with the [`TelemetryWorker`] using a [`TelemetryHandle`].
|
||||
This handle can be cloned and passed around. It uses an asynchronous channel to communicate with
|
||||
the running [`TelemetryWorker`] dedicated to registration. Registering can happen at any point
|
||||
in time during the process execution.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -0,0 +1,114 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use libp2p::multiaddr::{self, Multiaddr};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
/// List of telemetry servers we want to talk to. Contains the URL of the server, and the
|
||||
/// maximum verbosity level.
|
||||
///
|
||||
/// The URL string can be either a URL or a multiaddress.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct TelemetryEndpoints(
|
||||
#[serde(deserialize_with = "url_or_multiaddr_deser")] pub(crate) Vec<(Multiaddr, u8)>,
|
||||
);
|
||||
|
||||
/// Custom deserializer for TelemetryEndpoints, used to convert urls or multiaddr to multiaddr.
|
||||
fn url_or_multiaddr_deser<'de, D>(deserializer: D) -> Result<Vec<(Multiaddr, u8)>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Vec::<(String, u8)>::deserialize(deserializer)?
|
||||
.iter()
|
||||
.map(|e| url_to_multiaddr(&e.0).map_err(serde::de::Error::custom).map(|m| (m, e.1)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl TelemetryEndpoints {
|
||||
/// Create a `TelemetryEndpoints` based on a list of `(String, u8)`.
|
||||
pub fn new(endpoints: Vec<(String, u8)>) -> Result<Self, multiaddr::Error> {
|
||||
let endpoints: Result<Vec<(Multiaddr, u8)>, multiaddr::Error> =
|
||||
endpoints.iter().map(|e| Ok((url_to_multiaddr(&e.0)?, e.1))).collect();
|
||||
endpoints.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TelemetryEndpoints {
|
||||
/// Return `true` if there are no telemetry endpoints, `false` otherwise.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a WebSocket URL into a libp2p `Multiaddr`.
|
||||
fn url_to_multiaddr(url: &str) -> Result<Multiaddr, multiaddr::Error> {
|
||||
// First, assume that we have a `Multiaddr`.
|
||||
let parse_error = match url.parse() {
|
||||
Ok(ma) => return Ok(ma),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
// If not, try the `ws://path/url` format.
|
||||
if let Ok(ma) = multiaddr::from_url(url) {
|
||||
return Ok(ma);
|
||||
}
|
||||
|
||||
// If we have no clue about the format of that string, assume that we were expecting a
|
||||
// `Multiaddr`.
|
||||
Err(parse_error)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{url_to_multiaddr, Multiaddr, TelemetryEndpoints};
|
||||
|
||||
#[test]
|
||||
fn valid_endpoints() {
|
||||
let endp = vec![
|
||||
("wss://telemetry.pezkuwichain.io/submit/".into(), 3),
|
||||
("/ip4/80.123.90.4/tcp/5432".into(), 4),
|
||||
];
|
||||
let telem =
|
||||
TelemetryEndpoints::new(endp.clone()).expect("Telemetry endpoint should be valid");
|
||||
let mut res: Vec<(Multiaddr, u8)> = vec![];
|
||||
for (a, b) in endp.iter() {
|
||||
res.push((url_to_multiaddr(a).expect("provided url should be valid"), *b))
|
||||
}
|
||||
assert_eq!(telem.0, res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_endpoints() {
|
||||
let endp = vec![
|
||||
("/ip4/...80.123.90.4/tcp/5432".into(), 3),
|
||||
("/ip4/no:!?;rlkqre;;::::///tcp/5432".into(), 4),
|
||||
];
|
||||
let telem = TelemetryEndpoints::new(endp);
|
||||
assert!(telem.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_and_invalid_endpoints() {
|
||||
let endp = vec![
|
||||
("/ip4/80.123.90.4/tcp/5432".into(), 3),
|
||||
("/ip4/no:!?;rlkqre;;::::///tcp/5432".into(), 4),
|
||||
];
|
||||
let telem = TelemetryEndpoints::new(endp);
|
||||
assert!(telem.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("IO Error")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("This telemetry instance has already been initialized!")]
|
||||
TelemetryAlreadyInitialized,
|
||||
#[error("The telemetry worker has been dropped already.")]
|
||||
TelemetryWorkerDropped,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -0,0 +1,572 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bizinikiwi's client telemetry is a part of bizinikiwi that allows ingesting telemetry data
|
||||
//! with for example [Pezkuwi telemetry](https://github.com/paritytech/bizinikiwi-telemetry).
|
||||
//!
|
||||
//! It works using Tokio's [tracing](https://github.com/tokio-rs/tracing/) library. The telemetry
|
||||
//! information uses tracing's logging to report the telemetry data which is then retrieved by a
|
||||
//! tracing `Layer`. This layer will then send the data through an asynchronous channel to a
|
||||
//! background task called [`TelemetryWorker`] which will send the information to the configured
|
||||
//! remote telemetry servers.
|
||||
//!
|
||||
//! If multiple bizinikiwi nodes are running in the same process, it uses a `tracing::Span` to
|
||||
//! identify which bizinikiwi node is reporting the telemetry. Every task spawned using sc-service's
|
||||
//! `TaskManager` automatically inherit this span.
|
||||
//!
|
||||
//! Bizinikiwi's nodes initialize/register with the [`TelemetryWorker`] using a
|
||||
//! [`TelemetryWorkerHandle`]. This handle can be cloned and passed around. It uses an asynchronous
|
||||
//! channel to communicate with the running [`TelemetryWorker`] dedicated to registration.
|
||||
//! Registering can happen at any point in time during the process execution.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use futures::{channel::mpsc, prelude::*};
|
||||
use libp2p::Multiaddr;
|
||||
use log::{error, warn};
|
||||
use parking_lot::Mutex;
|
||||
use pezsc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
collections::{
|
||||
hash_map::Entry::{Occupied, Vacant},
|
||||
HashMap,
|
||||
},
|
||||
sync::{atomic, Arc},
|
||||
};
|
||||
|
||||
pub use log;
|
||||
pub use serde_json;
|
||||
|
||||
mod endpoints;
|
||||
mod error;
|
||||
mod node;
|
||||
mod transport;
|
||||
|
||||
pub use endpoints::*;
|
||||
pub use error::*;
|
||||
use node::*;
|
||||
use transport::*;
|
||||
|
||||
/// Bizinikiwi DEBUG log level.
|
||||
pub const BIZINIKIWI_DEBUG: VerbosityLevel = 9;
|
||||
/// Bizinikiwi INFO log level.
|
||||
pub const BIZINIKIWI_INFO: VerbosityLevel = 0;
|
||||
|
||||
/// Consensus TRACE log level.
|
||||
pub const CONSENSUS_TRACE: VerbosityLevel = 9;
|
||||
/// Consensus DEBUG log level.
|
||||
pub const CONSENSUS_DEBUG: VerbosityLevel = 5;
|
||||
/// Consensus WARN log level.
|
||||
pub const CONSENSUS_WARN: VerbosityLevel = 4;
|
||||
/// Consensus INFO log level.
|
||||
pub const CONSENSUS_INFO: VerbosityLevel = 1;
|
||||
|
||||
/// Telemetry message verbosity.
|
||||
pub type VerbosityLevel = u8;
|
||||
|
||||
pub(crate) type Id = u64;
|
||||
pub(crate) type TelemetryPayload = serde_json::Map<String, serde_json::Value>;
|
||||
pub(crate) type TelemetryMessage = (Id, VerbosityLevel, TelemetryPayload);
|
||||
|
||||
/// Message sent when the connection (re-)establishes.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ConnectionMessage {
|
||||
/// Node's name.
|
||||
pub name: String,
|
||||
/// Node's implementation.
|
||||
pub implementation: String,
|
||||
/// Node's version.
|
||||
pub version: String,
|
||||
/// Node's configuration.
|
||||
pub config: String,
|
||||
/// Node's chain.
|
||||
pub chain: String,
|
||||
/// Node's genesis hash.
|
||||
pub genesis_hash: String,
|
||||
/// Node is an authority.
|
||||
pub authority: bool,
|
||||
/// Node's startup time.
|
||||
pub startup_time: String,
|
||||
/// Node's network ID.
|
||||
pub network_id: String,
|
||||
|
||||
/// Node's OS.
|
||||
pub target_os: String,
|
||||
|
||||
/// Node's ISA.
|
||||
pub target_arch: String,
|
||||
|
||||
/// Node's target platform ABI or libc.
|
||||
pub target_env: String,
|
||||
|
||||
/// Node's software and hardware information.
|
||||
pub sysinfo: Option<SysInfo>,
|
||||
}
|
||||
|
||||
/// Hardware and software information for the node.
|
||||
///
|
||||
/// Gathering most of this information is highly OS-specific,
|
||||
/// so most of the fields here are optional.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SysInfo {
|
||||
/// The exact CPU model.
|
||||
pub cpu: Option<String>,
|
||||
/// The total amount of memory, in bytes.
|
||||
pub memory: Option<u64>,
|
||||
/// The number of physical CPU cores.
|
||||
pub core_count: Option<u32>,
|
||||
/// The Linux kernel version.
|
||||
pub linux_kernel: Option<String>,
|
||||
/// The exact Linux distribution used.
|
||||
pub linux_distro: Option<String>,
|
||||
/// Whether the node's running under a virtual machine.
|
||||
pub is_virtual_machine: Option<bool>,
|
||||
}
|
||||
|
||||
/// Telemetry worker.
|
||||
///
|
||||
/// It should run as a background task using the [`TelemetryWorker::run`] method. This method
|
||||
/// will consume the object and any further attempts of initializing a new telemetry through its
|
||||
/// handle will fail (without being fatal).
|
||||
#[derive(Debug)]
|
||||
pub struct TelemetryWorker {
|
||||
message_receiver: mpsc::Receiver<TelemetryMessage>,
|
||||
message_sender: mpsc::Sender<TelemetryMessage>,
|
||||
register_receiver: TracingUnboundedReceiver<Register>,
|
||||
register_sender: TracingUnboundedSender<Register>,
|
||||
id_counter: Arc<atomic::AtomicU64>,
|
||||
}
|
||||
|
||||
impl TelemetryWorker {
|
||||
/// Instantiate a new [`TelemetryWorker`] which can run in background.
|
||||
///
|
||||
/// Only one is needed per process.
|
||||
pub fn new(buffer_size: usize) -> Result<Self> {
|
||||
// Let's try to initialize a transport to get an early return.
|
||||
// Later transport will be initialized multiple times in
|
||||
// `::process_register`, so it's a convenient way to get an
|
||||
// error as early as possible.
|
||||
let _transport = initialize_transport()?;
|
||||
let (message_sender, message_receiver) = mpsc::channel(buffer_size);
|
||||
let (register_sender, register_receiver) =
|
||||
tracing_unbounded("mpsc_telemetry_register", 10_000);
|
||||
|
||||
Ok(Self {
|
||||
message_receiver,
|
||||
message_sender,
|
||||
register_receiver,
|
||||
register_sender,
|
||||
id_counter: Arc::new(atomic::AtomicU64::new(1)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a new [`TelemetryWorkerHandle`].
|
||||
///
|
||||
/// This is used when you want to register with the [`TelemetryWorker`].
|
||||
pub fn handle(&self) -> TelemetryWorkerHandle {
|
||||
TelemetryWorkerHandle {
|
||||
message_sender: self.message_sender.clone(),
|
||||
register_sender: self.register_sender.clone(),
|
||||
id_counter: self.id_counter.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the telemetry worker.
|
||||
///
|
||||
/// This should be run in a background task.
|
||||
pub async fn run(mut self) {
|
||||
let mut node_map: HashMap<Id, Vec<(VerbosityLevel, Multiaddr)>> = HashMap::new();
|
||||
let mut node_pool: HashMap<Multiaddr, _> = HashMap::new();
|
||||
let mut pending_connection_notifications: Vec<_> = Vec::new();
|
||||
|
||||
loop {
|
||||
futures::select! {
|
||||
message = self.message_receiver.next() => Self::process_message(
|
||||
message,
|
||||
&mut node_pool,
|
||||
&node_map,
|
||||
).await,
|
||||
init_payload = self.register_receiver.next() => Self::process_register(
|
||||
init_payload,
|
||||
&mut node_pool,
|
||||
&mut node_map,
|
||||
&mut pending_connection_notifications,
|
||||
).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_register(
|
||||
input: Option<Register>,
|
||||
node_pool: &mut HashMap<Multiaddr, Node<WsTrans>>,
|
||||
node_map: &mut HashMap<Id, Vec<(VerbosityLevel, Multiaddr)>>,
|
||||
pending_connection_notifications: &mut Vec<(Multiaddr, ConnectionNotifierSender)>,
|
||||
) {
|
||||
let input = input.expect("the stream is never closed; qed");
|
||||
|
||||
match input {
|
||||
Register::Telemetry { id, endpoints, connection_message } => {
|
||||
let endpoints = endpoints.0;
|
||||
|
||||
let connection_message = match serde_json::to_value(&connection_message) {
|
||||
Ok(serde_json::Value::Object(mut value)) => {
|
||||
value.insert("msg".into(), "system.connected".into());
|
||||
let mut obj = serde_json::Map::new();
|
||||
obj.insert("id".to_string(), id.into());
|
||||
obj.insert("payload".to_string(), value.into());
|
||||
Some(obj)
|
||||
},
|
||||
Ok(_) => {
|
||||
unreachable!("ConnectionMessage always serialize to an object; qed")
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "telemetry",
|
||||
"Could not serialize connection message: {}",
|
||||
err,
|
||||
);
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
for (addr, verbosity) in endpoints {
|
||||
log::trace!(
|
||||
target: "telemetry",
|
||||
"Initializing telemetry for: {:?}",
|
||||
addr,
|
||||
);
|
||||
node_map.entry(id).or_default().push((verbosity, addr.clone()));
|
||||
|
||||
let node = match node_pool.entry(addr.clone()) {
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
Vacant(entry) => {
|
||||
let transport = initialize_transport();
|
||||
let transport = match transport {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "telemetry",
|
||||
"Could not initialise transport: {}",
|
||||
err,
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
entry.insert(Node::new(transport, addr.clone(), Vec::new(), Vec::new()))
|
||||
},
|
||||
};
|
||||
|
||||
node.connection_messages.extend(connection_message.clone());
|
||||
|
||||
pending_connection_notifications.retain(|(addr_b, connection_message)| {
|
||||
if *addr_b == addr {
|
||||
node.telemetry_connection_notifier.push(connection_message.clone());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
Register::Notifier { addresses, connection_notifier } => {
|
||||
for addr in addresses {
|
||||
// If the Node has been initialized, we directly push the connection_notifier.
|
||||
// Otherwise we push it to a queue that will be consumed when the connection
|
||||
// initializes, thus ensuring that the connection notifier will be sent to the
|
||||
// Node when it becomes available.
|
||||
if let Some(node) = node_pool.get_mut(&addr) {
|
||||
node.telemetry_connection_notifier.push(connection_notifier.clone());
|
||||
} else {
|
||||
pending_connection_notifications.push((addr, connection_notifier.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch messages to the telemetry nodes
|
||||
async fn process_message(
|
||||
input: Option<TelemetryMessage>,
|
||||
node_pool: &mut HashMap<Multiaddr, Node<WsTrans>>,
|
||||
node_map: &HashMap<Id, Vec<(VerbosityLevel, Multiaddr)>>,
|
||||
) {
|
||||
let (id, verbosity, payload) = input.expect("the stream is never closed; qed");
|
||||
|
||||
let ts = chrono::Local::now().to_rfc3339();
|
||||
let mut message = serde_json::Map::new();
|
||||
message.insert("id".into(), id.into());
|
||||
message.insert("ts".into(), ts.into());
|
||||
message.insert("payload".into(), payload.into());
|
||||
|
||||
let nodes = if let Some(nodes) = node_map.get(&id) {
|
||||
nodes
|
||||
} else {
|
||||
// This is a normal error because the telemetry ID exists before the telemetry is
|
||||
// initialized.
|
||||
log::trace!(
|
||||
target: "telemetry",
|
||||
"Received telemetry log for unknown id ({:?}): {}",
|
||||
id,
|
||||
serde_json::to_string(&message)
|
||||
.unwrap_or_else(|err| format!(
|
||||
"could not be serialized ({}): {:?}",
|
||||
err,
|
||||
message,
|
||||
)),
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
for (node_max_verbosity, addr) in nodes {
|
||||
if verbosity > *node_max_verbosity {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(node) = node_pool.get_mut(addr) {
|
||||
let _ = node.send(message.clone()).await;
|
||||
} else {
|
||||
log::debug!(
|
||||
target: "telemetry",
|
||||
"Received message for unknown node ({}). This is a bug. \
|
||||
Message sent: {}",
|
||||
addr,
|
||||
serde_json::to_string(&message)
|
||||
.unwrap_or_else(|err| format!(
|
||||
"could not be serialized ({}): {:?}",
|
||||
err,
|
||||
message,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to the [`TelemetryWorker`] thats allows initializing the telemetry for a Bizinikiwi node.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TelemetryWorkerHandle {
|
||||
message_sender: mpsc::Sender<TelemetryMessage>,
|
||||
register_sender: TracingUnboundedSender<Register>,
|
||||
id_counter: Arc<atomic::AtomicU64>,
|
||||
}
|
||||
|
||||
impl TelemetryWorkerHandle {
|
||||
/// Instantiate a new [`Telemetry`] object.
|
||||
pub fn new_telemetry(&mut self, endpoints: TelemetryEndpoints) -> Telemetry {
|
||||
let addresses = endpoints.0.iter().map(|(addr, _)| addr.clone()).collect();
|
||||
|
||||
Telemetry {
|
||||
message_sender: self.message_sender.clone(),
|
||||
register_sender: self.register_sender.clone(),
|
||||
id: self.id_counter.fetch_add(1, atomic::Ordering::Relaxed),
|
||||
connection_notifier: TelemetryConnectionNotifier {
|
||||
register_sender: self.register_sender.clone(),
|
||||
addresses,
|
||||
},
|
||||
endpoints: Some(endpoints),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A telemetry instance that can be used to send telemetry messages.
|
||||
#[derive(Debug)]
|
||||
pub struct Telemetry {
|
||||
message_sender: mpsc::Sender<TelemetryMessage>,
|
||||
register_sender: TracingUnboundedSender<Register>,
|
||||
id: Id,
|
||||
connection_notifier: TelemetryConnectionNotifier,
|
||||
endpoints: Option<TelemetryEndpoints>,
|
||||
}
|
||||
|
||||
impl Telemetry {
|
||||
/// Initialize the telemetry with the endpoints provided in argument for the current bizinikiwi
|
||||
/// node.
|
||||
///
|
||||
/// This method must be called during the bizinikiwi node initialization.
|
||||
///
|
||||
/// The `endpoints` argument is a collection of telemetry WebSocket servers with a corresponding
|
||||
/// verbosity level.
|
||||
///
|
||||
/// The `connection_message` argument is a JSON object that is sent every time the connection
|
||||
/// (re-)establishes.
|
||||
pub fn start_telemetry(&mut self, connection_message: ConnectionMessage) -> Result<()> {
|
||||
let endpoints = self.endpoints.take().ok_or(Error::TelemetryAlreadyInitialized)?;
|
||||
|
||||
self.register_sender
|
||||
.unbounded_send(Register::Telemetry { id: self.id, endpoints, connection_message })
|
||||
.map_err(|_| Error::TelemetryWorkerDropped)
|
||||
}
|
||||
|
||||
/// Make a new clonable handle to this [`Telemetry`]. This is used for reporting telemetries.
|
||||
pub fn handle(&self) -> TelemetryHandle {
|
||||
TelemetryHandle {
|
||||
message_sender: Arc::new(Mutex::new(self.message_sender.clone())),
|
||||
id: self.id,
|
||||
connection_notifier: self.connection_notifier.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to a [`Telemetry`].
|
||||
///
|
||||
/// Used to report telemetry messages.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TelemetryHandle {
|
||||
message_sender: Arc<Mutex<mpsc::Sender<TelemetryMessage>>>,
|
||||
id: Id,
|
||||
connection_notifier: TelemetryConnectionNotifier,
|
||||
}
|
||||
|
||||
impl TelemetryHandle {
|
||||
/// Send telemetry messages.
|
||||
pub fn send_telemetry(&self, verbosity: VerbosityLevel, payload: TelemetryPayload) {
|
||||
match self.message_sender.lock().try_send((self.id, verbosity, payload)) {
|
||||
Ok(()) => {},
|
||||
Err(err) if err.is_full() => log::trace!(
|
||||
target: "telemetry",
|
||||
"Telemetry channel full.",
|
||||
),
|
||||
Err(_) => log::trace!(
|
||||
target: "telemetry",
|
||||
"Telemetry channel closed.",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get event stream for telemetry connection established events.
|
||||
///
|
||||
/// This function will return an error if the telemetry has already been started by
|
||||
/// [`Telemetry::start_telemetry`].
|
||||
pub fn on_connect_stream(&self) -> ConnectionNotifierReceiver {
|
||||
self.connection_notifier.on_connect_stream()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to create a stream of events with only one event: when a telemetry connection
|
||||
/// (re-)establishes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TelemetryConnectionNotifier {
|
||||
register_sender: TracingUnboundedSender<Register>,
|
||||
addresses: Vec<Multiaddr>,
|
||||
}
|
||||
|
||||
impl TelemetryConnectionNotifier {
|
||||
fn on_connect_stream(&self) -> ConnectionNotifierReceiver {
|
||||
let (message_sender, message_receiver) = connection_notifier_channel();
|
||||
if let Err(err) = self.register_sender.unbounded_send(Register::Notifier {
|
||||
addresses: self.addresses.clone(),
|
||||
connection_notifier: message_sender,
|
||||
}) {
|
||||
error!(
|
||||
target: "telemetry",
|
||||
"Could not create a telemetry connection notifier: \
|
||||
the telemetry is probably already running: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
message_receiver
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Register {
|
||||
Telemetry { id: Id, endpoints: TelemetryEndpoints, connection_message: ConnectionMessage },
|
||||
Notifier { addresses: Vec<Multiaddr>, connection_notifier: ConnectionNotifierSender },
|
||||
}
|
||||
|
||||
/// Report a telemetry.
|
||||
///
|
||||
/// Translates to `tracing::info`, but contains an additional verbosity parameter which the log
|
||||
/// record is tagged with. Additionally the verbosity parameter is added to the record as a
|
||||
/// key-value pair.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use pezsc_telemetry::*;
|
||||
/// # let authority_id = 42_u64;
|
||||
/// # let set_id = (43_u64, 44_u64);
|
||||
/// # let authorities = vec![45_u64];
|
||||
/// # let telemetry: Option<TelemetryHandle> = None;
|
||||
/// telemetry!(
|
||||
/// telemetry; // an `Option<TelemetryHandle>`
|
||||
/// CONSENSUS_INFO;
|
||||
/// "afg.authority_set";
|
||||
/// "authority_id" => authority_id.to_string(),
|
||||
/// "authority_set_id" => ?set_id,
|
||||
/// "authorities" => authorities,
|
||||
/// );
|
||||
/// ```
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! telemetry {
|
||||
( $telemetry:expr; $verbosity:expr; $msg:expr; $( $t:tt )* ) => {{
|
||||
if let Some(telemetry) = $telemetry.as_ref() {
|
||||
let verbosity: $crate::VerbosityLevel = $verbosity;
|
||||
match format_fields_to_json!($($t)*) {
|
||||
Err(err) => {
|
||||
$crate::log::debug!(
|
||||
target: "telemetry",
|
||||
"Could not serialize value for telemetry: {}",
|
||||
err,
|
||||
);
|
||||
},
|
||||
Ok(mut json) => {
|
||||
json.insert("msg".into(), $msg.into());
|
||||
telemetry.send_telemetry(verbosity, json);
|
||||
},
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
#[doc(hidden)]
|
||||
macro_rules! format_fields_to_json {
|
||||
( $k:literal => $v:expr $(,)? $(, $($t:tt)+ )? ) => {{
|
||||
$crate::serde_json::to_value(&$v)
|
||||
.map(|value| {
|
||||
let mut map = $crate::serde_json::Map::new();
|
||||
map.insert($k.into(), value);
|
||||
map
|
||||
})
|
||||
$(
|
||||
.and_then(|mut prev_map| {
|
||||
format_fields_to_json!($($t)*)
|
||||
.map(move |mut other_map| {
|
||||
prev_map.append(&mut other_map);
|
||||
prev_map
|
||||
})
|
||||
})
|
||||
)*
|
||||
}};
|
||||
( $k:literal => ? $v:expr $(,)? $(, $($t:tt)+ )? ) => {{
|
||||
let mut map = $crate::serde_json::Map::new();
|
||||
map.insert($k.into(), std::format!("{:?}", &$v).into());
|
||||
$crate::serde_json::Result::Ok(map)
|
||||
$(
|
||||
.and_then(|mut prev_map| {
|
||||
format_fields_to_json!($($t)*)
|
||||
.map(move |mut other_map| {
|
||||
prev_map.append(&mut other_map);
|
||||
prev_map
|
||||
})
|
||||
})
|
||||
)*
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::TelemetryPayload;
|
||||
use futures::{channel::mpsc, prelude::*};
|
||||
use libp2p::{
|
||||
core::{
|
||||
transport::{DialOpts, PortUse, Transport},
|
||||
Endpoint,
|
||||
},
|
||||
Multiaddr,
|
||||
};
|
||||
use rand::Rng as _;
|
||||
use std::{
|
||||
fmt, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use wasm_timer::Delay;
|
||||
|
||||
pub(crate) type ConnectionNotifierSender = mpsc::Sender<()>;
|
||||
pub(crate) type ConnectionNotifierReceiver = mpsc::Receiver<()>;
|
||||
|
||||
pub(crate) fn connection_notifier_channel() -> (ConnectionNotifierSender, ConnectionNotifierReceiver)
|
||||
{
|
||||
mpsc::channel(0)
|
||||
}
|
||||
|
||||
/// Handler for a single telemetry node.
|
||||
///
|
||||
/// This is a wrapper `Sink` around a network `Sink` with 3 particularities:
|
||||
/// - It is infallible: if the connection stops, it will reconnect automatically when the server
|
||||
/// becomes available again.
|
||||
/// - It holds a list of "connection messages" which are sent automatically when the connection is
|
||||
/// (re-)established. This is used for the "system.connected" message that needs to be send for
|
||||
/// every bizinikiwi node that connects.
|
||||
/// - It doesn't stay in pending while waiting for connection. Instead, it moves data into the void
|
||||
/// if the connection could not be established. This is important for the `Dispatcher` `Sink`
|
||||
/// which we don't want to block if one connection is broken.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Node<TTrans: Transport> {
|
||||
/// Address of the node.
|
||||
addr: Multiaddr,
|
||||
/// State of the connection.
|
||||
socket: NodeSocket<TTrans>,
|
||||
/// Transport used to establish new connections.
|
||||
transport: TTrans,
|
||||
/// Messages that are sent when the connection (re-)establishes.
|
||||
pub(crate) connection_messages: Vec<TelemetryPayload>,
|
||||
/// Notifier for when the connection (re-)establishes.
|
||||
pub(crate) telemetry_connection_notifier: Vec<ConnectionNotifierSender>,
|
||||
}
|
||||
|
||||
enum NodeSocket<TTrans: Transport> {
|
||||
/// We're connected to the node. This is the normal state.
|
||||
Connected(NodeSocketConnected<TTrans>),
|
||||
/// We are currently dialing the node.
|
||||
Dialing(TTrans::Dial),
|
||||
/// A new connection should be started as soon as possible.
|
||||
ReconnectNow,
|
||||
/// Waiting before attempting to dial again.
|
||||
WaitingReconnect(Delay),
|
||||
/// Temporary transition state.
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
impl<TTrans: Transport> NodeSocket<TTrans> {
|
||||
fn wait_reconnect() -> NodeSocket<TTrans> {
|
||||
let random_delay = rand::thread_rng().gen_range(10..20);
|
||||
let delay = Delay::new(Duration::from_secs(random_delay));
|
||||
log::trace!(target: "telemetry", "Pausing for {} secs before reconnecting", random_delay);
|
||||
NodeSocket::WaitingReconnect(delay)
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeSocketConnected<TTrans: Transport> {
|
||||
/// Where to send data.
|
||||
sink: TTrans::Output,
|
||||
/// Queue of packets to send before accepting new packets.
|
||||
buf: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<TTrans: Transport> Node<TTrans> {
|
||||
/// Builds a new node handler.
|
||||
pub(crate) fn new(
|
||||
transport: TTrans,
|
||||
addr: Multiaddr,
|
||||
connection_messages: Vec<serde_json::Map<String, serde_json::Value>>,
|
||||
telemetry_connection_notifier: Vec<ConnectionNotifierSender>,
|
||||
) -> Self {
|
||||
Node {
|
||||
addr,
|
||||
socket: NodeSocket::ReconnectNow,
|
||||
transport,
|
||||
connection_messages,
|
||||
telemetry_connection_notifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TTrans: Transport, TSinkErr> Node<TTrans>
|
||||
where
|
||||
TTrans::Dial: Unpin,
|
||||
TTrans::Output:
|
||||
Sink<Vec<u8>, Error = TSinkErr> + Stream<Item = Result<Vec<u8>, TSinkErr>> + Unpin,
|
||||
TSinkErr: fmt::Debug,
|
||||
{
|
||||
// NOTE: this code has been inspired from `Buffer` (`futures_util::sink::Buffer`).
|
||||
// https://docs.rs/futures-util/0.3.8/src/futures_util/sink/buffer.rs.html#32
|
||||
fn try_send_connection_messages(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
conn: &mut NodeSocketConnected<TTrans>,
|
||||
) -> Poll<Result<(), TSinkErr>> {
|
||||
while let Some(item) = conn.buf.pop() {
|
||||
if let Err(e) = conn.sink.start_send_unpin(item) {
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
futures::ready!(conn.sink.poll_ready_unpin(cx))?;
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Infallible {}
|
||||
|
||||
impl<TTrans: Transport, TSinkErr> Sink<TelemetryPayload> for Node<TTrans>
|
||||
where
|
||||
TTrans: Unpin,
|
||||
TTrans::Dial: Unpin,
|
||||
TTrans::Output:
|
||||
Sink<Vec<u8>, Error = TSinkErr> + Stream<Item = Result<Vec<u8>, TSinkErr>> + Unpin,
|
||||
TSinkErr: fmt::Debug,
|
||||
{
|
||||
type Error = Infallible;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
let mut socket = mem::replace(&mut self.socket, NodeSocket::Poisoned);
|
||||
self.socket = loop {
|
||||
match socket {
|
||||
NodeSocket::Connected(mut conn) => match conn.sink.poll_ready_unpin(cx) {
|
||||
Poll::Ready(Ok(())) => {
|
||||
match self.as_mut().try_send_connection_messages(cx, &mut conn) {
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::warn!(target: "telemetry", "⚠️ Disconnected from {}: {:?}", self.addr, err);
|
||||
socket = NodeSocket::wait_reconnect();
|
||||
},
|
||||
Poll::Ready(Ok(())) => {
|
||||
self.socket = NodeSocket::Connected(conn);
|
||||
return Poll::Ready(Ok(()));
|
||||
},
|
||||
Poll::Pending => {
|
||||
self.socket = NodeSocket::Connected(conn);
|
||||
return Poll::Pending;
|
||||
},
|
||||
}
|
||||
},
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::warn!(target: "telemetry", "⚠️ Disconnected from {}: {:?}", self.addr, err);
|
||||
socket = NodeSocket::wait_reconnect();
|
||||
},
|
||||
Poll::Pending => {
|
||||
self.socket = NodeSocket::Connected(conn);
|
||||
return Poll::Pending;
|
||||
},
|
||||
},
|
||||
NodeSocket::Dialing(mut s) => match Future::poll(Pin::new(&mut s), cx) {
|
||||
Poll::Ready(Ok(sink)) => {
|
||||
log::debug!(target: "telemetry", "✅ Connected to {}", self.addr);
|
||||
|
||||
{
|
||||
let mut index = 0;
|
||||
while index < self.telemetry_connection_notifier.len() {
|
||||
let sender = &mut self.telemetry_connection_notifier[index];
|
||||
if let Err(error) = sender.try_send(()) {
|
||||
if !error.is_disconnected() {
|
||||
log::debug!(target: "telemetry", "Failed to send a telemetry connection notification: {}", error);
|
||||
} else {
|
||||
self.telemetry_connection_notifier.swap_remove(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let buf = self
|
||||
.connection_messages
|
||||
.iter()
|
||||
.map(|json| {
|
||||
let mut json = json.clone();
|
||||
json.insert(
|
||||
"ts".to_string(),
|
||||
chrono::Local::now().to_rfc3339().into(),
|
||||
);
|
||||
json
|
||||
})
|
||||
.filter_map(|json| match serde_json::to_vec(&json) {
|
||||
Ok(message) => Some(message),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "telemetry",
|
||||
"An error occurred while generating new connection \
|
||||
messages: {}",
|
||||
err,
|
||||
);
|
||||
None
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
socket = NodeSocket::Connected(NodeSocketConnected { sink, buf });
|
||||
},
|
||||
Poll::Pending => break NodeSocket::Dialing(s),
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::warn!(target: "telemetry", "❌ Error while dialing {}: {:?}", self.addr, err);
|
||||
socket = NodeSocket::wait_reconnect();
|
||||
},
|
||||
},
|
||||
NodeSocket::ReconnectNow => {
|
||||
let addr = self.addr.clone();
|
||||
match self
|
||||
.transport
|
||||
.dial(addr, DialOpts { role: Endpoint::Dialer, port_use: PortUse::New })
|
||||
{
|
||||
Ok(d) => {
|
||||
log::trace!(target: "telemetry", "Re-dialing {}", self.addr);
|
||||
socket = NodeSocket::Dialing(d);
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!(target: "telemetry", "❌ Error while re-dialing {}: {:?}", self.addr, err);
|
||||
socket = NodeSocket::wait_reconnect();
|
||||
},
|
||||
}
|
||||
},
|
||||
NodeSocket::WaitingReconnect(mut s) => {
|
||||
if Future::poll(Pin::new(&mut s), cx).is_ready() {
|
||||
socket = NodeSocket::ReconnectNow;
|
||||
} else {
|
||||
break NodeSocket::WaitingReconnect(s);
|
||||
}
|
||||
},
|
||||
NodeSocket::Poisoned => {
|
||||
log::error!(target: "telemetry", "‼️ Poisoned connection with {}", self.addr);
|
||||
break NodeSocket::Poisoned;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// The Dispatcher blocks when the Node syncs blocks. This is why it is important that the
|
||||
// Node sinks don't go into "Pending" state while waiting for reconnection but rather
|
||||
// discard the excess of telemetry messages.
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: TelemetryPayload) -> Result<(), Self::Error> {
|
||||
// Any buffered outgoing telemetry messages are discarded while (re-)connecting.
|
||||
match &mut self.socket {
|
||||
NodeSocket::Connected(conn) => match serde_json::to_vec(&item) {
|
||||
Ok(data) => {
|
||||
log::trace!(target: "telemetry", "Sending {} bytes", data.len());
|
||||
let _ = conn.sink.start_send_unpin(data);
|
||||
},
|
||||
Err(err) => log::debug!(
|
||||
target: "telemetry",
|
||||
"Could not serialize payload: {}",
|
||||
err,
|
||||
),
|
||||
},
|
||||
// We are currently dialing the node.
|
||||
NodeSocket::Dialing(_) => log::trace!(target: "telemetry", "Dialing"),
|
||||
// A new connection should be started as soon as possible.
|
||||
NodeSocket::ReconnectNow => log::trace!(target: "telemetry", "Reconnecting"),
|
||||
// Waiting before attempting to dial again.
|
||||
NodeSocket::WaitingReconnect(_) => {},
|
||||
// Temporary transition state.
|
||||
NodeSocket::Poisoned => log::trace!(target: "telemetry", "Poisoned"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match &mut self.socket {
|
||||
NodeSocket::Connected(conn) => match conn.sink.poll_flush_unpin(cx) {
|
||||
Poll::Ready(Err(e)) => {
|
||||
// When `telemetry` closes the websocket connection we end
|
||||
// up here, which is sub-optimal. See
|
||||
// https://github.com/libp2p/rust-libp2p/issues/2021 for
|
||||
// what we could do to improve this.
|
||||
log::trace!(target: "telemetry", "[poll_flush] Error: {:?}", e);
|
||||
self.socket = NodeSocket::wait_reconnect();
|
||||
Poll::Ready(Ok(()))
|
||||
},
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
||||
Poll::Pending => Poll::Pending,
|
||||
},
|
||||
_ => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match &mut self.socket {
|
||||
NodeSocket::Connected(conn) => conn.sink.poll_close_unpin(cx).map(|_| Ok(())),
|
||||
_ => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TTrans: Transport> fmt::Debug for NodeSocket<TTrans> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use NodeSocket::*;
|
||||
f.write_str(match self {
|
||||
Connected(_) => "Connected",
|
||||
Dialing(_) => "Dialing",
|
||||
ReconnectNow => "ReconnectNow",
|
||||
WaitingReconnect(_) => "WaitingReconnect",
|
||||
Poisoned => "Poisoned",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{
|
||||
prelude::*,
|
||||
ready,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use libp2p::{core::transport::timeout::TransportTimeout, Transport};
|
||||
use std::{io, pin::Pin, time::Duration};
|
||||
|
||||
/// Timeout after which a connection attempt is considered failed. Includes the WebSocket HTTP
|
||||
/// upgrading.
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
|
||||
pub(crate) fn initialize_transport() -> Result<WsTrans, io::Error> {
|
||||
let transport = {
|
||||
let tcp_transport = libp2p::tcp::tokio::Transport::new(libp2p::tcp::Config::new());
|
||||
let inner = libp2p::dns::tokio::Transport::system(tcp_transport)?;
|
||||
libp2p::websocket::framed::WsConfig::new(inner).and_then(|connec, _| {
|
||||
let connec = connec
|
||||
.with(|item| {
|
||||
let item = libp2p::websocket::framed::OutgoingData::Binary(item);
|
||||
future::ready(Ok::<_, io::Error>(item))
|
||||
})
|
||||
.try_filter_map(|item| async move {
|
||||
if let libp2p::websocket::framed::Incoming::Data(data) = item {
|
||||
Ok(Some(data.into_bytes()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
future::ready(Ok::<_, io::Error>(connec))
|
||||
})
|
||||
};
|
||||
|
||||
Ok(TransportTimeout::new(
|
||||
transport.map(|out, _| {
|
||||
let out = out
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||
.sink_map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
||||
Box::pin(out) as Pin<Box<_>>
|
||||
}),
|
||||
CONNECT_TIMEOUT,
|
||||
)
|
||||
.boxed())
|
||||
}
|
||||
|
||||
/// A trait that implements `Stream` and `Sink`.
|
||||
pub(crate) trait StreamAndSink<I>: Stream + Sink<I> {}
|
||||
impl<T: ?Sized + Stream + Sink<I>, I> StreamAndSink<I> for T {}
|
||||
|
||||
/// A type alias for the WebSocket transport.
|
||||
pub(crate) type WsTrans = libp2p::core::transport::Boxed<
|
||||
Pin<
|
||||
Box<
|
||||
dyn StreamAndSink<Vec<u8>, Item = Result<Vec<u8>, io::Error>, Error = io::Error> + Send,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// Wraps around an `AsyncWrite` and implements `Sink`. Guarantees that each item being sent maps
|
||||
/// to one call of `write`.
|
||||
#[pin_project::pin_project]
|
||||
pub(crate) struct StreamSink<T>(#[pin] T, Option<Vec<u8>>);
|
||||
|
||||
impl<T> From<T> for StreamSink<T> {
|
||||
fn from(inner: T) -> StreamSink<T> {
|
||||
StreamSink(inner, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead> Stream for StreamSink<T> {
|
||||
type Item = Result<Vec<u8>, io::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
let mut buf = vec![0; 128];
|
||||
match ready!(AsyncRead::poll_read(this.0, cx, &mut buf)) {
|
||||
Ok(0) => Poll::Ready(None),
|
||||
Ok(n) => {
|
||||
buf.truncate(n);
|
||||
Poll::Ready(Some(Ok(buf)))
|
||||
},
|
||||
Err(err) => Poll::Ready(Some(Err(err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite> StreamSink<T> {
|
||||
fn poll_flush_buffer(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
|
||||
let this = self.project();
|
||||
|
||||
if let Some(buffer) = this.1 {
|
||||
if ready!(this.0.poll_write(cx, &buffer[..]))? != buffer.len() {
|
||||
log::error!(target: "telemetry",
|
||||
"Detected some internal buffering happening in the telemetry");
|
||||
let err = io::Error::new(io::ErrorKind::Other, "Internal buffering detected");
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
}
|
||||
|
||||
*this.1 = None;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite> Sink<Vec<u8>> for StreamSink<T> {
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(StreamSink::poll_flush_buffer(self, cx))?;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: Vec<u8>) -> Result<(), Self::Error> {
|
||||
let this = self.project();
|
||||
debug_assert!(this.1.is_none());
|
||||
*this.1 = Some(item);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.as_mut().poll_flush_buffer(cx))?;
|
||||
let this = self.project();
|
||||
AsyncWrite::poll_flush(this.0, cx)
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.as_mut().poll_flush_buffer(cx))?;
|
||||
let this = self.project();
|
||||
AsyncWrite::poll_close(this.0, cx)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user