Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ members = [
"nym-node-status-api/nym-node-status-client",
"nym-node/nym-node-metrics",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-outfox", "nym-signers-monitor",
"nym-statistics-api",
"nym-validator-rewarder",
"nyx-chain-watcher",
Expand Down
8 changes: 8 additions & 0 deletions common/ecash-signer-check-types/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC> {
pub fn malformed_details(&self) -> bool {
self.information.parse().is_err()
}

pub fn try_get_test_result(&self) -> Option<&SignerTestResult<LS, TS, LC, TC>> {
if let SignerStatus::Tested { result } = &self.status {
Some(result)
} else {
None
}
}
}

impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC>
Expand Down
16 changes: 15 additions & 1 deletion common/zulip-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
//! ```

use crate::error::ZulipClientError;
use crate::message::{SendMessageResponse, SendableMessage};
use crate::message::{DirectMessage, SendMessageResponse, SendableMessage, StreamMessage};
use nym_bin_common::bin_info;
use nym_http_api_client::UserAgent;
use reqwest::{header, Method, RequestBuilder};
Expand Down Expand Up @@ -92,6 +92,20 @@ impl Client {
.map_err(|source| ZulipClientError::RequestDecodeFailure { source })
}

pub async fn send_direct_message(
&self,
msg: impl Into<DirectMessage>,
) -> Result<SendMessageResponse, ZulipClientError> {
self.send_message(msg.into()).await
}

pub async fn send_channel_message(
&self,
msg: impl Into<StreamMessage>,
) -> Result<SendMessageResponse, ZulipClientError> {
self.send_message(msg.into()).await
}

fn build_request(&self, method: Method, endpoint: &'static str) -> RequestBuilder {
let url = format!("{}{endpoint}", self.server_url);
trace!("posting to {url}");
Expand Down
72 changes: 62 additions & 10 deletions common/zulip-client/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub enum SendMessageResponse {
},
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum SendableMessageContent {
Expand All @@ -40,7 +40,7 @@ pub enum SendableMessageContent {
},
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub struct SendableMessage {
#[serde(flatten)]
Expand Down Expand Up @@ -117,17 +117,17 @@ impl StreamMessage {
pub fn new(
to: impl Into<ToChannel>,
content: impl Into<String>,
topic: Option<String>,
topic: impl IntoMaybeTopic,
) -> Self {
StreamMessage {
to: to.into().to_string(),
topic,
topic: topic.into_maybe_topic(),
content: content.into(),
}
}

pub fn no_topic(to: impl Into<ToChannel>, content: impl Into<String>) -> Self {
Self::new(to, content, None)
Self::new(to, content, None::<String>)
}

#[must_use]
Expand Down Expand Up @@ -194,22 +194,74 @@ impl From<StreamMessage> for SendableMessageContent {
}
}

impl<T, S> From<(T, S, Option<S>)> for StreamMessage
impl<T, S, U> From<(T, S, U)> for StreamMessage
where
T: Into<ToChannel>,
S: Into<String>,
U: IntoMaybeTopic,
{
fn from((to, content, topic): (T, S, Option<S>)) -> Self {
StreamMessage::new(to, content, topic.map(Into::into))
fn from((to, content, topic): (T, S, U)) -> Self {
StreamMessage::new(to, content, topic)
}
}

impl<T, S> From<(T, S, Option<S>)> for SendableMessage
impl<T, S> From<(T, S)> for StreamMessage
where
T: Into<ToChannel>,
S: Into<String>,
{
fn from(inner: (T, S, Option<S>)) -> Self {
fn from((to, content): (T, S)) -> Self {
StreamMessage::no_topic(to, content)
}
}

impl<T, S, U> From<(T, S, U)> for SendableMessage
where
T: Into<ToChannel>,
S: Into<String>,
U: IntoMaybeTopic,
{
fn from(inner: (T, S, U)) -> Self {
StreamMessage::from(inner).into()
}
}

pub trait IntoMaybeTopic {
fn into_maybe_topic(self) -> Option<String>;
}

impl<S> IntoMaybeTopic for &Option<S>
where
S: Into<String> + Clone,
{
fn into_maybe_topic(self) -> Option<String> {
self.clone().map(|s| s.into())
}
}

impl<S> IntoMaybeTopic for Option<S>
where
S: Into<String>,
{
fn into_maybe_topic(self) -> Option<String> {
self.map(Into::into)
}
}

impl IntoMaybeTopic for String {
fn into_maybe_topic(self) -> Option<String> {
Some(self)
}
}

impl IntoMaybeTopic for &String {
fn into_maybe_topic(self) -> Option<String> {
Some(self.clone())
}
}

impl IntoMaybeTopic for &str {
fn into_maybe_topic(self) -> Option<String> {
Some(self.to_string())
}
}
31 changes: 31 additions & 0 deletions nym-signers-monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "nym-signers-monitor"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true

[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
humantime = { workspace = true }
itertools = { workspace = true }
time = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tracing = { workspace = true }
url = { workspace = true }

nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] }
nym-ecash-signer-check = { path = "../common/ecash-signer-check" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
zulip-client = { path = "../common/zulip-client" }

[lints]
workspace = true
15 changes: 15 additions & 0 deletions nym-signers-monitor/src/cli/build_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use nym_bin_common::bin_info_owned;
use nym_bin_common::output_format::OutputFormat;

#[derive(clap::Args, Debug)]
pub(crate) struct Args {
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}

pub(crate) fn execute(args: Args) {
println!("{}", args.output.format(&bin_info_owned!()))
}
20 changes: 20 additions & 0 deletions nym-signers-monitor/src/cli/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

pub(crate) mod vars {
pub(crate) const ZULIP_BOT_EMAIL_ARG: &str = "ZULIP_BOT_EMAIL";
pub(crate) const ZULIP_BOT_API_KEY_ARG: &str = "ZULIP_BOT_API_KEY";
pub(crate) const ZULIP_SERVER_URL_ARG: &str = "ZULIP_SERVER_URL";
pub(crate) const ZULIP_NOTIFICATION_CHANNEL_ID_ARG: &str = "ZULIP_NOTIFICATION_CHANNEL_ID";
pub(crate) const ZULIP_NOTIFICATION_CHANNEL_TOPIC_ARG: &str =
"ZULIP_NOTIFICATION_CHANNEL_TOPIC";

pub(crate) const SIGNERS_MONITOR_CHECK_INTERVAL_ARG: &str = "SIGNERS_MONITOR_CHECK_INTERVAL";
pub(crate) const SIGNERS_MONITOR_MIN_NOTIFICATION_DELAY_ARG: &str =
"SIGNERS_MONITOR_MIN_NOTIFICATION_DELAY";

pub(crate) const KNOWN_NETWORK_NAME_ARG: &str = "KNOWN_NETWORK_NAME";
pub(crate) const NYXD_CLIENT_CONFIG_ENV_FILE_ARG: &str = "NYXD_CLIENT_CONFIG_ENV_FILE";
pub(crate) const NYXD_RPC_ENDPOINT_ARG: &str = "NYXD_RPC_ENDPOINT";
pub(crate) const NYXD_DKG_CONTRACT_ADDRESS_ARG: &str = "NYXD_DKG_CONTRACT_ADDRESS";
}
43 changes: 43 additions & 0 deletions nym-signers-monitor/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use std::sync::OnceLock;

pub(crate) mod build_info;
pub(crate) mod env;
pub(crate) mod run;

// Helper for passing LONG_VERSION to clap
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}

#[derive(Parser, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Cli {
#[clap(subcommand)]
command: Commands,
}

impl Cli {
pub async fn execute(self) -> anyhow::Result<()> {
match self.command {
Commands::BuildInfo(args) => build_info::execute(args),
Commands::Run(args) => run::execute(*args).await?,
}

Ok(())
}
}

#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
/// Show build information of this binary
BuildInfo(build_info::Args),

/// Start signers monitor and send notifications on any failures
Run(Box<run::Args>),
}
Loading
Loading