use crate::oblivious_transfer_units::ObliviousTransferUnits;
use chrono::{DateTime, LocalResult, TimeZone, Utc};
use lazy_static::lazy_static;
pub use libp2p_identity::PeerId;
use std::fmt::Display;
use tonic::transport::Uri;
use crate::quilibrium_pb::node::node::pb::GetFrameInfoRequest;
use crate::quilibrium_pb::node::{
clock::pb::{self as clock_pb},
node::pb::{self as node_pb, node_service_client::NodeServiceClient},
};
#[derive(Debug, Clone)]
pub struct NodeClient {
client: NodeServiceClient<tonic::transport::Channel>,
}
const MAX_DECODING_MESSAGE_SIZE_BYTES: usize = 25 * 1024 * 1024;
impl NodeClient {
pub async fn new(uri: Uri) -> Result<Self, NodeClientError> {
let client = NodeServiceClient::connect(uri)
.await?
.max_decoding_message_size(MAX_DECODING_MESSAGE_SIZE_BYTES);
Ok(Self { client })
}
pub async fn frames(
&mut self,
options: FramesOptions,
) -> Result<FramesResponse, NodeClientError> {
let request = tonic::Request::new(options.into());
let response = self.client.get_frames(request).await?;
response.into_inner().try_into()
}
pub async fn frame_info(
&mut self,
filter: FrameFilter,
frame_number: u64,
) -> Result<Option<clock_pb::ClockFrame>, NodeClientError> {
let request = tonic::Request::new(GetFrameInfoRequest {
filter: filter.into(),
frame_number,
selector: vec![],
});
let response = self.client.get_frame_info(request).await?;
Ok(response.into_inner().clock_frame)
}
pub async fn network_info(&mut self) -> Result<NetworkInfoResponse, NodeClientError> {
let request = tonic::Request::new(node_pb::GetNetworkInfoRequest {});
let response = self.client.get_network_info(request).await?;
response.into_inner().try_into()
}
pub async fn peer_info(&mut self) -> Result<PeerInfoResponse, NodeClientError> {
let request = tonic::Request::new(node_pb::GetPeerInfoRequest {});
let response = self.client.get_peer_info(request).await?;
response.into_inner().try_into()
}
pub async fn token_info(&mut self) -> Result<TokenInfo, NodeClientError> {
let request = tonic::Request::new(node_pb::GetTokenInfoRequest {});
let response = self.client.get_token_info(request).await?;
response.into_inner().try_into()
}
}
#[derive(Debug, thiserror::Error)]
pub enum NodeClientError {
#[error("Invalid frame filter")]
InvalidFrameFilter,
#[error(transparent)]
InvalidMultiaddr(#[from] multiaddr::Error),
#[error(transparent)]
InvalidPeerId(#[from] libp2p_identity::ParseError),
#[error("Invalid Unix timestamp: {0}")]
InvalidTimestamp(i64),
#[error("Invalid protocol version triple: {0:?}")]
InvalidVersion(Vec<u8>),
#[error(transparent)]
QuilTokenError(#[from] crate::oblivious_transfer_units::QuilTokenError),
#[error(transparent)]
Status(#[from] tonic::Status),
#[error(transparent)]
Transport(#[from] tonic::transport::Error),
}
pub struct FramesOptions {
pub filter: FrameFilter,
pub from_frame_number: u64,
pub to_frame_number: u64,
pub include_candidates: bool,
}
impl Default for FramesOptions {
fn default() -> Self {
Self {
filter: FrameFilter::MasterClock,
from_frame_number: 0,
to_frame_number: 1,
include_candidates: false,
}
}
}
impl FramesOptions {
pub fn new() -> Self {
Self::default()
}
pub fn filter(mut self, filter: FrameFilter) -> Self {
self.filter = filter;
self
}
pub fn from_frame_number(mut self, from_frame_number: u64) -> Self {
self.from_frame_number = from_frame_number;
self
}
pub fn to_frame_number(mut self, to_frame_number: u64) -> Self {
self.to_frame_number = to_frame_number;
self
}
pub fn include_candidates(mut self, include_candidates: bool) -> Self {
self.include_candidates = include_candidates;
self
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FramesResponse {
pub truncated_clock_frames: Vec<ClockFrame>,
}
impl TryFrom<node_pb::FramesResponse> for FramesResponse {
type Error = NodeClientError;
fn try_from(value: node_pb::FramesResponse) -> Result<Self, Self::Error> {
Ok(Self {
truncated_clock_frames: value
.truncated_clock_frames
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
}
impl From<FramesOptions> for node_pb::GetFramesRequest {
fn from(value: FramesOptions) -> Self {
Self {
filter: value.filter.into(),
from_frame_number: value.from_frame_number,
to_frame_number: value.to_frame_number,
include_candidates: value.include_candidates,
}
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ClockFrame {
pub filter: FrameFilter,
pub frame_number: u64,
#[serde(with = "chrono::serde::ts_milliseconds")]
pub timestamp: DateTime<Utc>,
pub difficulty: u32,
}
impl TryFrom<clock_pb::ClockFrame> for ClockFrame {
type Error = NodeClientError;
fn try_from(value: clock_pb::ClockFrame) -> Result<Self, Self::Error> {
Ok(Self {
filter: value.filter.try_into()?,
frame_number: value.frame_number,
timestamp: convert_timestamp(value.timestamp)?,
difficulty: value.difficulty,
})
}
}
const FRAME_FILTER_BYTES: usize = 32;
lazy_static! {
static ref MASTER_CLOCK_FRAME_FILTER: [u8; FRAME_FILTER_BYTES] =
hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
.expect("valid hex")
.try_into()
.expect("FRAME_FILTER_BYTES long");
static ref CEREMONY_APPLICATION_FRAME_FILTER: [u8; FRAME_FILTER_BYTES] =
hex::decode("34001BE7432C2E6669ADA0279788682AB9F62671B1B538AB99504694D981CBD3")
.expect("valid hex")
.try_into()
.expect("FRAME_FILTER_BYTES long");
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum FrameFilter {
CeremonyApplication,
MasterClock,
Unknown([u8; FRAME_FILTER_BYTES]),
}
impl Display for FrameFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
FrameFilter::CeremonyApplication => write!(f, "ceremony-application"),
FrameFilter::MasterClock => write!(f, "master-clock"),
FrameFilter::Unknown(filter) => write!(f, "unknown-{}", hex::encode(filter)),
}
}
}
impl TryFrom<Vec<u8>> for FrameFilter {
type Error = NodeClientError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value == MASTER_CLOCK_FRAME_FILTER.as_slice() {
Ok(Self::MasterClock)
} else if value == CEREMONY_APPLICATION_FRAME_FILTER.as_slice() {
Ok(Self::CeremonyApplication)
} else if value.len() == FRAME_FILTER_BYTES {
Ok(Self::Unknown(value.try_into().expect("checked length")))
} else {
Err(NodeClientError::InvalidFrameFilter)
}
}
}
impl From<FrameFilter> for Vec<u8> {
fn from(value: FrameFilter) -> Self {
match value {
FrameFilter::CeremonyApplication => CEREMONY_APPLICATION_FRAME_FILTER.to_vec(),
FrameFilter::MasterClock => MASTER_CLOCK_FRAME_FILTER.to_vec(),
FrameFilter::Unknown(value) => value.to_vec(),
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NetworkInfoResponse {
pub network_info: Vec<NetworkInfo>,
}
impl TryFrom<node_pb::NetworkInfoResponse> for NetworkInfoResponse {
type Error = NodeClientError;
fn try_from(value: node_pb::NetworkInfoResponse) -> Result<Self, Self::Error> {
Ok(Self {
network_info: value
.network_info
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NetworkInfo {
pub peer_id: PeerId,
pub multiaddrs: Vec<multiaddr::Multiaddr>,
pub peer_score: f64,
}
impl TryFrom<node_pb::NetworkInfo> for NetworkInfo {
type Error = NodeClientError;
fn try_from(value: node_pb::NetworkInfo) -> Result<Self, Self::Error> {
Ok(Self {
peer_id: PeerId::from_bytes(&value.peer_id)?,
multiaddrs: value
.multiaddrs
.iter()
.map(|m| m.parse())
.collect::<Result<_, _>>()?,
peer_score: value.peer_score,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PeerInfoResponse {
pub peers: Vec<PeerInfo>,
pub uncooperative_peers: Vec<PeerInfo>,
}
impl TryFrom<node_pb::PeerInfoResponse> for PeerInfoResponse {
type Error = NodeClientError;
fn try_from(value: node_pb::PeerInfoResponse) -> Result<Self, Self::Error> {
Ok(Self {
peers: value
.peer_info
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
uncooperative_peers: value
.uncooperative_peer_info
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PeerInfo {
pub peer_id: PeerId,
pub multiaddrs: Vec<multiaddr::Multiaddr>,
pub max_frame: u64,
#[serde(with = "chrono::serde::ts_milliseconds")]
pub timestamp: DateTime<Utc>,
pub version: [u8; 3],
pub signature: Vec<u8>,
pub public_key: Vec<u8>,
}
impl TryFrom<node_pb::PeerInfo> for PeerInfo {
type Error = NodeClientError;
fn try_from(value: node_pb::PeerInfo) -> Result<Self, Self::Error> {
Ok(Self {
peer_id: PeerId::from_bytes(&value.peer_id)?,
multiaddrs: value
.multiaddrs
.iter()
.map(|m| m.parse())
.collect::<Result<_, _>>()?,
max_frame: value.max_frame,
timestamp: convert_timestamp(value.timestamp)?,
version: value
.version
.try_into()
.map_err(NodeClientError::InvalidVersion)?,
signature: value.signature,
public_key: value.public_key,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TokenInfo {
pub confirmed_token_supply: ObliviousTransferUnits,
pub unconfirmed_token_supply: ObliviousTransferUnits,
pub owned_tokens: ObliviousTransferUnits,
}
impl TryFrom<node_pb::TokenInfoResponse> for TokenInfo {
type Error = NodeClientError;
fn try_from(value: node_pb::TokenInfoResponse) -> Result<Self, Self::Error> {
Ok(Self {
confirmed_token_supply: value.confirmed_token_supply.as_slice().try_into()?,
unconfirmed_token_supply: value.unconfirmed_token_supply.as_slice().try_into()?,
owned_tokens: value.owned_tokens.as_slice().try_into()?,
})
}
}
fn convert_timestamp(timestamp: i64) -> Result<DateTime<Utc>, NodeClientError> {
match Utc.timestamp_millis_opt(timestamp) {
LocalResult::Single(timestamp) => Ok(timestamp),
LocalResult::Ambiguous(_, _) => Err(NodeClientError::InvalidTimestamp(timestamp)),
LocalResult::None => Err(NodeClientError::InvalidTimestamp(timestamp)),
}
}