Skip to content

Commit

Permalink
Move cred verification to common crate
Browse files Browse the repository at this point in the history
  • Loading branch information
neacsu committed Sep 9, 2024
1 parent 18891e5 commit 52dff83
Show file tree
Hide file tree
Showing 23 changed files with 577 additions and 461 deletions.
32 changes: 26 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ members = [
"wasm/node-tester",
"wasm/zknym-lib",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract", "common/credential-verification",
]

default-members = [
Expand Down
33 changes: 33 additions & 0 deletions common/credential-verification/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "nym-credential-verification"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true

[dependencies]
bs58 = { workspace = true }
cosmwasm-std = { workspace = true }
cw-utils = { workspace = true }
futures = { workspace = true }
rand = { workspace = true }
si-scale = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
time = { workspace = true }
tracing = { workspace = true }

nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-credentials = { path = "../credentials" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
nym-ecash-double-spending = { path = "../ecash-double-spending" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-gateway-storage = { path = "../gateway-storage" }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
148 changes: 148 additions & 0 deletions common/credential-verification/src/bandwidth_storage_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::Bandwidth;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::Storage;
use si_scale::helpers::bibytes2;
use time::OffsetDateTime;
use tracing::*;

use crate::error::*;
use crate::BandwidthFlushingBehaviourConfig;
use crate::ClientBandwidth;

const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB

#[derive(Clone)]
pub struct BandwidthStorageManager<S> {
pub(crate) storage: S,
pub(crate) client_bandwidth: ClientBandwidth,
pub(crate) client_id: i64,
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
pub(crate) only_coconut_credentials: bool,
}

impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
pub fn new(
storage: S,
client_bandwidth: ClientBandwidth,
client_id: i64,
bandwidth_cfg: BandwidthFlushingBehaviourConfig,
only_coconut_credentials: bool,
) -> Self {
BandwidthStorageManager {
storage,
client_bandwidth,
client_id,
bandwidth_cfg,
only_coconut_credentials,
}
}

async fn sync_expiration(&mut self) -> Result<()> {
self.storage
.set_expiration(self.client_id, self.client_bandwidth.bandwidth.expiration)
.await?;
Ok(())
}

pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<ServerResponse> {
debug!("handling testnet bandwidth request");

if self.only_coconut_credentials {
return Err(Error::OnlyCoconutCredentials);
}

self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
.await?;
let available_total = self.client_bandwidth.bandwidth.bytes;

Ok(ServerResponse::Bandwidth { available_total })
}

#[instrument(skip_all)]
pub async fn try_use_bandwidth(&mut self, required_bandwidth: i64) -> Result<i64> {
if self.client_bandwidth.bandwidth.expired() {
self.expire_bandwidth().await?;
}
let available_bandwidth = self.client_bandwidth.bandwidth.bytes;

if available_bandwidth < required_bandwidth {
return Err(Error::OutOfBandwidth {
required: required_bandwidth,
available: available_bandwidth,
});
}

let available_bi2 = bibytes2(available_bandwidth as f64);
let required_bi2 = bibytes2(required_bandwidth as f64);
debug!(available = available_bi2, required = required_bi2);

self.consume_bandwidth(required_bandwidth).await?;
Ok(available_bandwidth)
}

async fn expire_bandwidth(&mut self) -> Result<()> {
self.storage.reset_bandwidth(self.client_id).await?;
self.client_bandwidth.bandwidth = Default::default();
self.client_bandwidth.update_sync_data();
Ok(())
}

/// Decreases the amount of available bandwidth of the connected client by the specified value.
///
/// # Arguments
///
/// * `amount`: amount to decrease the available bandwidth by.
async fn consume_bandwidth(&mut self, amount: i64) -> Result<()> {
self.client_bandwidth.bandwidth.bytes -= amount;
self.client_bandwidth.bytes_delta_since_sync -= amount;

// since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets
// through, that's completely fine
if self.client_bandwidth.should_sync(self.bandwidth_cfg) {
self.sync_bandwidth().await?;
}

Ok(())
}

#[instrument(level = "trace", skip_all)]
async fn sync_bandwidth(&mut self) -> Result<()> {
trace!("syncing client bandwidth with the underlying storage");
let updated = self
.storage
.increase_bandwidth(self.client_id, self.client_bandwidth.bytes_delta_since_sync)
.await?;

trace!(updated);

self.client_bandwidth.bandwidth.bytes = updated;

self.client_bandwidth.update_sync_data();
Ok(())
}

/// Increases the amount of available bandwidth of the connected client by the specified value.
///
/// # Arguments
///
/// * `amount`: amount to increase the available bandwidth by.
/// * `expiration` : the expiration date of that bandwidth
pub async fn increase_bandwidth(
&mut self,
bandwidth: Bandwidth,
expiration: OffsetDateTime,
) -> Result<()> {
self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64;
self.client_bandwidth.bytes_delta_since_sync += bandwidth.value() as i64;
self.client_bandwidth.bandwidth.expiration = expiration;

// any increases to bandwidth should get flushed immediately
// (we don't want to accidentally miss somebody claiming a gigabyte voucher)
self.sync_expiration().await?;
self.sync_bandwidth().await
}
}
57 changes: 57 additions & 0 deletions common/credential-verification/src/client_bandwidth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use std::time::Duration;

use nym_credentials_interface::AvailableBandwidth;
use time::OffsetDateTime;

#[derive(Debug, Clone, Copy)]
pub struct BandwidthFlushingBehaviourConfig {
/// Defines maximum delay between client bandwidth information being flushed to the persistent storage.
pub client_bandwidth_max_flushing_rate: Duration,

/// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage.
pub client_bandwidth_max_delta_flushing_amount: i64,
}

#[derive(Debug, Clone, Copy)]
pub struct ClientBandwidth {
pub(crate) bandwidth: AvailableBandwidth,
pub(crate) last_flushed: OffsetDateTime,

/// the number of bytes the client had during the last sync.
/// it is used to determine whether the current value should be synced with the storage
/// by checking the delta with the known amount
pub(crate) bytes_at_last_sync: i64,
pub(crate) bytes_delta_since_sync: i64,
}

impl ClientBandwidth {
pub fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth {
ClientBandwidth {
bandwidth,
last_flushed: OffsetDateTime::now_utc(),
bytes_at_last_sync: bandwidth.bytes,
bytes_delta_since_sync: 0,
}
}

pub(crate) fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool {
if self.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount {
return true;
}

if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() {
return true;
}

false
}

pub(crate) fn update_sync_data(&mut self) {
self.last_flushed = OffsetDateTime::now_utc();
self.bytes_at_last_sync = self.bandwidth.bytes;
self.bytes_delta_since_sync = 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright 2022-2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use crate::node::client_handling::bandwidth::Bandwidth;
use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError;
use crate::node::client_handling::websocket::connection_handler::ecash::helpers::for_each_api_concurrent;
use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState;
use crate::GatewayError;
use crate::ecash::error::EcashTicketError;
use crate::ecash::helpers::for_each_api_concurrent;
use crate::ecash::state::SharedState;
use crate::Error;
use cosmwasm_std::Fraction;
use cw_utils::ThresholdResponse;
use futures::channel::mpsc::UnboundedReceiver;
use futures::{Stream, StreamExt};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_gateway_storage::Storage;
use nym_validator_client::nym_api::EpochId;
Expand Down Expand Up @@ -105,25 +105,25 @@ impl PendingRedemptionVote {
}
}

pub(crate) struct CredentialHandlerConfig {
pub struct CredentialHandlerConfig {
/// Specifies the multiplier for revoking a malformed/double-spent ticket
/// (if it has to go all the way to the nym-api for verification)
/// e.g. if one ticket grants 100Mb and `revocation_bandwidth_penalty` is set to 1.5,
/// the client will lose 150Mb
pub(crate) revocation_bandwidth_penalty: f32,
pub revocation_bandwidth_penalty: f32,

/// Specifies the interval for attempting to resolve any failed, pending operations,
/// such as ticket verification or redemption.
pub(crate) pending_poller: Duration,
pub pending_poller: Duration,

pub(crate) minimum_api_quorum: f32,
pub minimum_api_quorum: f32,

/// Specifies the minimum number of tickets this gateway will attempt to redeem.
pub(crate) minimum_redemption_tickets: usize,
pub minimum_redemption_tickets: usize,

/// Specifies the maximum time between two subsequent tickets redemptions.
/// That's required as nym-apis will purge all ticket information for tickets older than 30 days.
pub(crate) maximum_time_between_redemption: Duration,
pub maximum_time_between_redemption: Duration,
}

pub(crate) struct CredentialHandler<St: Storage> {
Expand Down Expand Up @@ -260,7 +260,7 @@ where
config: CredentialHandlerConfig,
ticket_receiver: UnboundedReceiver<ClientTicket>,
shared_state: SharedState<St>,
) -> Result<Self, GatewayError> {
) -> Result<Self, Error> {
let multisig_threshold = shared_state
.nyxd_client
.read()
Expand All @@ -269,7 +269,7 @@ where
.await?;

let ThresholdResponse::AbsolutePercentage { percentage, .. } = multisig_threshold else {
return Err(GatewayError::InvalidMultisigThreshold);
return Err(Error::InvalidMultisigThreshold);
};

// that's a nasty conversion, but it works : )
Expand Down
Loading

0 comments on commit 52dff83

Please sign in to comment.