most of the issuance monitoring logic

This commit is contained in:
Jędrzej Stuczyński
2023-12-15 16:52:02 +00:00
committed by Jon Häggblad
parent 668a255e0d
commit cfc13671a4
21 changed files with 1095 additions and 784 deletions
Generated
+613 -659
View File
File diff suppressed because it is too large Load Diff
@@ -5,21 +5,24 @@ use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
pub use nym_api_requests::{
coconut::{
models::{
EpochCredentialsResponse, IssuedCredentialBody, IssuedCredentialResponse,
IssuedCredentialsResponse,
},
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
VerifyCredentialBody, VerifyCredentialResponse,
},
models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody,
VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_coconut_dkg_common::types::EpochId;
pub use nym_coconut_dkg_common::types::EpochId;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
@@ -7,12 +7,12 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::dealer::{
ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
use nym_coconut_dkg_common::{
dealer::{ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse},
msg::QueryMsg as DkgQueryMsg,
types::{DealerDetails, Epoch, EpochId, InitialReplacementData},
verification_key::{ContractVKShare, PagedVKSharesResponse},
};
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use nym_coconut_dkg_common::types::{DealerDetails, Epoch, EpochId, InitialReplacementData};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, PagedVKSharesResponse};
use serde::Deserialize;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+2
View File
@@ -43,3 +43,5 @@ mod utils;
pub type Attribute = bls12_381::Scalar;
pub type PrivateAttribute = Attribute;
pub type PublicAttribute = Attribute;
pub use bls12_381::G1Projective;
+7 -8
View File
@@ -362,12 +362,12 @@ pub fn blind_sign(
/// The function returns `true` if the partial blind signature is valid, and `false` otherwise.
pub fn verify_partial_blind_signature(
params: &Parameters,
blind_sign_request: &BlindSignRequest,
private_attribute_commitments: &[G1Projective],
public_attributes: &[&Attribute],
blind_sig: &BlindedSignature,
partial_verification_key: &VerificationKey,
) -> bool {
let num_private_attributes = blind_sign_request.private_attributes_commitments.len();
let num_private_attributes = private_attribute_commitments.len();
if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() {
return false;
}
@@ -388,8 +388,7 @@ pub fn verify_partial_blind_signature(
];
// for each private attribute, add (cm_i, beta_i) to the miller terms
for (private_attr_commit, beta_g2) in blind_sign_request
.private_attributes_commitments
for (private_attr_commit, beta_g2) in private_attribute_commitments
.iter()
.zip(&partial_verification_key.beta_g2)
{
@@ -422,7 +421,7 @@ pub fn verify_partial_blind_signature(
// is equivalent to checking e(a, b) • e(c, d)^{-1} == id
// and thus to e(a, b) • e(c^{-1}, d) == id
//
// compute e(c^1, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
// compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
multi_miller_loop(&terms_refs)
.final_exponentiation()
.is_identity()
@@ -519,7 +518,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator_keypair.verification_key()
@@ -539,7 +538,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&[],
&blind_sig,
validator_keypair.verification_key()
@@ -568,7 +567,7 @@ mod tests {
// this assertion should fail, as we try to verify with a wrong validator key
assert!(!verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator2_keypair.verification_key()
@@ -187,18 +187,18 @@ pub struct EpochCredentialsResponse {
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialsResponse {
// note: BTreeMap returns ordered results so it's fine to use it with pagination
pub credentials: BTreeMap<i64, IssuedCredentialInner>,
pub credentials: BTreeMap<i64, IssuedCredentialBody>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialResponse {
pub credential: Option<IssuedCredentialInner>,
pub credential: Option<IssuedCredentialBody>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialInner {
pub struct IssuedCredentialBody {
pub credential: IssuedCredential,
pub signature: identity::Signature,
+2 -2
View File
@@ -3,7 +3,7 @@
use crate::coconut::error::Result;
use crate::coconut::storage::models::IssuedCredential;
use nym_api_requests::coconut::models::IssuedCredentialInner;
use nym_api_requests::coconut::models::IssuedCredentialBody;
use nym_api_requests::coconut::models::IssuedCredentialsResponse;
use std::collections::BTreeMap;
@@ -14,7 +14,7 @@ pub(crate) fn build_credentials_response(
for raw_credential in raw {
let id = raw_credential.id;
let api_issued = IssuedCredentialInner::try_from(raw_credential)?;
let api_issued = IssuedCredentialBody::try_from(raw_credential)?;
let old = credentials.insert(id, api_issued);
if old.is_some() {
// why do we panic here rather than return an error? because it's a critical failure because
+1 -8
View File
@@ -4,7 +4,7 @@
use crate::coconut::error::CoconutError;
use nym_api_requests::coconut::models::{
EpochCredentialsResponse, IssuedCredential as ApiIssuedCredential,
IssuedCredentialInner as ApiIssuedCredentialInner,
IssuedCredentialBody as ApiIssuedCredentialInner,
};
use nym_api_requests::coconut::BlindedSignatureResponse;
use nym_coconut::{Base58, BlindedSignature};
@@ -97,13 +97,6 @@ impl TryFrom<IssuedCredential> for BlindedSignature {
}
}
impl IssuedCredential {
// safety: this should only ever be called on sanitized data from the database,
// thus the unwraps are fine (if somebody manually entered their db file and modified it, it's on them)
// pub fn private_attribute_commitments(&self) -> Vec<Scalar>
// pub fn public_attributes(&self)
}
pub fn join_attributes<I, M>(attrs: I) -> String
where
I: IntoIterator<Item = M>,
+2 -2
View File
@@ -9,7 +9,7 @@ use async_trait::async_trait;
use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_api_requests::coconut::models::{IssuedCredentialInner, IssuedCredentialResponse};
use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
@@ -716,7 +716,7 @@ impl TestFixture {
serde_json::from_str(&response.into_string().await.unwrap()).unwrap()
}
async fn issued_unchecked(&self, id: i64) -> IssuedCredentialInner {
async fn issued_unchecked(&self, id: i64) -> IssuedCredentialBody {
self.issued_credential(id)
.await
.unwrap()
+4
View File
@@ -31,9 +31,13 @@ humantime-serde = "1.0"
# internal
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-config = { path = "../common/config" }
nym-coconut = { path = "../common/nymcoconut" }
nym-credentials = { path = "../common/credentials" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg"}
nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract"}
nyxd-scraper = { path = "../common/nyxd-scraper" }
[build-dependencies]
@@ -9,6 +9,7 @@ CREATE TABLE rewarding_epoch
start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
budget TEXT NOT NULL,
spent TEXT NOT NULL,
rewarding_tx TEXT,
rewarding_error TEXT
);
@@ -51,12 +52,12 @@ CREATE TABLE malformed_credential
CREATE TABLE credential_issuance_reward
(
rewarding_epoch_id INTEGER NOT NULL REFERENCES rewarding_epoch (id),
operator_account TEXT NOT NULL,
amount TEXT NOT NULL,
api_endpoint TEXT NOT NULL,
issued_partial_credentials INTEGER NOT NULL,
issued_credentials_share TEXT NOT NULL,
rewarding_epoch_id INTEGER NOT NULL REFERENCES rewarding_epoch (id),
operator_account TEXT NOT NULL,
amount TEXT NOT NULL,
api_endpoint TEXT NOT NULL,
issued_partial_credentials INTEGER NOT NULL,
issued_credentials_share TEXT NOT NULL,
validated_issued_credentials INTEGER NOT NULL,
UNIQUE (rewarding_epoch_id, operator_account)
+1 -1
View File
@@ -255,7 +255,7 @@ pub struct NyxdScraper {
// TODO: debug with everything that's currently hardcoded in the scraper
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, Copy)]
pub struct IssuanceMonitor {
#[serde(with = "humantime_serde")]
pub run_interval: Duration,
+58
View File
@@ -1,8 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_coconut::CoconutError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::tx::ErrorReport;
use nym_validator_client::nyxd::{AccountId, Hash};
use std::io;
use std::path::PathBuf;
use thiserror::Error;
@@ -85,4 +88,59 @@ pub enum NymRewarderError {
#[error("could not find details for validator {consensus_address}")]
MissingValidatorDetails { consensus_address: String },
#[error("api url ({raw}) provided by {runner_account} is invalid: {source}")]
MalformedApiUrl {
raw: String,
runner_account: AccountId,
#[source]
source: url::ParseError,
},
#[error("failed to resolve nym-api query: {0}")]
ApiQueryFailure(#[from] NymAPIError),
#[error("operator {runner_account} didn't return all requested credentials! requested {requested} but got only {received}")]
IncompleteRequest {
runner_account: AccountId,
requested: usize,
received: usize,
},
#[error("the following private attribute commitment is malformed: {raw}: {source}")]
MalformedCredentialCommitment {
raw: String,
#[source]
source: CoconutError,
},
#[error("could not verify the blinded credential")]
BlindVerificationFailure,
#[error("the same deposit transaction ({tx_hash}) has been used for multiple issued credentials! {first} and {other}")]
DuplicateDepositHash {
tx_hash: Hash,
first: i64,
other: i64,
},
#[error("could not find the deposit value in the event of transaction {tx_hash}")]
DepositValueNotFound { tx_hash: Hash },
#[error("could not find the deposit info in the event of transaction {tx_hash}")]
DepositInfoNotFound { tx_hash: Hash },
#[error("the provided deposit value of transaction {tx_hash} is inconsistent. got '{request:?}' while the value on chain is '{on_chain}'")]
InconsistentDepositValue {
tx_hash: Hash,
request: Option<String>,
on_chain: String,
},
#[error("the provided deposit info of transaction {tx_hash}is inconsistent. got '{request:?}' while the value on chain is '{on_chain}'")]
InconsistentDepositInfo {
tx_hash: Hash,
request: Option<String>,
on_chain: String,
},
}
@@ -1,12 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config;
use crate::error::NymRewarderError;
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 nym_task::TaskClient;
use nym_validator_client::nyxd::AccountId;
use std::time::Duration;
use tracing::info;
@@ -16,29 +17,25 @@ pub mod types;
pub struct CredentialIssuance {
monitoring_run_interval: Duration,
monitoring_results: MonitoringResults,
dkg_contract_address: AccountId,
}
impl CredentialIssuance {
pub(crate) fn new(
epoch: Epoch,
monitoring_run_interval: Duration,
dkg_contract_address: AccountId,
) -> Self {
pub(crate) fn new(epoch: Epoch, monitoring_run_interval: Duration) -> Self {
CredentialIssuance {
monitoring_run_interval,
monitoring_results: MonitoringResults::new(epoch),
dkg_contract_address,
}
}
pub(crate) fn start_monitor(&self, task_client: TaskClient) {
pub(crate) fn start_monitor(
&self,
monitor_config: config::IssuanceMonitor,
nyxd_client: NyxdClient,
task_client: TaskClient,
) {
let monitoring_results = self.monitoring_results.clone();
let mut monitor = CredentialIssuanceMonitor::new(
self.monitoring_run_interval,
self.dkg_contract_address.clone(),
monitoring_results,
);
let mut monitor =
CredentialIssuanceMonitor::new(monitor_config, nyxd_client, monitoring_results);
tokio::spawn(async move { monitor.run(task_client).await });
}
@@ -1,42 +1,258 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config;
use crate::error::NymRewarderError;
use crate::rewarder::credential_issuance::types::MonitoringResults;
use crate::rewarder::credential_issuance::types::{CredentialIssuer, MonitoringResults};
use crate::rewarder::nyxd_client::NyxdClient;
use bip39::rand::prelude::SliceRandom;
use bip39::rand::thread_rng;
use nym_coconut::{
hash_to_scalar, verify_partial_blind_signature, Base58, G1Projective, Parameters,
VerificationKey,
};
use nym_coconut_dkg_common::types::EpochId;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_task::TaskClient;
use nym_validator_client::nyxd::AccountId;
use std::time::Duration;
use nym_validator_client::nym_api;
use nym_validator_client::nym_api::{IssuedCredentialBody, NymApiClientExt};
use nym_validator_client::nyxd::Hash;
use std::collections::HashMap;
use std::sync::OnceLock;
use tokio::time::interval;
use tracing::{error, info};
use tracing::{debug, error, info, warn};
pub(crate) fn bandwidth_voucher_params() -> &'static Parameters {
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(|| BandwidthVoucher::default_parameters())
}
pub struct CredentialIssuanceMonitor {
run_interval: Duration,
dkg_contract_address: AccountId,
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<String, HashMap<Hash, i64>>,
}
impl CredentialIssuanceMonitor {
pub fn new(
run_interval: Duration,
dkg_contract_address: AccountId,
config: config::IssuanceMonitor,
nyxd_client: NyxdClient,
monitoring_results: MonitoringResults,
) -> CredentialIssuanceMonitor {
CredentialIssuanceMonitor {
run_interval,
dkg_contract_address,
config,
nyxd_client,
monitoring_results,
seen_deposits: Default::default(),
}
}
// 1. if not present -> go to DKG contract and grab the accounts + endpoints
// TODO: currently we can't obtain public key of the runner in order to verify the signature
async fn validate_issued_credential(
&mut self,
runner: String,
credential_id: i64,
issued_credential: IssuedCredentialBody,
vk: &VerificationKey,
) -> Result<(), NymRewarderError> {
warn!("unimplemented: public key sharing mechanism");
// let plaintext = issued_credential.credential.signable_plaintext();
// if !operator_public_key.verify(&plaintext) {
// ...
// }
let tx_hash = issued_credential.credential.tx_hash;
// 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(&tx_hash) {
return if used != credential_id {
Err(NymRewarderError::DuplicateDepositHash {
tx_hash,
first: used,
other: credential_id,
})
} else {
debug!("we have already verified this credential before");
Ok(())
};
} else {
known_runner.insert(tx_hash, credential_id);
}
}
// check if this deposit even exists
let (deposit_value, deposit_info) = self
.nyxd_client
.get_deposit_transaction_attributes(tx_hash)
.await?;
// check if the deposit values match
let credential_value = issued_credential.credential.public_attributes.get(0);
let credential_info = issued_credential.credential.public_attributes.get(1);
if credential_value != Some(&deposit_value) {
return Err(NymRewarderError::InconsistentDepositValue {
tx_hash,
request: credential_value.cloned(),
on_chain: deposit_value,
});
}
if credential_info != Some(&deposit_info) {
return Err(NymRewarderError::InconsistentDepositInfo {
tx_hash,
request: credential_info.cloned(),
on_chain: deposit_info,
});
}
let public_attributes = issued_credential
.credential
.public_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<_>>();
#[allow(clippy::map_identity)]
let attributes_refs = public_attributes.iter().collect::<Vec<_>>();
let mut public_attribute_commitments = Vec::with_capacity(
issued_credential
.credential
.bs58_encoded_private_attributes_commitments
.len(),
);
for raw_cm in issued_credential
.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,
source,
})
}
}
}
// actually do verify the credential now
if !verify_partial_blind_signature(
bandwidth_voucher_params(),
&public_attribute_commitments,
&attributes_refs,
&issued_credential.credential.blinded_partial_credential,
vk,
) {
return Err(NymRewarderError::BlindVerificationFailure);
}
Ok(())
}
async fn check_issuer(
&mut self,
epoch_id: EpochId,
issuer: CredentialIssuer,
) -> Result<(), NymRewarderError> {
let url = match issuer.api_runner.parse() {
Ok(url) => url,
Err(source) => {
return Err(NymRewarderError::MalformedApiUrl {
raw: issuer.api_runner,
runner_account: issuer.operator_account,
source,
})
}
};
let api_client = nym_api::Client::new(url, None);
let epoch_credentials = api_client.epoch_credentials(epoch_id).await?;
let Some(first_id) = epoch_credentials.first_epoch_credential_id else {
// no point in doing anything more - if they haven't issued anything, there's nothing to verify
debug!(
"{} hasn't issued any credentials this epoch",
issuer.operator_account
);
return Ok(());
};
let credential_range: Vec<_> =
(first_id..first_id + epoch_credentials.total_issued as i64).collect();
let issued = credential_range.len();
let sampled = if issued <= self.config.min_validate_per_issuer as usize {
credential_range
} else {
let mut rng = thread_rng();
let sample_size = (issued as f32 * self.config.sampling_rate) as usize;
credential_range
.choose_multiple(&mut rng, sample_size)
.copied()
.collect::<Vec<_>>()
};
let request_size = sampled.len();
let credentials = api_client.issued_credentials(sampled).await?;
if credentials.credentials.len() != request_size {
// TODO: we need some signatures here to actually show the validator is cheating
return Err(NymRewarderError::IncompleteRequest {
runner_account: issuer.operator_account,
requested: request_size,
received: credentials.credentials.len(),
});
}
for (id, credential) in credentials.credentials {
// 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
{
error!(
"failed to verify credential {id} from {}!!: {err}",
issuer.operator_account
);
return Err(err);
}
}
Ok(())
}
async fn check_issuers(&mut self) -> Result<(), NymRewarderError> {
let epoch = self.nyxd_client.dkg_epoch().await?;
let issuers = self
.nyxd_client
.get_credential_issuers(epoch.epoch_id)
.await?;
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
if let Err(err) = self.check_issuer(epoch.epoch_id, issuer).await {
// TODO: insert info to the db
error!("failed to check credential issuance of {address}: {err}")
}
}
Ok(())
}
pub async fn run(&mut self, mut task_client: TaskClient) {
info!("starting");
let mut run_interval = interval(self.run_interval);
let mut run_interval = interval(self.config.run_interval);
while !task_client.is_shutdown() {
tokio::select! {
@@ -1,13 +1,15 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymRewarderError;
use crate::rewarder::epoch::Epoch;
use cosmwasm_std::{Decimal, Uint128};
use cosmwasm_std::{Addr, Decimal, Uint128};
use nym_coconut::VerificationKey;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_validator_client::nyxd::{AccountId, Coin};
use std::mem;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{error, info};
use tracing::info;
#[derive(Clone)]
pub struct MonitoringResults {
@@ -36,43 +38,46 @@ impl MonitoringResults {
dkg_epoch: u32,
operators: Vec<(String, AccountId)>,
) {
let mut guard = self.inner.lock().await;
guard.operators = operators
.into_iter()
.map(|(api_runner, runner_account)| RawOperatorIssuing {
api_runner,
runner_account,
issued_credentials: 0,
validated_credentials: 0,
})
.collect();
guard.dkg_epoch = Some(dkg_epoch)
todo!()
// let mut guard = self.inner.lock().await;
// guard.operators = operators
// .into_iter()
// .map(|(api_runner, runner_account)| RawOperatorIssuing {
// api_runner,
// runner_account,
// issued_credentials: 0,
// validated_credentials: 0,
// })
// .collect();
// guard.dkg_epoch = Some(dkg_epoch)
}
pub(crate) async fn append_run_results(&self, results: Vec<(String, RawOperatorResult)>) {
let mut guard = self.inner.lock().await;
// sure, a hashmap would have been quicker, but we'll have at most 30-40 runners so
// performance overhead is negligible
for (api_runner, results) in results {
if let Some(entry) = guard
.operators
.iter_mut()
.find(|o| o.api_runner == api_runner)
{
entry.issued_credentials += results.issued_credentials;
entry.validated_credentials += results.validated_credentials;
} else {
error!("somehow could not find operator results for runner {api_runner}!")
}
}
todo!()
// let mut guard = self.inner.lock().await;
//
// // sure, a hashmap would have been quicker, but we'll have at most 30-40 runners so
// // performance overhead is negligible
// for (api_runner, results) in results {
// if let Some(entry) = guard
// .operators
// .iter_mut()
// .find(|o| o.api_runner == api_runner)
// {
// entry.issued_credentials += results.issued_credentials;
// entry.validated_credentials += results.validated_credentials;
// } else {
// error!("somehow could not find operator results for runner {api_runner}!")
// }
// }
}
pub(crate) async fn finish_epoch(&self) -> MonitoringResultsInner {
let mut guard = self.inner.lock().await;
let next_epoch = guard.epoch.next();
let next_results = MonitoringResultsInner::new(next_epoch);
mem::replace(&mut guard, next_results)
todo!()
// let mut guard = self.inner.lock().await;
// let next_epoch = guard.epoch.next();
// let next_results = MonitoringResultsInner::new(next_epoch);
// mem::replace(&mut guard, next_results)
}
}
@@ -138,6 +143,8 @@ pub struct RawOperatorIssuing {
pub issued_credentials: u32,
pub validated_credentials: u32,
pub(crate) starting_credential_id: u32,
}
pub struct OperatorIssuing {
@@ -180,3 +187,24 @@ impl CredentialIssuanceResults {
.collect()
}
}
pub struct CredentialIssuer {
pub operator_account: AccountId,
pub api_runner: String,
pub verification_key: VerificationKey,
}
impl TryFrom<ContractVKShare> for CredentialIssuer {
type Error = NymRewarderError;
fn try_from(value: ContractVKShare) -> Result<Self, Self::Error> {
todo!()
}
}
// 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 {
#[allow(clippy::unwrap_used)]
addr.as_str().parse().unwrap()
}
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use cosmwasm_std::Decimal;
use nym_validator_client::nyxd::{AccountId, PublicKey};
use nyxd_scraper::constants::{BECH32_CONSENSUS_ADDRESS_PREFIX, BECH32_PREFIX};
use sha2::{Digest, Sha256};
+14 -4
View File
@@ -48,6 +48,15 @@ impl EpochRewards {
amounts
}
pub fn total_spent(&self) -> Coin {
let amount = self
.amounts()
.into_iter()
.map(|(_, amount)| amount[0].amount)
.sum();
Coin::new(amount, &self.total_budget.denom)
}
}
pub struct Rewarder {
@@ -63,7 +72,6 @@ pub struct Rewarder {
impl Rewarder {
pub async fn new(config: Config) -> Result<Self, NymRewarderError> {
let nyxd_scraper = NyxdScraper::new(config.scraper_config()).await?;
let dkg_contract = config.dkg_contract_address();
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? {
@@ -77,7 +85,6 @@ impl Rewarder {
credential_issuance: CredentialIssuance::new(
current_epoch,
config.issuance_monitor.run_interval,
dkg_contract,
),
epoch_signing: EpochSigning {
nyxd_scraper,
@@ -176,8 +183,11 @@ impl Rewarder {
// setup shutdowns
let mut task_manager = TaskManager::new(5);
self.credential_issuance
.start_monitor(task_manager.subscribe());
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
@@ -3,12 +3,23 @@
use crate::config::Config;
use crate::error::NymRewarderError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::module_traits::staking::{
QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse,
use crate::rewarder::credential_issuance::types::CredentialIssuer;
use cosmwasm_std::Addr;
use nym_coconut::VerificationKey;
use nym_coconut_bandwidth_contract_common::events::{
COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO, DEPOSIT_VALUE,
};
use nym_validator_client::nyxd::{AccountId, CosmWasmClient, PageRequest, StakingQueryClient};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use nym_coconut_dkg_common::types::Epoch;
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
use nym_validator_client::nyxd::helpers::find_tx_attribute;
use nym_validator_client::nyxd::module_traits::staking::{
QueryHistoricalInfoResponse, QueryValidatorsResponse,
};
use nym_validator_client::nyxd::{
AccountId, CosmWasmClient, Hash, PageRequest, StakingQueryClient,
};
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
use std::ops::Deref;
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -51,11 +62,44 @@ impl NyxdClient {
&self,
pagination: Option<PageRequest>,
) -> Result<QueryValidatorsResponse, NymRewarderError> {
Ok(self
.inner
let guard = self.inner.read().await;
Ok(StakingQueryClient::validators(guard.deref(), "".to_string(), pagination).await?)
}
pub(crate) async fn dkg_epoch(&self) -> Result<Epoch, NymRewarderError> {
Ok(self.inner.read().await.get_current_epoch().await?)
}
pub(crate) async fn get_credential_issuers(
&self,
dkg_epoch: u64,
) -> Result<Vec<CredentialIssuer>, NymRewarderError> {
self.inner
.read()
.await
.validators("".to_string(), pagination)
.await?)
.get_all_verification_key_shares(dkg_epoch)
.await?
.into_iter()
.map(TryInto::try_into)
.collect()
}
pub(crate) async fn get_deposit_transaction_attributes(
&self,
tx_hash: Hash,
) -> Result<(String, String), NymRewarderError> {
let tx = self.inner.read().await.get_tx(tx_hash).await?;
// todo: we need to make it more concrete that the first attribute is the deposit value
// and the second one is the deposit info
let deposit_value =
find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_VALUE)
.ok_or(NymRewarderError::DepositValueNotFound { tx_hash })?;
let deposit_info =
find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO)
.ok_or(NymRewarderError::DepositInfoNotFound { tx_hash })?;
Ok((deposit_value, deposit_info))
}
}
@@ -29,18 +29,20 @@ impl StorageManager {
&self,
epoch: Epoch,
rewarding_budget: String,
total_spent: String,
rewarding_tx: Option<String>,
rewarding_error: Option<String>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO rewarding_epoch (id, start_time, end_time, budget, rewarding_tx, rewarding_error)
VALUES (?, ?, ? ,?, ?, ?)
INSERT INTO rewarding_epoch (id, start_time, end_time, budget, spent, rewarding_tx, rewarding_error)
VALUES (?, ?, ? ,?, ?, ?, ?)
"#,
epoch.id,
epoch.start_time,
epoch.end_time,
rewarding_budget,
total_spent: String,
rewarding_tx,
rewarding_error
).execute(&self.connection_pool).await?;
@@ -72,6 +72,7 @@ impl RewarderStorage {
.insert_rewarding_epoch(
reward.epoch,
reward.total_budget.to_string(),
reward.total_spent().to_string(),
reward_tx,
reward_err,
)