diff --git a/envs/sandbox.env b/envs/sandbox.env index 856550cca0..5349798f41 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -24,4 +24,5 @@ SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" EXPLORER_API=https://sandbox-explorer.nymtech.net/api NYXD="https://rpc.sandbox.nymtech.net" +NYXD_WS="wss://rpc.sandbox.nymtech.net/websocket" NYM_API="https://sandbox-nym-api1.nymtech.net/api" diff --git a/nym-validator-rewarder/migrations/01_initial.sql b/nym-validator-rewarder/migrations/01_initial.sql index ec7758fcb5..b59fdfe383 100644 --- a/nym-validator-rewarder/migrations/01_initial.sql +++ b/nym-validator-rewarder/migrations/01_initial.sql @@ -40,7 +40,7 @@ CREATE TABLE epoch_credential_issuance ( rewarding_epoch_id INTEGER NOT NULL PRIMARY KEY REFERENCES rewarding_epoch (id), starting_dkg_epoch INTEGER NOT NULL, - ending_dkg_epoch INTEGER NOT NULL, + ending_dkg_epoch INTEGER NOT NULL, total_issued_credentials INTEGER NOT NULL, budget TEXT NOT NULL ); diff --git a/nym-validator-rewarder/src/config/mod.rs b/nym-validator-rewarder/src/config/mod.rs index 0f696febdd..5ff7bde1bc 100644 --- a/nym-validator-rewarder/src/config/mod.rs +++ b/nym-validator-rewarder/src/config/mod.rs @@ -11,7 +11,7 @@ use nym_config::{ }; use nym_network_defaults::NymNetworkDetails; use nym_validator_client::nyxd; -use nym_validator_client::nyxd::{AccountId, Coin}; +use nym_validator_client::nyxd::Coin; use serde::{Deserialize, Serialize}; use std::io; use std::path::{Path, PathBuf}; @@ -69,6 +69,9 @@ pub struct Config { #[zeroize(skip)] pub rewarding: Rewarding, + #[zeroize(skip)] + pub block_signing: BlockSigning, + #[zeroize(skip)] pub issuance_monitor: IssuanceMonitor, @@ -95,8 +98,10 @@ impl Config { Config { save_path: None, rewarding: Rewarding::default(), + block_signing: Default::default(), issuance_monitor: IssuanceMonitor::default(), nyxd_scraper: NyxdScraper { + enabled: true, websocket_url: network.endpoints[0] .websocket_url() .expect("TODO: hardcoded websocket url is not available"), @@ -123,17 +128,11 @@ impl Config { .expect("failed to create nyxd client config") } - pub fn dkg_contract_address(&self) -> AccountId { - // TEMP - NymNetworkDetails::new_from_env() - .contracts - .coconut_dkg_contract_address - .expect("missing contract address") - .parse() - .expect("invalid contract address") - } - pub fn ensure_is_valid(&self) -> Result<(), NymRewarderError> { + if self.block_signing.enabled && !self.nyxd_scraper.enabled { + return Err(NymRewarderError::BlockSigningRewardWithoutScraper); + } + self.rewarding.ratios.ensure_is_valid()?; Ok(()) } @@ -192,7 +191,7 @@ pub struct Base { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Rewarding { - /// + /// Specifies total budget for the epoch pub epoch_budget: Coin, #[serde(with = "humantime_serde")] @@ -250,13 +249,31 @@ impl RewardingRatios { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct NyxdScraper { + /// Specifies whether the chain scraper is enabled. + pub enabled: bool, + /// Url to the websocket endpoint of a validator, for example `wss://rpc.nymtech.net/websocket` pub websocket_url: Url, // TODO: debug with everything that's currently hardcoded in the scraper } +#[derive(Debug, Clone, Deserialize, Serialize, Copy)] +pub struct BlockSigning { + /// Specifies whether credential issuance for block signing is enabled. + pub enabled: bool, +} + +impl Default for BlockSigning { + fn default() -> Self { + BlockSigning { enabled: true } + } +} + #[derive(Debug, Clone, Deserialize, Serialize, Copy)] pub struct IssuanceMonitor { + /// Specifies whether credential issuance monitoring (and associated rewards) are enabled. + pub enabled: bool, + #[serde(with = "humantime_serde")] pub run_interval: Duration, @@ -271,6 +288,7 @@ pub struct IssuanceMonitor { impl Default for IssuanceMonitor { fn default() -> Self { IssuanceMonitor { + enabled: true, run_interval: DEFAULT_MONITOR_RUN_INTERVAL, min_validate_per_issuer: DEFAULT_MONITOR_MIN_VALIDATE, sampling_rate: DEFAULT_MONITOR_SAMPLING_RATE, diff --git a/nym-validator-rewarder/src/error.rs b/nym-validator-rewarder/src/error.rs index 62b92cd398..03c3fc4d60 100644 --- a/nym-validator-rewarder/src/error.rs +++ b/nym-validator-rewarder/src/error.rs @@ -150,4 +150,7 @@ pub enum NymRewarderError { request: Option, on_chain: String, }, + + #[error("can't enable block signing rewarding without the block scraper")] + BlockSigningRewardWithoutScraper, } diff --git a/nym-validator-rewarder/src/main.rs b/nym-validator-rewarder/src/main.rs index f5d59b57c2..fb03008a9a 100644 --- a/nym-validator-rewarder/src/main.rs +++ b/nym-validator-rewarder/src/main.rs @@ -1,6 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + use crate::cli::Cli; use clap::{crate_name, crate_version, Parser}; use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger}; diff --git a/nym-validator-rewarder/src/rewarder/mod.rs b/nym-validator-rewarder/src/rewarder/mod.rs index 332fe86ff7..0193dd4b4e 100644 --- a/nym-validator-rewarder/src/rewarder/mod.rs +++ b/nym-validator-rewarder/src/rewarder/mod.rs @@ -30,8 +30,8 @@ mod tasks; pub struct EpochRewards { pub epoch: Epoch, - pub signing: EpochSigningResults, - pub credentials: CredentialIssuanceResults, + pub signing: Option, + pub credentials: Option, pub total_budget: Coin, pub signing_budget: Coin, @@ -40,11 +40,17 @@ pub struct EpochRewards { impl EpochRewards { pub fn amounts(&self) -> Vec<(AccountId, Vec)> { - let signing = self.signing.rewarding_amounts(&self.signing_budget); - let mut credentials = self.credentials.rewarding_amounts(&self.credentials_budget); + let mut amounts = Vec::new(); - let mut amounts = signing; - amounts.append(&mut credentials); + if let Some(signing) = &self.signing { + let mut signing_amounts = signing.rewarding_amounts(&self.signing_budget); + amounts.append(&mut signing_amounts); + } + + if let Some(credentials) = &self.credentials { + let mut credentials_amounts = credentials.rewarding_amounts(&self.credentials_budget); + amounts.append(&mut credentials_amounts); + } amounts } @@ -65,13 +71,18 @@ pub struct Rewarder { storage: RewarderStorage, nyxd_client: NyxdClient, - epoch_signing: EpochSigning, - credential_issuance: CredentialIssuance, + epoch_signing: Option, + credential_issuance: Option, } impl Rewarder { pub async fn new(config: Config) -> Result { - let nyxd_scraper = NyxdScraper::new(config.scraper_config()).await?; + let nyxd_scraper = if config.nyxd_scraper.enabled { + Some(NyxdScraper::new(config.scraper_config()).await?) + } else { + None + }; + let nyxd_client = NyxdClient::new(&config); let storage = RewarderStorage::init(&config.storage_paths.reward_history).await?; let current_epoch = if let Some(last_epoch) = storage.load_last_rewarding_epoch().await? { @@ -80,13 +91,28 @@ impl Rewarder { Epoch::first(config.rewarding.epoch_duration)? }; + let epoch_signing = if config.block_signing.enabled { + // safety: our config has been validated at load time to ensure that if block signing is enabled, + // so is the scraper + Some(EpochSigning { + #[allow(clippy::unwrap_used)] + nyxd_scraper: nyxd_scraper.unwrap(), + nyxd_client: nyxd_client.clone(), + }) + } else { + None + }; + + let credential_issuance = if config.issuance_monitor.enabled { + Some(CredentialIssuance::new(current_epoch, &nyxd_client).await?) + } else { + None + }; + Ok(Rewarder { current_epoch, - credential_issuance: CredentialIssuance::new(current_epoch, &nyxd_client).await?, - epoch_signing: EpochSigning { - nyxd_scraper, - nyxd_client: nyxd_client.clone(), - }, + credential_issuance, + epoch_signing, nyxd_client, storage, config, @@ -96,21 +122,35 @@ impl Rewarder { #[instrument(skip(self))] async fn calculate_block_signing_rewards( &mut self, - ) -> Result { + ) -> Result, NymRewarderError> { info!("calculating reward shares"); - self.epoch_signing - .get_signed_blocks_results(self.current_epoch) - .await + if let Some(epoch_signing) = &mut self.epoch_signing { + Some( + epoch_signing + .get_signed_blocks_results(self.current_epoch) + .await, + ) + } else { + None + } + .transpose() } #[instrument(skip(self))] async fn calculate_credential_rewards( &mut self, - ) -> Result { + ) -> Result, NymRewarderError> { info!("calculating reward shares"); - self.credential_issuance - .get_issued_credentials_results(self.current_epoch) - .await + if let Some(credential_issuance) = &mut self.credential_issuance { + Some( + credential_issuance + .get_issued_credentials_results(self.current_epoch) + .await, + ) + } else { + None + } + .transpose() } async fn determine_epoch_rewards(&mut self) -> Result { @@ -180,16 +220,18 @@ impl Rewarder { // setup shutdowns let mut task_manager = TaskManager::new(5); - self.credential_issuance.start_monitor( - self.config.issuance_monitor, - self.nyxd_client.clone(), - task_manager.subscribe(), - ); - self.epoch_signing.nyxd_scraper.start().await?; - self.epoch_signing - .nyxd_scraper - .wait_for_startup_sync() - .await; + if let Some(ref credential_issuance) = self.credential_issuance { + credential_issuance.start_monitor( + self.config.issuance_monitor, + self.nyxd_client.clone(), + task_manager.subscribe(), + ); + } + + if let Some(epoch_signing) = &self.epoch_signing { + epoch_signing.nyxd_scraper.start().await?; + epoch_signing.nyxd_scraper.wait_for_startup_sync().await; + } // rewarding epochs last from :00 to :00 // \/\/\/\/\/\/\/ TEMP TESTING!!! @@ -225,7 +267,9 @@ impl Rewarder { } } - self.epoch_signing.nyxd_scraper.stop().await; + if let Some(epoch_signing) = self.epoch_signing { + epoch_signing.nyxd_scraper.stop().await; + } Ok(()) } diff --git a/nym-validator-rewarder/src/rewarder/storage/mod.rs b/nym-validator-rewarder/src/rewarder/storage/mod.rs index 784361a37c..36a2c23790 100644 --- a/nym-validator-rewarder/src/rewarder/storage/mod.rs +++ b/nym-validator-rewarder/src/rewarder/storage/mod.rs @@ -81,58 +81,82 @@ impl RewarderStorage { self.manager .insert_rewarding_epoch_block_signing( epoch_id, - reward.signing.total_voting_power_at_epoch_start, - reward.signing.blocks, + reward + .signing + .as_ref() + .map(|s| s.total_voting_power_at_epoch_start) + .unwrap_or_default(), + reward + .signing + .as_ref() + .map(|s| s.blocks) + .unwrap_or_default(), reward.signing_budget.to_string(), ) .await?; - for validator in reward.signing.validators { - let reward_amount = validator.reward_amount(&reward.signing_budget).to_string(); - self.manager - .insert_rewarding_epoch_block_signing_reward( - epoch_id, - validator.validator.consensus_address, - validator.operator_account.to_string(), - reward_amount, - validator.voting_power_at_epoch_start, - validator.voting_power_ratio.to_string(), - validator.signed_blocks, - validator.ratio_signed.to_string(), - ) - .await?; + if let Some(signing) = reward.signing { + for validator in signing.validators { + let reward_amount = validator.reward_amount(&reward.signing_budget).to_string(); + self.manager + .insert_rewarding_epoch_block_signing_reward( + epoch_id, + validator.validator.consensus_address, + validator.operator_account.to_string(), + reward_amount, + validator.voting_power_at_epoch_start, + validator.voting_power_ratio.to_string(), + validator.signed_blocks, + validator.ratio_signed.to_string(), + ) + .await?; + } } // safety: we must have at least a single value here - let dkg_epoch_start = reward.credentials.dkg_epochs.first().unwrap(); - let dkg_epoch_end = reward.credentials.dkg_epochs.last().unwrap(); + let dkg_epoch_start = reward + .credentials + .as_ref() + .map(|c| *c.dkg_epochs.first().unwrap()) + .unwrap_or_default(); + let dkg_epoch_end = reward + .credentials + .as_ref() + .map(|c| *c.dkg_epochs.last().unwrap()) + .unwrap_or_default(); self.manager .insert_rewarding_epoch_credential_issuance( epoch_id, - *dkg_epoch_start, - *dkg_epoch_end, - reward.credentials.total_issued, + dkg_epoch_start, + dkg_epoch_end, + reward + .credentials + .as_ref() + .map(|c| c.total_issued) + .unwrap_or_default(), reward.credentials_budget.to_string(), ) .await?; - for api_runner in reward.credentials.api_runners { - let reward_amount = api_runner - .reward_amount(&reward.credentials_budget) - .to_string(); + if let Some(credentials) = reward.credentials { + for api_runner in credentials.api_runners { + let reward_amount = api_runner + .reward_amount(&reward.credentials_budget) + .to_string(); - self.manager - .insert_rewarding_epoch_credential_issuance_reward( - epoch_id, - api_runner.runner_account.to_string(), - reward_amount, - api_runner.api_runner, - api_runner.issued_credentials, - api_runner.issued_ratio.to_string(), - api_runner.validated_credentials, - ) - .await?; + self.manager + .insert_rewarding_epoch_credential_issuance_reward( + epoch_id, + api_runner.runner_account.to_string(), + reward_amount, + api_runner.api_runner, + api_runner.issued_credentials, + api_runner.issued_ratio.to_string(), + api_runner.validated_credentials, + ) + .await?; + } } Ok(())