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
This commit is contained in:
committed by
GitHub
parent
1ded24dcfc
commit
a6fda391ae
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* 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
|
||||
)
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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<Self, NymRewarderError> {
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -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<String, HashMap<Hash, i64>>,
|
||||
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<bool, NymRewarderError> {
|
||||
// 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<RawOperatorResult, NymRewarderError> {
|
||||
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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ContractVKShare> for CredentialIssuer {
|
||||
type Error = NymRewarderError;
|
||||
|
||||
fn try_from(value: ContractVKShare) -> Result<Self, Self::Error> {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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<Vec<CredentialIssuer>, 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(
|
||||
|
||||
@@ -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<u8>,
|
||||
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<Option<i64>, 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<u8>,
|
||||
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<u8>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Option<i64>, 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<RewardingResult, NymRewarderError>, // total_spent: Coin,
|
||||
// rewarding_tx: Result<Hash, NymRewarderError>,
|
||||
rewarding_result: Result<RewardingResult, NymRewarderError>,
|
||||
// total_spent: Coin,
|
||||
// rewarding_tx: Result<Hash, NymRewarderError>,
|
||||
) -> Result<(), NymRewarderError> {
|
||||
info!("persisting reward details");
|
||||
let denom = &reward.total_budget.denom;
|
||||
|
||||
Reference in New Issue
Block a user