From a6fda391ae73db413a5e6e6dbe0ea0924f2b8207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 16 May 2024 09:15:24 +0100 Subject: [PATCH] Feature/rewarder voucher issuance (#4548) * retrieve ed25519 identities of issuers * signature verification on issued credentials * wip * persisting information about verified deposits, any failures and foul plays * clippy --- .../02_credential_issuance_failures.sql | 50 ++++++ nym-validator-rewarder/src/error.rs | 10 ++ .../src/rewarder/credential_issuance/mod.rs | 12 +- .../rewarder/credential_issuance/monitor.rs | 162 ++++++++++-------- .../src/rewarder/credential_issuance/types.rs | 25 +-- nym-validator-rewarder/src/rewarder/mod.rs | 5 +- .../src/rewarder/nyxd_client.rs | 39 ++++- .../src/rewarder/storage/manager.rs | 135 +++++++++++++++ .../src/rewarder/storage/mod.rs | 89 +++++++++- 9 files changed, 418 insertions(+), 109 deletions(-) create mode 100644 nym-validator-rewarder/migrations/02_credential_issuance_failures.sql diff --git a/nym-validator-rewarder/migrations/02_credential_issuance_failures.sql b/nym-validator-rewarder/migrations/02_credential_issuance_failures.sql new file mode 100644 index 0000000000..0323a8dd4d --- /dev/null +++ b/nym-validator-rewarder/migrations/02_credential_issuance_failures.sql @@ -0,0 +1,50 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +CREATE TABLE validated_deposit +( + operator_identity_bs58 TEXT NOT NULL, + credential_id INTEGER NOT NULL, + deposit_tx TEXT NOT NULL, + + signed_plaintext BLOB NOT NULL, + signature_bs58 TEXT NOT NULL +); + +-- evidence of attempting to re-use the same deposit tx for multiple credentials +CREATE TABLE double_signing_evidence +( + operator_identity_bs58 TEXT NOT NULL, + credential_id INTEGER NOT NULL, + original_credential_id INTEGER NOT NULL, + deposit_tx TEXT NOT NULL, + + signed_plaintext BLOB NOT NULL, + signature_bs58 TEXT NOT NULL +); + +-- evidence of foul play +CREATE TABLE issuance_evidence +( + operator_account TEXT NOT NULL, + operator_identity_bs58 TEXT NOT NULL, + + credential_id INTEGER NOT NULL, + signed_plaintext BLOB NOT NULL, + signature_bs58 TEXT NOT NULL, + + failure_message TEXT NOT NULL +); + +-- does not necessarily imply foul play, but something has gone wrong +CREATE TABLE issuance_validation_failure +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + + operator_account TEXT NOT NULL, + operator_identity_bs58 TEXT NOT NULL, + + failure_message TEXT NOT NULL +) \ No newline at end of file diff --git a/nym-validator-rewarder/src/error.rs b/nym-validator-rewarder/src/error.rs index cfff19d0b5..cc7a756363 100644 --- a/nym-validator-rewarder/src/error.rs +++ b/nym-validator-rewarder/src/error.rs @@ -3,6 +3,7 @@ use crate::config::RewardingRatios; use nym_coconut::CoconutError; +use nym_crypto::asymmetric::ed25519; use nym_validator_client::nym_api::error::NymAPIError; use nym_validator_client::nyxd::error::NyxdError; use nym_validator_client::nyxd::tx::ErrorReport; @@ -98,6 +99,12 @@ pub enum NymRewarderError { source: url::ParseError, }, + #[error("the provided ed25519 identity key is malformed: {source}")] + MalformedIdentityKey { + #[from] + source: ed25519::Ed25519RecoveryError, + }, + #[error("failed to resolve nym-api query: {0}")] ApiQueryFailure(#[from] NymAPIError), @@ -122,6 +129,9 @@ pub enum NymRewarderError { source: CoconutError, }, + #[error("the signature on issued credential with id {credential_id} is invalid")] + SignatureVerificationFailure { credential_id: i64 }, + #[error("could not verify the blinded credential")] BlindVerificationFailure, diff --git a/nym-validator-rewarder/src/rewarder/credential_issuance/mod.rs b/nym-validator-rewarder/src/rewarder/credential_issuance/mod.rs index 7102ac2e69..3bd0c6b75b 100644 --- a/nym-validator-rewarder/src/rewarder/credential_issuance/mod.rs +++ b/nym-validator-rewarder/src/rewarder/credential_issuance/mod.rs @@ -7,6 +7,7 @@ use crate::rewarder::credential_issuance::monitor::CredentialIssuanceMonitor; use crate::rewarder::credential_issuance::types::{CredentialIssuanceResults, MonitoringResults}; use crate::rewarder::epoch::Epoch; use crate::rewarder::nyxd_client::NyxdClient; +use crate::rewarder::storage::RewarderStorage; use nym_task::TaskClient; use nym_validator_client::nyxd::AccountId; use tracing::info; @@ -16,17 +17,20 @@ pub mod types; pub struct CredentialIssuance { monitoring_results: MonitoringResults, + storage: RewarderStorage, } impl CredentialIssuance { pub(crate) async fn new( epoch: Epoch, + storage: RewarderStorage, nyxd_client: &NyxdClient, whitelist: &[AccountId], ) -> Result { Ok(CredentialIssuance { monitoring_results: MonitoringResults::new_initial(epoch, nyxd_client, whitelist) .await?, + storage, }) } @@ -37,8 +41,12 @@ impl CredentialIssuance { task_client: TaskClient, ) { let monitoring_results = self.monitoring_results.clone(); - let mut monitor = - CredentialIssuanceMonitor::new(monitor_config, nyxd_client, monitoring_results); + let mut monitor = CredentialIssuanceMonitor::new( + monitor_config, + nyxd_client, + self.storage.clone(), + monitoring_results, + ); tokio::spawn(async move { monitor.run(task_client).await }); } diff --git a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs index 6a02ca52a5..4df0395ec4 100644 --- a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs +++ b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs @@ -8,6 +8,7 @@ use crate::rewarder::credential_issuance::types::{ }; use crate::rewarder::helpers::api_client; use crate::rewarder::nyxd_client::NyxdClient; +use crate::rewarder::storage::RewarderStorage; use bip39::rand::prelude::SliceRandom; use bip39::rand::thread_rng; use nym_coconut::{ @@ -15,75 +16,88 @@ use nym_coconut::{ }; use nym_coconut_dkg_common::types::EpochId; use nym_credentials::coconut::bandwidth::bandwidth_credential_params; +use nym_crypto::asymmetric::ed25519; use nym_task::TaskClient; use nym_validator_client::nym_api::{IssuedCredential, IssuedCredentialBody, NymApiClientExt}; -use nym_validator_client::nyxd::Hash; use std::cmp::max; -use std::collections::HashMap; use tokio::time::interval; -use tracing::{debug, error, info, instrument, trace, warn}; +use tracing::{debug, error, info, instrument, trace}; pub struct CredentialIssuanceMonitor { nyxd_client: NyxdClient, monitoring_results: MonitoringResults, config: config::IssuanceMonitor, - - // map of validator address -> transaction hash -> issued credential - // (ideally we'd have hashed the AccountId directly, but it doesn't implement `Hash`) - seen_deposits: HashMap>, + storage: RewarderStorage, } impl CredentialIssuanceMonitor { pub fn new( config: config::IssuanceMonitor, nyxd_client: NyxdClient, + storage: RewarderStorage, monitoring_results: MonitoringResults, ) -> CredentialIssuanceMonitor { CredentialIssuanceMonitor { config, + storage, nyxd_client, monitoring_results, - seen_deposits: Default::default(), } } fn validate_credential_signature( &mut self, - _issued_credential: &IssuedCredentialBody, + issued_credential: &IssuedCredentialBody, + identity_key: &ed25519::PublicKey, ) -> Result<(), NymRewarderError> { - warn!("unimplemented: public key sharing mechanism"); - // let plaintext = issued_credential.credential.signable_plaintext(); - // if !operator_public_key.verify(&plaintext) { - // ... - // } - Ok(()) + let plaintext = issued_credential.credential.signable_plaintext(); + if identity_key + .verify(plaintext, &issued_credential.signature) + .is_err() + { + Err(NymRewarderError::SignatureVerificationFailure { + credential_id: issued_credential.credential.id, + }) + } else { + Ok(()) + } } - fn check_deposit_reuse( + async fn check_deposit_reuse( &mut self, - runner: &str, - credential_id: i64, - deposit_tx: Hash, + issuer_identity: &str, + credential_info: &IssuedCredentialBody, ) -> Result { - // check if we've seen this tx hash before - // TODO: we should persist them in the database in case we crash - if let Some(known_runner) = self.seen_deposits.get_mut(runner) { - if let Some(&used) = known_runner.get(&deposit_tx) { - return if used != credential_id { - Err(NymRewarderError::DuplicateDepositHash { - tx_hash: deposit_tx, - first: used, - other: credential_id, - }) - } else { + let credential_id = credential_info.credential.id; + let deposit_tx = credential_info.credential.tx_hash; + let prior_id = self + .storage + .get_deposit_credential_id(issuer_identity.to_string(), deposit_tx.to_string()) + .await?; + + match prior_id { + None => Ok(false), + Some(prior) => { + if prior == credential_id { debug!("we have already verified this credential before"); Ok(true) - }; - } else { - known_runner.insert(deposit_tx, credential_id); + } else { + error!("double signing detected!! used deposit {deposit_tx} for credentials {prior} and {credential_id}!!"); + self.storage + .insert_double_signing_evidence( + issuer_identity.to_string(), + prior, + credential_info, + ) + .await?; + Err(NymRewarderError::DuplicateDepositHash { + tx_hash: deposit_tx, + first: prior, + other: credential_id, + }) + } } } - Ok(false) } async fn validate_deposit( @@ -126,7 +140,7 @@ impl CredentialIssuanceMonitor { fn verify_credential( &mut self, vk: &VerificationKey, - credential: IssuedCredential, + credential: &IssuedCredential, ) -> Result<(), NymRewarderError> { let public_attributes = credential .public_attributes @@ -140,12 +154,12 @@ impl CredentialIssuanceMonitor { let mut public_attribute_commitments = Vec::with_capacity(credential.bs58_encoded_private_attributes_commitments.len()); - for raw_cm in credential.bs58_encoded_private_attributes_commitments { - match G1Projective::try_from_bs58(&raw_cm) { + for raw_cm in &credential.bs58_encoded_private_attributes_commitments { + match G1Projective::try_from_bs58(raw_cm) { Ok(cm) => public_attribute_commitments.push(cm), Err(source) => { return Err(NymRewarderError::MalformedCredentialCommitment { - raw: raw_cm, + raw: raw_cm.clone(), source, }) } @@ -167,30 +181,34 @@ impl CredentialIssuanceMonitor { Ok(()) } - // TODO: currently we can't obtain public key of the runner in order to verify the signature - #[instrument(skip_all, fields(credential_id = credential_id, deposit = %issued_credential.credential.tx_hash))] + #[instrument(skip_all, fields(credential_id = %issued_credential.credential.id, deposit = %issued_credential.credential.tx_hash))] async fn validate_issued_credential( &mut self, - runner: String, - credential_id: i64, - issued_credential: IssuedCredentialBody, - vk: &VerificationKey, + issuer: &CredentialIssuer, + issued_credential: &IssuedCredentialBody, ) -> Result<(), NymRewarderError> { - self.validate_credential_signature(&issued_credential)?; - - let deposit_tx = issued_credential.credential.tx_hash; + // check if the issuer has actually signed that issued credential information + self.validate_credential_signature(issued_credential, &issuer.public_key)?; + let encoded_key = issuer.public_key.to_base58_string(); // make sure the issuer is not using the same deposit for multiple credentials - let already_checked = self.check_deposit_reuse(&runner, credential_id, deposit_tx)?; + let already_checked = self + .check_deposit_reuse(&encoded_key, issued_credential) + .await?; if already_checked { return Ok(()); } // check the correctness of the deposit itself - self.validate_deposit(&issued_credential).await?; + self.validate_deposit(issued_credential).await?; + + // insert validated deposit info into the storage + self.storage + .insert_validated_deposit(encoded_key, issued_credential) + .await?; // check if the partial credential correctly verifies - self.verify_credential(vk, issued_credential.credential)?; + self.verify_credential(&issuer.verification_key, &issued_credential.credential)?; Ok(()) } @@ -220,12 +238,12 @@ impl CredentialIssuanceMonitor { async fn check_issuer( &mut self, epoch_id: EpochId, - issuer: CredentialIssuer, + issuer: &CredentialIssuer, ) -> Result { info!("checking the issuer's credentials..."); debug!("checking the issuer's credentials..."); - let api_client = api_client(&issuer)?; + let api_client = api_client(issuer)?; let epoch_credentials = api_client.epoch_credentials(epoch_id).await?; let whitelisted = self.config.whitelist.contains(&issuer.operator_account); @@ -234,8 +252,8 @@ impl CredentialIssuanceMonitor { // no point in doing anything more - if they haven't issued anything, there's nothing to verify debug!("no credentials issued this epoch",); return Ok(RawOperatorResult::new_empty( - issuer.operator_account, - issuer.api_runner, + issuer.operator_account.clone(), + issuer.api_runner.clone(), whitelisted, )); }; @@ -248,9 +266,9 @@ impl CredentialIssuanceMonitor { let credentials = api_client.issued_credentials(sampled.clone()).await?; if credentials.credentials.len() != request_size { - // TODO: we need some signatures here to actually show the validator is cheating + error!("received an incomplete credential request! the issuer **MIGHT** be cheating!! but we're lacking sufficient signatures to be certain"); return Err(NymRewarderError::IncompleteRequest { - runner_account: issuer.operator_account, + runner_account: issuer.operator_account.clone(), requested: request_size, received: credentials.credentials.len(), }); @@ -258,27 +276,21 @@ impl CredentialIssuanceMonitor { for (id, credential) in credentials.credentials { trace!("checking credential {id}..."); - // TODO: insert the failure information, alongside the signature, to the evidence db - if let Err(err) = self - .validate_issued_credential( - issuer.operator_account.to_string(), - id, - credential, - &issuer.verification_key, - ) - .await - { + if let Err(err) = self.validate_issued_credential(issuer, &credential).await { error!( - "failed to verify credential {id} from {}!!: {err}", - issuer.operator_account + "failed to validate credential {id} from {} ({})!!: {err}", + issuer.public_key, issuer.operator_account ); + self.storage + .insert_issuance_foul_play_evidence(issuer, &credential, err.to_string()) + .await?; return Err(err); } } Ok(RawOperatorResult { - operator_account: issuer.operator_account, - api_runner: issuer.api_runner, + operator_account: issuer.operator_account.clone(), + api_runner: issuer.api_runner.clone(), whitelisted, issued_credentials: epoch_credentials.total_issued, validated_credentials: sampled, @@ -296,14 +308,16 @@ impl CredentialIssuanceMonitor { let mut results = Vec::with_capacity(issuers.len()); for issuer in issuers { - let address = issuer.operator_account.clone(); // we could parallelize it, but we're running the test so infrequently (relatively speaking) // that doing it sequentially is fine - match self.check_issuer(epoch.epoch_id, issuer).await { + match self.check_issuer(epoch.epoch_id, &issuer).await { Ok(res) => results.push(res), Err(err) => { - // TODO: insert info to the db - error!("failed to check credential issuance of {address}: {err}") + let address = &issuer.operator_account; + error!("failed to check credential issuance of {address}: {err}"); + self.storage + .insert_issuance_validation_failure_info(&issuer, err.to_string()) + .await?; } } } diff --git a/nym-validator-rewarder/src/rewarder/credential_issuance/types.rs b/nym-validator-rewarder/src/rewarder/credential_issuance/types.rs index 4b84870f70..ce8b65b659 100644 --- a/nym-validator-rewarder/src/rewarder/credential_issuance/types.rs +++ b/nym-validator-rewarder/src/rewarder/credential_issuance/types.rs @@ -6,8 +6,8 @@ use crate::rewarder::epoch::Epoch; use crate::rewarder::helpers::api_client; use crate::rewarder::nyxd_client::NyxdClient; use cosmwasm_std::{Addr, Decimal, Uint128}; -use nym_coconut::{Base58, VerificationKey}; -use nym_coconut_dkg_common::verification_key::ContractVKShare; +use nym_coconut::VerificationKey; +use nym_crypto::asymmetric::ed25519; use nym_validator_client::nym_api::NymApiClientExt; use nym_validator_client::nyxd::{AccountId, Coin}; use std::collections::{HashMap, HashSet}; @@ -338,32 +338,15 @@ impl CredentialIssuanceResults { #[derive(Debug)] pub struct CredentialIssuer { - // pub public_key: identity::PublicKey, + pub public_key: ed25519::PublicKey, pub operator_account: AccountId, pub api_runner: String, pub verification_key: VerificationKey, } -impl TryFrom for CredentialIssuer { - type Error = NymRewarderError; - - fn try_from(value: ContractVKShare) -> Result { - Ok(CredentialIssuer { - operator_account: addr_to_account_id(value.owner.clone()), - api_runner: value.announce_address, - verification_key: VerificationKey::try_from_bs58(value.share).map_err(|source| { - NymRewarderError::MalformedPartialVerificationKey { - runner: value.owner.to_string(), - source, - } - })?, - }) - } -} - // safety: we're converting between different wrappers for bech32 addresses // and we trust (reasonably so), the values coming out of registered dealers in the DKG contract -fn addr_to_account_id(addr: Addr) -> AccountId { +pub(crate) fn addr_to_account_id(addr: Addr) -> AccountId { #[allow(clippy::unwrap_used)] addr.as_str().parse().unwrap() } diff --git a/nym-validator-rewarder/src/rewarder/mod.rs b/nym-validator-rewarder/src/rewarder/mod.rs index 5d813a11f0..4829091f5c 100644 --- a/nym-validator-rewarder/src/rewarder/mod.rs +++ b/nym-validator-rewarder/src/rewarder/mod.rs @@ -121,7 +121,10 @@ impl Rewarder { return Err(NymRewarderError::EmptyCredentialIssuanceWhitelist); } - Some(CredentialIssuance::new(current_epoch, &nyxd_client, whitelist).await?) + Some( + CredentialIssuance::new(current_epoch, storage.clone(), &nyxd_client, whitelist) + .await?, + ) } else { None }; diff --git a/nym-validator-rewarder/src/rewarder/nyxd_client.rs b/nym-validator-rewarder/src/rewarder/nyxd_client.rs index b430136930..540245b5bc 100644 --- a/nym-validator-rewarder/src/rewarder/nyxd_client.rs +++ b/nym-validator-rewarder/src/rewarder/nyxd_client.rs @@ -3,11 +3,13 @@ use crate::config::Config; use crate::error::NymRewarderError; -use crate::rewarder::credential_issuance::types::CredentialIssuer; +use crate::rewarder::credential_issuance::types::{addr_to_account_id, CredentialIssuer}; +use nym_coconut::{Base58, VerificationKey}; use nym_coconut_bandwidth_contract_common::events::{ COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO, DEPOSIT_VALUE, }; use nym_coconut_dkg_common::types::Epoch; +use nym_crypto::asymmetric::ed25519; use nym_network_defaults::NymNetworkDetails; use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient}; use nym_validator_client::nyxd::helpers::find_tx_attribute; @@ -18,6 +20,7 @@ use nym_validator_client::nyxd::{ AccountId, Coin, CosmWasmClient, Hash, PageRequest, StakingQueryClient, }; use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; +use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; use tokio::sync::RwLock; @@ -92,14 +95,32 @@ impl NyxdClient { &self, dkg_epoch: u64, ) -> Result, NymRewarderError> { - self.inner - .read() - .await - .get_all_verification_key_shares(dkg_epoch) - .await? - .into_iter() - .map(TryInto::try_into) - .collect() + let guard = self.inner.read().await; + let mut dealers_map = HashMap::new(); + let dealers = guard.get_all_current_dealers().await?; + for dealer in dealers { + dealers_map.insert(dealer.address.to_string(), dealer); + } + let vk_shares = guard.get_all_verification_key_shares(dkg_epoch).await?; + + let mut issuers = Vec::with_capacity(vk_shares.len()); + for share in vk_shares { + if let Some(info) = dealers_map.remove(&share.owner.to_string()) { + issuers.push(CredentialIssuer { + public_key: ed25519::PublicKey::from_base58_string(&info.ed25519_identity)?, + operator_account: addr_to_account_id(share.owner), + api_runner: share.announce_address, + verification_key: VerificationKey::try_from_bs58(share.share).map_err( + |source| NymRewarderError::MalformedPartialVerificationKey { + runner: info.address.to_string(), + source, + }, + )?, + }) + } + } + + Ok(issuers) } pub(crate) async fn get_deposit_transaction_attributes( diff --git a/nym-validator-rewarder/src/rewarder/storage/manager.rs b/nym-validator-rewarder/src/rewarder/storage/manager.rs index 1eb9e985cf..453668c3ef 100644 --- a/nym-validator-rewarder/src/rewarder/storage/manager.rs +++ b/nym-validator-rewarder/src/rewarder/storage/manager.rs @@ -181,4 +181,139 @@ impl StorageManager { Ok(()) } + + pub(crate) async fn insert_validated_deposit( + &self, + operator_identity_bs58: String, + credential_id: i64, + deposit_tx: String, + signed_plaintext: Vec, + signature_bs58: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO validated_deposit ( + operator_identity_bs58, + credential_id, + deposit_tx, + signed_plaintext, + signature_bs58 + ) VALUES (?, ?, ?, ?, ?) + "#, + operator_identity_bs58, + credential_id, + deposit_tx, + signed_plaintext, + signature_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn get_deposit_credential_id( + &self, + operator_identity_bs58: String, + deposit_tx: String, + ) -> Result, sqlx::Error> { + Ok(sqlx::query!( + r#" + SELECT credential_id + FROM validated_deposit + WHERE operator_identity_bs58 = ? AND deposit_tx = ? + "#, + operator_identity_bs58, + deposit_tx + ) + .fetch_optional(&self.connection_pool) + .await? + .map(|record| record.credential_id)) + } + + pub(crate) async fn insert_double_signing_evidence( + &self, + operator_identity_bs58: String, + credential_id: i64, + original_credential_id: i64, + deposit_tx: String, + signed_plaintext: Vec, + signature_bs58: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO double_signing_evidence ( + operator_identity_bs58, + credential_id, + original_credential_id, + deposit_tx, + signed_plaintext, + signature_bs58 + ) VALUES (?, ?, ?, ?, ?, ?) + "#, + operator_identity_bs58, + credential_id, + original_credential_id, + deposit_tx, + signed_plaintext, + signature_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn insert_foul_play_evidence( + &self, + operator_account: String, + operator_identity_bs58: String, + credential_id: i64, + signed_plaintext: Vec, + signature_bs58: String, + failure_message: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO issuance_evidence ( + operator_account, + operator_identity_bs58, + credential_id, + signed_plaintext, + signature_bs58, + failure_message + ) VALUES (?, ?, ?, ?, ?, ?) + "#, + operator_account, + operator_identity_bs58, + credential_id, + signed_plaintext, + signature_bs58, + failure_message, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn insert_validation_failure_info( + &self, + operator_account: String, + operator_identity_bs58: String, + failure_message: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO issuance_validation_failure ( + operator_account, + operator_identity_bs58, + failure_message + ) VALUES (?, ?, ?) + "#, + operator_account, + operator_identity_bs58, + failure_message, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/nym-validator-rewarder/src/rewarder/storage/mod.rs b/nym-validator-rewarder/src/rewarder/storage/mod.rs index dd5e8f68a7..c0463700d0 100644 --- a/nym-validator-rewarder/src/rewarder/storage/mod.rs +++ b/nym-validator-rewarder/src/rewarder/storage/mod.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::error::NymRewarderError; +use crate::rewarder::credential_issuance::types::CredentialIssuer; use crate::rewarder::epoch::Epoch; use crate::rewarder::storage::manager::StorageManager; use crate::rewarder::{EpochRewards, RewardingResult}; +use nym_validator_client::nym_api::IssuedCredentialBody; use nym_validator_client::nyxd::Coin; use sqlx::ConnectOptions; use std::fmt::Debug; @@ -13,6 +15,7 @@ use tracing::{error, info, instrument}; mod manager; +#[derive(Clone)] pub struct RewarderStorage { pub(crate) manager: StorageManager, } @@ -77,11 +80,93 @@ impl RewarderStorage { .await?) } + pub(crate) async fn get_deposit_credential_id( + &self, + operator_identity_bs58: String, + deposit_tx: String, + ) -> Result, NymRewarderError> { + Ok(self + .manager + .get_deposit_credential_id(operator_identity_bs58, deposit_tx) + .await?) + } + + pub(crate) async fn insert_validated_deposit( + &self, + operator_identity_bs58: String, + credential_info: &IssuedCredentialBody, + ) -> Result<(), NymRewarderError> { + self.manager + .insert_validated_deposit( + operator_identity_bs58, + credential_info.credential.id, + credential_info.credential.tx_hash.to_string(), + credential_info.credential.signable_plaintext(), + credential_info.signature.to_base58_string(), + ) + .await?; + Ok(()) + } + + pub(crate) async fn insert_double_signing_evidence( + &self, + operator_identity_bs58: String, + original_credential_id: i64, + credential_info: &IssuedCredentialBody, + ) -> Result<(), NymRewarderError> { + self.manager + .insert_double_signing_evidence( + operator_identity_bs58, + credential_info.credential.id, + original_credential_id, + credential_info.credential.tx_hash.to_string(), + credential_info.credential.signable_plaintext(), + credential_info.signature.to_base58_string(), + ) + .await?; + Ok(()) + } + + pub(crate) async fn insert_issuance_foul_play_evidence( + &self, + issuer: &CredentialIssuer, + credential_info: &IssuedCredentialBody, + error_message: String, + ) -> Result<(), NymRewarderError> { + self.manager + .insert_foul_play_evidence( + issuer.operator_account.to_string(), + issuer.public_key.to_base58_string(), + credential_info.credential.id, + credential_info.credential.signable_plaintext(), + credential_info.signature.to_base58_string(), + error_message, + ) + .await?; + Ok(()) + } + + pub(crate) async fn insert_issuance_validation_failure_info( + &self, + issuer: &CredentialIssuer, + error_message: String, + ) -> Result<(), NymRewarderError> { + self.manager + .insert_validation_failure_info( + issuer.operator_account.to_string(), + issuer.public_key.to_base58_string(), + error_message, + ) + .await?; + Ok(()) + } + pub(crate) async fn save_rewarding_information( &self, reward: EpochRewards, - rewarding_result: Result, // total_spent: Coin, - // rewarding_tx: Result, + rewarding_result: Result, + // total_spent: Coin, + // rewarding_tx: Result, ) -> Result<(), NymRewarderError> { info!("persisting reward details"); let denom = &reward.total_budget.denom;