This commit is contained in:
Jędrzej Stuczyński
2023-12-04 14:26:22 +00:00
committed by Jon Häggblad
parent 62894e2b40
commit b4b32bb907
17 changed files with 603 additions and 5 deletions
Generated
+21 -1
View File
@@ -6687,7 +6687,6 @@ dependencies = [
"nym-types",
"nym-validator-client",
"opentelemetry",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"serde_json",
@@ -7492,6 +7491,27 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-validator-rewarder"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"clap 4.4.7",
"nym-bin-common",
"nym-config",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"serde",
"tendermint-rpc",
"thiserror",
"tokio",
"tracing",
"url",
"zeroize",
]
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
+2
View File
@@ -102,6 +102,7 @@ members = [
"nym-node",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
"tools/nym-cli",
@@ -124,6 +125,7 @@ default-members = [
"nym-api",
"tools/nymvisor",
"explorer-api",
"nym-validator-rewarder",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
+3 -4
View File
@@ -18,7 +18,7 @@ rust-version = "1.58.1"
[dependencies]
axum = { workspace = true }
anyhow = "1.0.40"
anyhow = { workspace = true }
bs58 = "0.4.0"
clap = { workspace = true, features = ["cargo", "derive"] }
colored = "2.0"
@@ -28,7 +28,6 @@ futures = { workspace = true }
humantime-serde = "1.0"
lazy_static = "1.4.0"
log = { workspace = true }
pretty_env_logger = "0.4.0"
rand = "0.7.3"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -41,7 +40,7 @@ cfg-if = "1.0.0"
thiserror = { workspace = true }
## tracing
tracing = { version = "0.1.37", optional = true }
tracing = { workspace = true, optional = true }
opentelemetry = { version = "0.19.0", optional = true }
@@ -65,7 +64,7 @@ nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
cpu-cycles = { path = "../cpu-cycles", optional = true }
[dev-dependencies]
tokio = { version = "1.21.2", features = [
tokio = { workspace = true, features = [
"rt-multi-thread",
"net",
"signal",
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "nym-validator-rewarder"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license = "GPL-3"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
bip39 = { workspace = true, features = ["zeroize"] }
clap = { workspace = true, features = ["cargo"] }
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros"] }
tracing.workspace = true
url.workspace = true
zeroize.workspace = true
tendermint-rpc = { workspace = true, features = ["websocket-client"] }
# internal
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-config = { path = "../common/config" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
@@ -0,0 +1,17 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use nym_bin_common::bin_info_owned;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args, Debug)]
pub(crate) struct Args {
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> Result<(), NymRewarderError> {
println!("{}", args.output.format(&bin_info_owned!()));
Ok(())
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NymRewarderError;
use std::path::PathBuf;
#[derive(Debug, clap::Args)]
pub struct Args {
#[command(flatten)]
config_override: ConfigOverridableArgs,
/// Specifies custom location for the configuration file of nym validators rewarder.
#[clap(long)]
custom_config_path: Option<PathBuf>,
/// Mnemonic used for rewarding operations
#[clap(long)]
mnemonic: bip39::Mnemonic,
/// Overwrite existing configuration file.
#[clap(long, short)]
force: bool,
}
#[derive(Debug, clap::Args)]
pub struct ConfigOverridableArgs {
//
}
pub(crate) fn execute(args: Args) -> Result<(), NymRewarderError> {
let path = args
.custom_config_path
.clone()
.unwrap_or(Config::default_location());
if path.exists() && !args.force {
return Err(NymRewarderError::ExistingConfig { path });
}
Config::new(args.mnemonic)
.with_override(args.config_override)
.save_to_path(&path)
.map_err(|source| NymRewarderError::ConfigSaveFailure { path, source })?;
Ok(())
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NymRewarderError;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use std::path::PathBuf;
use std::sync::OnceLock;
use tracing::{debug, error};
pub mod build_info;
pub mod init;
pub mod run;
pub mod upgrade_helpers;
// 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 {
/// Path pointing to an env file that configures the validator rewarder and overrides any preconfigured values.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Flag used for disabling the printed banner in tty.
#[clap(long)]
pub(crate) no_banner: bool,
#[clap(subcommand)]
command: Commands,
}
impl Cli {
pub(crate) async fn execute(self) -> Result<(), NymRewarderError> {
match self.command {
Commands::Init(args) => init::execute(args),
Commands::Run(args) => run::execute(args).await,
Commands::BuildInfo(args) => build_info::execute(args),
}
}
}
#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
/// Initialise a validator rewarder with persistent config.toml file.
Init(init::Args),
/// Run the validator rewarder with the preconfigured settings.
Run(run::Args),
/// Show build information of this binary
BuildInfo(build_info::Args),
}
fn try_load_current_config(custom_path: &Option<PathBuf>) -> Result<Config, NymRewarderError> {
let config_path = custom_path.clone().unwrap_or(Config::default_location());
debug!(
"attempting to load configuration file from {}",
config_path.display()
);
if let Ok(cfg) = Config::read_from_toml_file(&config_path) {
return Ok(cfg);
}
upgrade_helpers::try_upgrade_config(&config_path)?;
Config::read_from_toml_file(&config_path).map_err(|err| {
error!(
"Failed to load config. Are you sure you have run `init` before? (Error was: {err})",
);
err
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::try_load_current_config;
use crate::error::NymRewarderError;
use crate::rewarder::Rewarder;
use std::path::PathBuf;
#[derive(Debug, clap::Args)]
pub struct Args {
/// Specifies custom location for the configuration file of nym validators rewarder.
custom_config_path: Option<PathBuf>,
}
pub(crate) async fn execute(args: Args) -> Result<(), NymRewarderError> {
let config = try_load_current_config(&args.custom_config_path)?.with_override(args);
Rewarder::new(config).run().await
}
@@ -0,0 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use std::path::Path;
pub(crate) fn try_upgrade_config<P: AsRef<Path>>(config_path: P) -> Result<(), NymRewarderError> {
let _ = config_path;
Ok(())
}
+166
View File
@@ -0,0 +1,166 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::r#override::ConfigOverride;
use crate::config::template::CONFIG_TEMPLATE;
use crate::error::NymRewarderError;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use nym_network_defaults::NymNetworkDetails;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::{Path, PathBuf};
use tracing::{debug, warn};
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub mod r#override;
pub mod persistence;
mod template;
const DEFAULT_REWARDER_DIR: &str = "validators-rewarder";
/// Get default path to rewarder's config directory.
/// It should get resolved to `$HOME/.nym/validators-rewarder/config`
pub fn default_config_directory() -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_REWARDER_DIR)
.join(DEFAULT_CONFIG_DIR)
}
/// Get default path to rewarder's config file.
/// It should get resolved to `$HOME/.nym/validators-rewarder/config/config.toml`
pub fn default_config_filepath() -> PathBuf {
default_config_directory().join(DEFAULT_CONFIG_FILENAME)
}
/// Get default path to rewarder's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/validators-rewarder/data`
pub fn default_data_directory() -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_REWARDER_DIR)
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct Config {
// additional metadata holding on-disk location of this config file
#[serde(skip)]
#[zeroize(skip)]
pub(crate) save_path: Option<PathBuf>,
pub distribution: RewardingRatios,
#[serde(flatten)]
pub base: Base,
}
impl NymConfigTemplate for Config {
fn template(&self) -> &'static str {
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new(mnemonic: bip39::Mnemonic) -> Self {
let network = NymNetworkDetails::new_from_env();
Config {
save_path: None,
distribution: RewardingRatios::default(),
base: Base {
upstream_nyxd: network.endpoints[0].nyxd_url(),
mnemonic,
},
}
}
pub fn ensure_is_valid(&self) -> Result<(), NymRewarderError> {
self.distribution.ensure_is_valid()?;
Ok(())
}
pub fn r#override<O: ConfigOverride>(&mut self, r#override: O) {
r#override.override_config(self)
}
pub fn with_override<O: ConfigOverride>(mut self, r#override: O) -> Self {
self.r#override(r#override);
self
}
// simple wrapper that reads config file and assigns path location
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymRewarderError> {
let path = path.as_ref();
let mut loaded: Config = read_config_from_toml_file(path).map_err(|source| {
NymRewarderError::ConfigLoadFailure {
path: path.to_path_buf(),
source,
}
})?;
loaded.ensure_is_valid()?;
loaded.save_path = Some(path.to_path_buf());
debug!("loaded config file from {}", path.display());
Ok(loaded)
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, NymRewarderError> {
Self::read_from_path(path)
}
pub fn default_location() -> PathBuf {
default_config_filepath()
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = Self::default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn save_to_path<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
save_formatted_config_to_file(self, path)
}
}
#[derive(Debug, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct Base {
/// Url to the upstream instance of nyxd to use for any queries.
#[zeroize(skip)]
pub upstream_nyxd: Url,
/// Mnemonic to the nyx account distributing the rewards
pub(crate) mnemonic: bip39::Mnemonic,
}
#[derive(Debug, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct RewardingRatios {
/// The percent of the epoch reward being awarded for block signing.
pub block_signing: f32,
/// The percent of the epoch reward being awarded for credential issuance.
pub credential_issuance: f32,
/// The percent of the epoch reward given to Nym.
pub nym: f32,
}
impl Default for RewardingRatios {
fn default() -> Self {
RewardingRatios {
// TODO: define proper defaults
block_signing: 67.0,
credential_issuance: 33.0,
nym: 0.0,
}
}
}
impl RewardingRatios {
pub fn ensure_is_valid(&self) -> Result<(), NymRewarderError> {
Ok(())
}
}
@@ -0,0 +1,17 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::{init, run};
use crate::config::Config;
pub trait ConfigOverride {
fn override_config(self, config: &mut Config);
}
impl ConfigOverride for init::ConfigOverridableArgs {
fn override_config(self, config: &mut Config) {}
}
impl ConfigOverride for run::Args {
fn override_config(self, config: &mut Config) {}
}
@@ -0,0 +1,2 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
@@ -0,0 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
"#;
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_validator_client::nyxd::error::NyxdError;
use std::io;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NymRewarderError {
#[error(
"failed to load config file using path '{}'. detailed message: {source}", path.display()
)]
ConfigLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"failed to save config file using path '{}'. detailed message: {source}", path.display()
)]
ConfigSaveFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("there already exists a config file at: {}. if you want to overwrite its content, use --force flag", path.display())]
ExistingConfig { path: PathBuf },
// TODO: I think this one should get split into more, explicit, variants
#[error(transparent)]
NyxdFailure(#[from] NyxdError),
#[error("the provided rewarding ratios don't add up to 1. ratios: {ratios:?}")]
InvalidRewardingRatios { ratios: Vec<f32> },
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::Cli;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::maybe_print_banner;
use nym_network_defaults::setup_env;
pub mod cli;
pub mod config;
pub mod error;
mod rewarder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Cli::parse();
setup_env(args.config_env_file.as_ref());
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
args.execute().await?;
Ok(())
}
@@ -0,0 +1,98 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NymRewarderError;
use nym_network_defaults::NymNetworkDetails;
use nym_task::TaskManager;
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use tendermint_rpc::WebSocketClient;
use tracing::info;
mod tasks;
pub struct Rewarder {
config: Config,
}
impl Rewarder {
pub fn new(config: Config) -> Self {
Rewarder { config }
}
pub async fn run(mut self) -> Result<(), NymRewarderError> {
info!("Starting nym validators rewarder");
// setup shutdowns
let task_manager = TaskManager::new(5);
//
//
// let client_config =
// nyxd::Config::try_from_nym_network_details(&NymNetworkDetails::new_from_env())?;
//
// let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
// client_config,
// self.config.base.upstream_nyxd.as_str(),
// // note: the clone here is fine as the mnemonic itself implements ZeroizeOnDrop
// self.config.base.mnemonic.clone(),
// )?;
let (client, driver) = WebSocketClient::new("https://rpc.nymtech.net/")
.await
.unwrap();
/*
/// #[tokio::main]
/// async fn main() {
/// let (client, driver) = WebSocketClient::new("ws://127.0.0.1:26657/websocket")
/// .await
/// .unwrap();
/// let driver_handle = tokio::spawn(async move { driver.run().await });
///
/// // Standard client functionality
/// let tx = format!("some-key=some-value");
/// client.broadcast_tx_async(Transaction::from(tx.into_bytes())).await.unwrap();
///
/// // Subscription functionality
/// let mut subs = client.subscribe(EventType::NewBlock.into())
/// .await
/// .unwrap();
///
/// // Grab 5 NewBlock events
/// let mut ev_count = 5_i32;
///
/// while let Some(res) = subs.next().await {
/// let ev = res.unwrap();
/// println!("Got event: {:?}", ev);
/// ev_count -= 1;
/// if ev_count < 0 {
/// break;
/// }
/// }
///
/// // Signal to the driver to terminate.
/// client.close().unwrap();
/// // Await the driver's termination to ensure proper connection closure.
/// let _ = driver_handle.await.unwrap();
/// }
/// ```
*/
/*
task 1:
on timer:
- go to DKG contract
- get all coconut signers
- for each of them get the info, verify, etc
task 2:
on timer (or maybe per block?):
- query abci endpoint for VP
- also maybe missed blocks, etc
*/
todo!()
}
}
@@ -0,0 +1,2 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only