Feature/remove double spending bloomfilter (#5417)

* removed all uses of the bloomfilter inside nym-api

* changed http status code on bf queries
This commit is contained in:
Jędrzej Stuczyński
2025-02-03 16:11:13 +00:00
committed by GitHub
parent d1fb926a2a
commit 70e2e32385
23 changed files with 226 additions and 869 deletions
Generated
+3 -38
View File
@@ -720,12 +720,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bitcoin_hashes"
version = "0.11.0"
@@ -821,17 +815,6 @@ dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "bloomfilter"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0bdbcf2078e0ba8a74e1fe0cf36f54054a04485759b61dfd60b174658e9607"
dependencies = [
"bit-vec",
"getrandom",
"siphasher 1.0.1",
]
[[package]]
name = "bls12_381"
version = "0.8.0"
@@ -4635,7 +4618,6 @@ dependencies = [
"axum-test",
"bincode",
"bip39",
"bloomfilter",
"bs58",
"cfg-if",
"clap",
@@ -4666,7 +4648,6 @@ dependencies = [
"nym-crypto",
"nym-dkg",
"nym-ecash-contract-common",
"nym-ecash-double-spending",
"nym-ecash-time",
"nym-gateway-client",
"nym-http-api-common",
@@ -5377,7 +5358,6 @@ dependencies = [
"nym-credentials",
"nym-credentials-interface",
"nym-ecash-contract-common",
"nym-ecash-double-spending",
"nym-gateway-requests",
"nym-gateway-storage",
"nym-task",
@@ -5494,15 +5474,6 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "nym-ecash-double-spending"
version = "0.1.0"
dependencies = [
"bit-vec",
"bloomfilter",
"nym-network-defaults",
]
[[package]]
name = "nym-ecash-time"
version = "0.1.0"
@@ -7549,7 +7520,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher 0.3.11",
"siphasher",
]
[[package]]
@@ -7558,7 +7529,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher 0.3.11",
"siphasher",
]
[[package]]
@@ -9144,12 +9115,6 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
@@ -10857,7 +10822,7 @@ checksum = "71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2"
dependencies = [
"anyhow",
"bytes",
"siphasher 0.3.11",
"siphasher",
"uniffi_checksum_derive",
]
-1
View File
@@ -48,7 +48,6 @@ members = [
"common/credentials-interface",
"common/crypto",
"common/dkg",
"common/ecash-double-spending",
"common/ecash-time",
"common/execute",
"common/exit-policy",
@@ -11,8 +11,7 @@ use crate::{
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
VerifyEcashTicketBody,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
@@ -647,13 +646,6 @@ impl NymApiClient {
.await?)
}
#[deprecated]
pub async fn spent_credentials_filter(
&self,
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
Ok(self.nym_api.double_spending_filter_v1().await?)
}
pub async fn partial_expiration_date_signatures(
&self,
expiration_date: Option<Date>,
@@ -849,20 +849,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::DOUBLE_SPENDING_FILTER_V1,
],
NO_PARAMS,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn partial_expiration_date_signatures(
&self,
@@ -13,8 +13,6 @@ pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
pub const ECASH_ROUTES: &str = "ecash";
pub use ecash::*;
@@ -26,7 +26,6 @@ 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" }
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "nym-ecash-double-spending"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
bit-vec = { workspace = true }
bloomfilter = { workspace = true }
nym-network-defaults = { path = "../network-defaults" }
-136
View File
@@ -1,136 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bit_vec::BitVec;
use bloomfilter::Bloom;
use nym_network_defaults::{BloomfilterParameters, ECASH_DS_BLOOMFILTER_PARAMS};
pub struct DoubleSpendingFilter {
params: BloomfilterParameters,
inner: Bloom<Vec<u8>>,
}
impl Default for DoubleSpendingFilter {
fn default() -> Self {
DoubleSpendingFilter::new_empty_ecash()
}
}
pub fn bloom_from_params<T>(params: &BloomfilterParameters, bitvec: BitVec) -> Bloom<Vec<T>> {
assert_eq!(params.bitmap_size, bitvec.len() as u64);
Bloom::from_bit_vec(
bitvec,
params.bitmap_size,
params.num_hashes,
params.sip_keys,
)
}
impl DoubleSpendingFilter {
pub fn new_empty(params: BloomfilterParameters) -> Self {
let bitvec = BitVec::from_elem(params.bitmap_size as usize, false);
DoubleSpendingFilter {
inner: bloom_from_params(&params, bitvec),
params,
}
}
pub fn params(&self) -> BloomfilterParameters {
self.params
}
pub fn rebuild(&self) -> DoubleSpendingFilterBuilder {
DoubleSpendingFilterBuilder::new(self.params)
}
pub fn reset(&mut self) {
self.inner.clear()
}
pub fn new_empty_ecash() -> Self {
DoubleSpendingFilter::new_empty(ECASH_DS_BLOOMFILTER_PARAMS)
}
pub fn builder(params: BloomfilterParameters) -> DoubleSpendingFilterBuilder {
DoubleSpendingFilterBuilder::new(params)
}
pub fn from_bytes(params: BloomfilterParameters, bitmap: &[u8]) -> Self {
DoubleSpendingFilter {
inner: bloom_from_params(&params, BitVec::from_bytes(bitmap)),
params,
}
}
pub fn replace_bitvec(&mut self, new: BitVec) {
self.inner = bloom_from_params(&self.params, new)
}
pub fn dump_bitmap(&self) -> Vec<u8> {
self.inner.bitmap()
}
pub fn set(&mut self, b: &Vec<u8>) {
self.inner.set(b);
}
pub fn check(&self, b: &Vec<u8>) -> bool {
self.inner.check(b)
}
}
pub struct DoubleSpendingFilterBuilder {
params: BloomfilterParameters,
bit_vec_builder: Option<BitVecBuilder>,
}
impl DoubleSpendingFilterBuilder {
pub fn new(params: BloomfilterParameters) -> Self {
DoubleSpendingFilterBuilder {
params,
bit_vec_builder: None,
}
}
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
match &mut self.bit_vec_builder {
None => {
self.bit_vec_builder = Some(BitVecBuilder::new(b));
true
}
Some(builder) => builder.add_bytes(b),
}
}
pub fn build(self) -> DoubleSpendingFilter {
match self.bit_vec_builder {
None => DoubleSpendingFilter::new_empty(self.params),
Some(builder) => DoubleSpendingFilter {
inner: bloom_from_params(&self.params, builder.finish()),
params: self.params,
},
}
}
}
pub struct BitVecBuilder(BitVec);
impl BitVecBuilder {
pub fn new(initial_bitmap: &[u8]) -> Self {
BitVecBuilder(BitVec::from_bytes(initial_bitmap))
}
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
let add = BitVec::from_bytes(b);
if self.0.len() != add.len() {
return false;
}
self.0.or(&add);
true
}
pub fn finish(self) -> BitVec {
self.0
}
}
-2
View File
@@ -16,7 +16,6 @@ async-trait = { workspace = true }
bs58 = { workspace = true }
bip39 = { workspace = true }
bincode.workspace = true
bloomfilter = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive", "env"] }
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
@@ -84,7 +83,6 @@ tracing = { workspace = true }
#ephemera = { path = "../ephemera" }
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" }
nym-ecash-double-spending = { path = "../common/ecash-double-spending" }
nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] }
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" }
@@ -0,0 +1,7 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
DROP TABLE partial_bloomfilter;
DROP TABLE bloomfilter_parameters;
+9 -21
View File
@@ -6,6 +6,7 @@ use crate::ecash::state::EcashState;
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::state::AppState;
use axum::extract::State;
use axum::http::StatusCode;
use axum::{Json, Router};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{
@@ -89,12 +90,6 @@ async fn verify_ticket(
});
}
// check the bloomfilter for obvious double-spending so that we wouldn't need to waste time on crypto verification
// TODO: when blacklisting is implemented, this should get removed
if state.check_bloomfilter(sn).await {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
// actual double spend detection with storage
if let Some(previous_payment) = state.get_ticket_data_by_serial_number(sn).await? {
match nym_compact_ecash::identify::identify(
@@ -127,27 +122,17 @@ async fn verify_ticket(
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket);
}
// finally get EXCLUSIVE lock on the bloomfilter, check if for the final time and insert the SN
let was_present = state
.update_bloomfilter(sn, spend_date, today_ecash)
// store credential and check whether it wasn't already there (due to a parallel request)
let was_inserted = state
.store_verified_ticket(credential_data, gateway_cosmos_addr)
.await?;
if was_present {
if !was_inserted {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
//store credential
state
.store_verified_ticket(credential_data, gateway_cosmos_addr)
.await?;
Ok(Json(EcashTicketVerificationResponse { verified: Ok(()) }))
}
// // for particular SN returns what gateway has submitted it and whether it has been verified correctly
// async fn credential_status() -> ! {
// todo!()
// }
#[utoipa::path(
tag = "Ecash",
post,
@@ -240,5 +225,8 @@ async fn batch_redeem_tickets(
)]
#[deprecated]
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
AxumResult::Err(AxumErrorResponse::new(
"permanently restricted",
StatusCode::GONE,
))
}
-2
View File
@@ -1,2 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::storage::EcashStorageExt;
use crate::node_status_api::models::NymApiStorageError;
use crate::support::config::Config;
use crate::support::storage::NymApiStorage;
use nym_ecash_time::ecash_today_date;
use nym_task::TaskClient;
use std::time::Duration;
use time::Date;
use tokio::task::JoinHandle;
use tracing::{debug, error, trace};
/// Task responsible for clearing out the database from stale ecash data,
/// such as verified tickets or issued partial ticketbooks.
pub struct EcashBackgroundStateCleaner {
run_interval: Duration,
issued_ticketbooks_retention_period_days: u32,
verified_tickets_retention_period_days: u32,
storage: NymApiStorage,
task_client: TaskClient,
}
impl EcashBackgroundStateCleaner {
pub fn new(global_config: &Config, storage: NymApiStorage, task_client: TaskClient) -> Self {
EcashBackgroundStateCleaner {
run_interval: global_config.ecash_signer.debug.stale_data_cleaner_interval,
issued_ticketbooks_retention_period_days: global_config
.ecash_signer
.debug
.issued_ticketbooks_retention_period_days,
verified_tickets_retention_period_days: global_config
.ecash_signer
.debug
.verified_tickets_retention_period_days,
storage,
task_client,
}
}
fn ticketbook_retention_cutoff(&self) -> Date {
ecash_today_date()
- time::Duration::days(self.issued_ticketbooks_retention_period_days as i64)
}
fn verified_tickets_retention_cutoff(&self) -> Date {
ecash_today_date()
- time::Duration::days(self.verified_tickets_retention_period_days as i64)
}
async fn clean_stale_data(&self) -> Result<(), NymApiStorageError> {
// 1. remove old verified tickets
self.storage
.remove_expired_verified_tickets(self.verified_tickets_retention_cutoff())
.await?;
// 2. remove old issued partial ticketbooks
self.storage
.remove_old_issued_ticketbooks(self.ticketbook_retention_cutoff())
.await?;
Ok(())
}
async fn run(&mut self) {
let mut ticker = tokio::time::interval(self.run_interval);
loop {
tokio::select! {
_ = self.task_client.recv() => {
trace!("EcashBackgroundStateCleaner: Received shutdown");
break;
}
_ = ticker.tick() => {
if let Err(err) = self.clean_stale_data().await {
error!("failed to clear out stale data: {err}")
}
}
}
}
debug!("EcashBackgroundStateCleaner: exiting");
}
pub(crate) fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move { self.run().await })
}
}
+1 -93
View File
@@ -2,106 +2,14 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::error::EcashError;
use crate::ecash::state::local::TicketDoubleSpendingFilter;
use crate::ecash::storage::EcashStorageExt;
use crate::support::storage::NymApiStorage;
use futures::{stream, StreamExt};
use nym_compact_ecash::constants;
use nym_config::defaults::BloomfilterParameters;
use nym_dkg::Threshold;
use nym_ecash_double_spending::{DoubleSpendingFilter, DoubleSpendingFilterBuilder};
use nym_ecash_time::{cred_exp_date, ecash_today};
use nym_validator_client::EcashApiClient;
use std::future::Future;
use time::ext::NumericalDuration;
use time::Date;
use tokio::sync::Mutex;
use tracing::{debug, error, info, warn};
// attempt to completely rebuild the bloomfilter data for given day
async fn try_rebuild_today_bloomfilter(
today: Date,
params: BloomfilterParameters,
storage: &NymApiStorage,
) -> Result<DoubleSpendingFilter, EcashError> {
info!("rebuilding bloomfilter for {today}");
let tickets = storage.get_all_spent_tickets_on(today).await?;
debug!(
"there are {} tickets to insert into the filter",
tickets.len()
);
let mut filter = DoubleSpendingFilter::new_empty(params);
for ticket in tickets {
filter.set(&ticket.serial_number)
}
Ok(filter)
}
pub(crate) async fn prepare_partial_bloomfilter_builder(
storage: &NymApiStorage,
params: BloomfilterParameters,
params_id: i64,
start: Date,
days: i64,
) -> Result<DoubleSpendingFilterBuilder, EcashError> {
info!(
"attempting to rebuild partial bloomfilter starting at {start} which includes {days} days"
);
let mut filter_builder = DoubleSpendingFilter::builder(params);
for i in 0..days {
let date = start - i.days();
let Some(bitmap) = storage
.try_load_partial_bloomfilter_bitmap(date, params_id)
.await?
else {
warn!("missing double spending bloomfilter bitmap for {date} (if this API hasn't been running for at least {days} day(s) since 'ecash'-based zk-nyms were introduced this is expected)");
continue;
};
if !filter_builder.add_bytes(&bitmap) {
error!(
"failed to add bitmap from {date} to the global bloomfilter. it may be malformed!"
);
}
}
Ok(filter_builder)
}
pub(super) async fn try_rebuild_bloomfilter(
storage: &NymApiStorage,
) -> Result<TicketDoubleSpendingFilter, EcashError> {
info!("attempting to rebuild the double spending bloomfilter...");
let today = ecash_today().date();
let (params_id, params) = storage.get_double_spending_filter_params().await?;
info!("will use the following parameters: {params:?}");
// we're never going to have persisted data for 'today'. we need to rebuild it from scratch
let today_filter = try_rebuild_today_bloomfilter(today, params, storage).await?;
info!("attempting to rebuild the global filter");
let mut global_filter = prepare_partial_bloomfilter_builder(
storage,
params,
params_id,
today.previous_day().unwrap(),
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
)
.await?;
if !global_filter.add_bytes(&today_filter.dump_bitmap()) {
error!("failed to add bitmap from {today} to the global bloomfilter. it may be malformed!");
}
Ok(TicketDoubleSpendingFilter::new(
today,
params_id,
global_filter.build(),
today_filter,
))
}
use tracing::warn;
pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), EcashError> {
let today = ecash_today();
-70
View File
@@ -9,9 +9,7 @@ use crate::ecash::helpers::{
use crate::ecash::keys::KeyPair;
use crate::ecash::storage::models::IssuedHash;
use nym_api_requests::ecash::models::{CommitedDeposit, DepositId};
use nym_config::defaults::BloomfilterParameters;
use nym_crypto::asymmetric::identity;
use nym_ecash_double_spending::DoubleSpendingFilter;
use nym_ticketbooks_merkle::{
IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf,
};
@@ -21,69 +19,6 @@ use time::Date;
use tokio::sync::RwLock;
use tracing::error;
pub(crate) struct TicketDoubleSpendingFilter {
built_on: Date,
params_id: i64,
today_filter: DoubleSpendingFilter,
global_filter: DoubleSpendingFilter,
}
impl TicketDoubleSpendingFilter {
pub(crate) fn new(
built_on: Date,
params_id: i64,
global_filter: DoubleSpendingFilter,
today_filter: DoubleSpendingFilter,
) -> TicketDoubleSpendingFilter {
TicketDoubleSpendingFilter {
built_on,
params_id,
today_filter,
global_filter,
}
}
pub(crate) fn built_on(&self) -> Date {
self.built_on
}
pub(crate) fn params(&self) -> BloomfilterParameters {
self.today_filter.params()
}
pub(crate) fn params_id(&self) -> i64 {
self.params_id
}
pub(crate) fn check(&self, sn: &Vec<u8>) -> bool {
self.global_filter.check(sn)
}
/// Returns boolean to indicate if the entry was already present
pub(crate) fn insert_both(&mut self, sn: &Vec<u8>) -> bool {
self.today_filter.set(sn);
self.insert_global_only(sn)
}
/// Returns boolean to indicate if the entry was already present
pub(crate) fn insert_global_only(&mut self, sn: &Vec<u8>) -> bool {
let existed = self.global_filter.check(sn);
self.global_filter.set(sn);
existed
}
pub(crate) fn export_today_bitmap(&self) -> Vec<u8> {
self.today_filter.dump_bitmap()
}
pub(crate) fn advance_day(&mut self, date: Date, new_global: DoubleSpendingFilter) {
self.built_on = date;
self.global_filter = new_global;
self.today_filter.reset();
}
}
#[derive(Default)]
pub(crate) struct DailyMerkleTree {
pub(crate) merkle_tree: IssuedTicketbooksMerkleTree,
@@ -207,9 +142,6 @@ pub(crate) struct LocalEcashState {
pub(crate) partial_expiration_date_signatures:
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
// the actual, up to date, bloomfilter
pub(crate) double_spending_filter: Arc<RwLock<TicketDoubleSpendingFilter>>,
// merkle trees for ticketbooks issued for particular expiration dates
pub(crate) issued_merkle_trees: Arc<RwLock<HashMap<Date, DailyMerkleTree>>>,
}
@@ -218,7 +150,6 @@ impl LocalEcashState {
pub(crate) fn new(
ecash_keypair: KeyPair,
identity_keypair: identity::KeyPair,
double_spending_filter: TicketDoubleSpendingFilter,
explicitly_disabled: bool,
) -> Self {
LocalEcashState {
@@ -228,7 +159,6 @@ impl LocalEcashState {
active_signer: Default::default(),
partial_coin_index_signatures: Default::default(),
partial_expiration_date_signatures: Default::default(),
double_spending_filter: Arc::new(RwLock::new(double_spending_filter)),
issued_merkle_trees: Arc::new(Default::default()),
}
}
+51 -172
View File
@@ -8,11 +8,9 @@ use crate::ecash::error::{EcashError, RedemptionError, Result};
use crate::ecash::helpers::{IssuedCoinIndicesSignatures, IssuedExpirationDateSignatures};
use crate::ecash::keys::KeyPair;
use crate::ecash::state::auxiliary::AuxiliaryEcashState;
use crate::ecash::state::cleaner::EcashBackgroundStateCleaner;
use crate::ecash::state::global::GlobalEcachState;
use crate::ecash::state::helpers::{
ensure_sane_expiration_date, prepare_partial_bloomfilter_builder, query_all_threshold_apis,
try_rebuild_bloomfilter,
};
use crate::ecash::state::helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
use crate::ecash::state::local::{DailyMerkleTree, LocalEcashState};
use crate::ecash::storage::models::{SerialNumberWrapper, TicketProvider};
use crate::ecash::storage::EcashStorageExt;
@@ -33,31 +31,30 @@ use nym_compact_ecash::scheme::expiration_date_signatures::{
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
};
use nym_compact_ecash::{
constants, scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
SecretKeyAuth, VerificationKeyAuth,
};
use nym_config::defaults::BloomfilterParameters;
use nym_credentials::ecash::utils::EcashTime;
use nym_credentials::{aggregate_verification_keys, CredentialSpendingData};
use nym_crypto::asymmetric::identity;
use nym_ecash_contract_common::deposit::{Deposit, DepositId};
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
use nym_ecash_double_spending::DoubleSpendingFilter;
use nym_ecash_time::{cred_exp_date, ecash_today_date};
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
use nym_task::TaskClient;
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::EcashApiClient;
use rand::{thread_rng, RngCore};
use std::collections::HashMap;
use std::ops::Deref;
use time::ext::NumericalDuration;
use time::{Date, OffsetDateTime};
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
use tokio::task::JoinHandle;
use tracing::{debug, error, info, warn};
pub(crate) mod auxiliary;
pub(crate) mod bloom;
mod cleaner;
pub(crate) mod global;
mod helpers;
pub(crate) mod local;
@@ -84,10 +81,24 @@ impl EcashStateConfig {
}
}
#[derive(Default)]
pub(crate) enum BackgroundCleanerState {
WaitingStartup(EcashBackgroundStateCleaner),
Running {
_handle: JoinHandle<()>,
},
// an ephemeral state so that we could swap between the other two
#[default]
Invalid,
}
pub struct EcashState {
// additional global config parameters
pub(crate) config: EcashStateConfig,
pub(crate) background_cleaner_state: BackgroundCleanerState,
// state global to the system, like aggregated keys, addresses, etc.
pub(crate) global: GlobalEcachState,
@@ -99,7 +110,8 @@ pub struct EcashState {
}
impl EcashState {
pub(crate) async fn new<C, D>(
#[allow(clippy::too_many_arguments)]
pub(crate) fn new<C, D>(
global_config: &Config,
contract_address: AccountId,
client: C,
@@ -107,24 +119,38 @@ impl EcashState {
key_pair: KeyPair,
comm_channel: D,
storage: NymApiStorage,
) -> Result<Self>
task_client: TaskClient,
) -> Self
where
C: LocalClient + Send + Sync + 'static,
D: APICommunicationChannel + Send + Sync + 'static,
{
let double_spending_filter = try_rebuild_bloomfilter(&storage).await?;
Ok(Self {
Self {
config: EcashStateConfig::new(global_config),
background_cleaner_state: BackgroundCleanerState::WaitingStartup(
EcashBackgroundStateCleaner::new(global_config, storage.clone(), task_client),
),
global: GlobalEcachState::new(contract_address),
local: LocalEcashState::new(
key_pair,
identity_keypair,
double_spending_filter,
!global_config.ecash_signer.enabled,
),
aux: AuxiliaryEcashState::new(client, comm_channel, storage),
})
}
}
pub(crate) fn spawn_background_cleaner(&mut self) {
match std::mem::take(&mut self.background_cleaner_state) {
BackgroundCleanerState::WaitingStartup(cleaner) => {
self.background_cleaner_state = BackgroundCleanerState::Running {
_handle: cleaner.start(),
}
}
// whilst we normally don't want to panic, this one would only occur at startup,
// if some logical invariants got broken (which have to be fixed in code anyway)
_ => panic!("attempted to spawn background cleaner more than once"),
}
}
/// Ensures that this nym-api is one of ecash signers for the current epoch
@@ -766,12 +792,6 @@ impl EcashState {
// remove the in-memory merkle tree
map.remove(&date);
}
// remove data from the storage
self.aux
.storage
.remove_old_issued_ticketbooks(cutoff)
.await?;
}
Ok(())
@@ -807,6 +827,12 @@ impl EcashState {
return Err(EcashError::ExpirationDateTooEarly);
}
if challenge.expiration_date > ecash_default_expiration_date() {
// we wouldn't have issued any credentials for that expiration date so no point
// in attempting to construct an ultimately empty response
return Err(EcashError::ExpirationDateTooLate);
}
let merkle_proof = self
.get_merkle_proof(challenge.expiration_date, &challenge.deposits)
.await?;
@@ -863,18 +889,17 @@ impl EcashState {
})
}
/// Returns a boolean to indicate whether the ticket has actually been inserted
pub async fn store_verified_ticket(
&self,
ticket_data: &CredentialSpendingData,
gateway_addr: &AccountId,
) -> Result<()> {
) -> Result<bool> {
self.aux
.storage
.store_verified_ticket(ticket_data, gateway_addr)
.await
.map_err(Into::into)
// TODO UNIMPLEMENTED: we should probably also be removing old tickets here
}
pub async fn get_ticket_provider(
@@ -921,150 +946,4 @@ impl EcashState {
.await
.map_err(Into::into)
}
pub async fn check_bloomfilter(&self, serial_number: &Vec<u8>) -> bool {
self.local
.double_spending_filter
.read()
.await
.check(serial_number)
}
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
params: BloomfilterParameters,
sn: &Vec<u8>,
) -> Result<(), EcashError> {
let mut filter = match self
.aux
.storage
.try_load_partial_bloomfilter_bitmap(date, params_id)
.await?
{
Some(bitmap) => DoubleSpendingFilter::from_bytes(params, &bitmap),
None => {
warn!("no existing partial bloomfilter for {date}");
DoubleSpendingFilter::new_empty(params)
}
};
filter.set(sn);
let updated_bitmap = filter.dump_bitmap();
self.aux
.storage
.update_archived_partial_bloomfilter(date, &updated_bitmap)
.await?;
Ok(())
}
/// Attempt to insert the provided serial number into the bloomfilter.
/// Furthermore, attempt to rotate the filter if we have advanced into a next day.
pub async fn update_bloomfilter(
&self,
serial_number: &Vec<u8>,
spending_date: Date,
today: Date,
) -> Result<bool, EcashError> {
let mut guard = self.local.double_spending_filter.write().await;
let filter_date = guard.built_on();
let yesterday = today.previous_day().unwrap();
let params_id = guard.params_id();
let params = guard.params();
// if the filter is up-to-date, we just insert the entry and call it a day
if filter_date == today {
if spending_date == today {
return Ok(guard.insert_both(serial_number));
}
// sanity check because this should NEVER happen,
// but when it inevitably does, we don't want to crash
if spending_date != yesterday {
error!("attempted to insert a ticket with spending date of {spending_date} while it's {today} today!!");
}
// this shouldn't be happening too often, so it's fine to interact with the storage
warn!("updating archived partial bloomfilter for {spending_date}. those logs have to be closely controlled to make sure they're not too frequent");
self.update_archived_partial_bloomfilter(
spending_date,
params_id,
params,
serial_number,
)
.await?;
return Ok(guard.insert_global_only(serial_number));
}
info!("we need to advance our bloomfilter");
let previous_bitmap = guard.export_today_bitmap();
// archive the BF for today's date
self.aux
.storage
.insert_partial_bloomfilter(filter_date, params_id, &previous_bitmap)
.await?;
let new_global_filter = if filter_date == yesterday {
// normal case when we update filter daily
let two_days_ago = yesterday.previous_day().unwrap();
let mut filter_builder = prepare_partial_bloomfilter_builder(
&self.aux.storage,
params,
params_id,
two_days_ago,
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 2,
)
.await?;
// add the bitmap from 'old today', i.e. yesterday
// (we have it on hand so no point in retrieving it from storage)
filter_builder.add_bytes(&previous_bitmap);
filter_builder.build()
} else {
// initial deployment case when we don't even get tickets daily
prepare_partial_bloomfilter_builder(
&self.aux.storage,
params,
params_id,
yesterday,
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
)
.await?
.build()
};
guard.advance_day(today, new_global_filter);
// drop guard so other tasks could read the filter already whilst we clean-up the storage
let res = if spending_date == today {
Ok(guard.insert_both(serial_number))
} else {
Ok(guard.insert_global_only(serial_number))
};
drop(guard);
let cutoff = cred_exp_date().ecash_date();
// sanity check:
assert_eq!(
cutoff,
today + (constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1).days()
);
// remove the data we no longer need to hold, i.e. partial bloomfilters beyond max credential validity
// and the ticket data for those
self.aux
.storage
.remove_old_partial_bloomfilters(cutoff)
.await?;
self.aux
.storage
.remove_expired_verified_tickets(cutoff)
.await?;
res
}
}
+11 -129
View File
@@ -3,7 +3,7 @@
use crate::ecash::storage::models::{
IssuedHash, RawExpirationDateSignatures, RawIssuedTicketbook, SerialNumberWrapper,
StoredBloomfilterParams, TicketProvider, VerifiedTicket,
TicketProvider, VerifiedTicket,
};
use crate::support::storage::manager::StorageManager;
use async_trait::async_trait;
@@ -65,6 +65,7 @@ pub trait EcashStorageManagerExt {
gateway_address: &str,
) -> Result<Option<TicketProvider>, sqlx::Error>;
/// Returns a boolean to indicate whether the ticket has actually been inserted
async fn insert_verified_ticket(
&self,
provider_id: i64,
@@ -72,7 +73,7 @@ pub trait EcashStorageManagerExt {
verified_at: OffsetDateTime,
ticket_data: Vec<u8>,
serial_number: Vec<u8>,
) -> Result<(), sqlx::Error>;
) -> Result<bool, sqlx::Error>;
async fn get_ticket(&self, serial_number: &[u8])
-> Result<Option<VerifiedTicket>, sqlx::Error>;
@@ -145,41 +146,6 @@ pub trait EcashStorageManagerExt {
data: &[u8],
) -> Result<(), sqlx::Error>;
async fn insert_double_spending_filter_params(
&self,
num_hashes: u32,
bitmap_size: u32,
sip0_key0: &[u8],
sip0_key1: &[u8],
sip1_key0: &[u8],
sip1_key1: &[u8],
) -> Result<i64, sqlx::Error>;
async fn get_latest_double_spending_filter_params(
&self,
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error>;
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
new_bitmap: &[u8],
) -> Result<(), sqlx::Error>;
async fn try_load_partial_bloomfilter_bitmap(
&self,
date: Date,
params_id: i64,
) -> Result<Option<Vec<u8>>, sqlx::Error>;
async fn insert_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
bitmap: &[u8],
) -> Result<(), sqlx::Error>;
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error>;
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error>;
}
@@ -330,6 +296,8 @@ impl EcashStorageManagerExt for StorageManager {
.fetch_optional(&self.connection_pool)
.await
}
/// Returns a boolean to indicate whether the ticket has actually been inserted
async fn insert_verified_ticket(
&self,
provider_id: i64,
@@ -337,10 +305,10 @@ impl EcashStorageManagerExt for StorageManager {
verified_at: OffsetDateTime,
ticket_data: Vec<u8>,
serial_number: Vec<u8>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
) -> Result<bool, sqlx::Error> {
let affected = sqlx::query!(
r#"
INSERT INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
INSERT OR IGNORE INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
VALUES (?, ?, ?, ?, ?)
"#,
ticket_data,
@@ -350,9 +318,9 @@ impl EcashStorageManagerExt for StorageManager {
provider_id
)
.execute(&self.connection_pool)
.await?;
.await?.rows_affected();
Ok(())
Ok(affected == 1)
}
async fn get_ticket(
@@ -573,95 +541,9 @@ impl EcashStorageManagerExt for StorageManager {
Ok(())
}
async fn insert_double_spending_filter_params(
&self,
num_hashes: u32,
bitmap_size: u32,
sip0_key0: &[u8],
sip0_key1: &[u8],
sip1_key0: &[u8],
sip1_key1: &[u8],
) -> Result<i64, sqlx::Error> {
let row_id = sqlx::query!(
r#"
INSERT INTO bloomfilter_parameters(num_hashes, bitmap_size,sip0_key0, sip0_key1, sip1_key0, sip1_key1)
VALUES (?, ?, ?, ?, ?, ?)
"#,
num_hashes,
bitmap_size,
sip0_key0,
sip0_key1,
sip1_key0,
sip1_key1
).execute(&self.connection_pool).await?.last_insert_rowid();
Ok(row_id)
}
async fn get_latest_double_spending_filter_params(
&self,
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error> {
sqlx::query_as("SELECT * FROM bloomfilter_parameters ORDER BY id DESC LIMIT 1")
.fetch_optional(&self.connection_pool)
.await
}
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
new_bitmap: &[u8],
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE partial_bloomfilter SET bitmap = ? WHERE date = ?",
new_bitmap,
date
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
async fn try_load_partial_bloomfilter_bitmap(
&self,
date: Date,
params_id: i64,
) -> Result<Option<Vec<u8>>, sqlx::Error> {
sqlx::query!(
"SELECT bitmap FROM partial_bloomfilter WHERE date = ? AND parameters = ?",
date,
params_id
)
.fetch_optional(&self.connection_pool)
.await
.map(|maybe_record| maybe_record.map(|r| r.bitmap))
}
async fn insert_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
bitmap: &[u8],
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO partial_bloomfilter(date, parameters, bitmap) VALUES (?, ?, ?)",
date,
params_id,
bitmap
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM partial_bloomfilter WHERE date > ?", cutoff)
.execute(&self.connection_pool)
.await?;
Ok(())
}
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM verified_tickets WHERE spending_date > ?",
"DELETE FROM verified_tickets WHERE spending_date < ?",
cutoff
)
.execute(&self.connection_pool)
+7 -99
View File
@@ -14,7 +14,6 @@ use async_trait::async_trait;
use nym_coconut_dkg_common::types::EpochId;
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
use nym_compact_ecash::{BlindedSignature, VerificationKeyAuth};
use nym_config::defaults::BloomfilterParameters;
use nym_credentials::CredentialSpendingData;
use nym_credentials_interface::TicketType;
use nym_ecash_contract_common::deposit::DepositId;
@@ -22,7 +21,7 @@ use nym_ticketbooks_merkle::{IssuedTicketbook, MerkleLeaf};
use nym_validator_client::nyxd::AccountId;
use std::collections::HashSet;
use time::{Date, OffsetDateTime};
use tracing::{info, warn};
use tracing::warn;
mod helpers;
pub(crate) mod manager;
@@ -30,32 +29,6 @@ pub(crate) mod models;
#[async_trait]
pub trait EcashStorageExt {
async fn get_double_spending_filter_params(
&self,
) -> Result<(i64, BloomfilterParameters), NymApiStorageError>;
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
new_bitmap: &[u8],
) -> Result<(), NymApiStorageError>;
async fn try_load_partial_bloomfilter_bitmap(
&self,
date: Date,
params_id: i64,
) -> Result<Option<Vec<u8>>, NymApiStorageError>;
async fn insert_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
bitmap: &[u8],
) -> Result<(), NymApiStorageError>;
async fn remove_old_partial_bloomfilters(&self, cutoff: Date)
-> Result<(), NymApiStorageError>;
async fn remove_expired_verified_tickets(&self, cutoff: Date)
-> Result<(), NymApiStorageError>;
@@ -96,11 +69,12 @@ pub trait EcashStorageExt {
serial_number: &[u8],
) -> Result<Option<CredentialSpendingData>, NymApiStorageError>;
/// Returns a boolean to indicate whether the ticket has actually been inserted
async fn store_verified_ticket(
&self,
ticket_data: &CredentialSpendingData,
gateway_addr: &AccountId,
) -> Result<(), NymApiStorageError>;
) -> Result<bool, NymApiStorageError>;
async fn get_ticket_provider(
&self,
@@ -118,6 +92,7 @@ pub trait EcashStorageExt {
last_batch_verification: OffsetDateTime,
) -> Result<(), NymApiStorageError>;
#[allow(dead_code)]
async fn get_all_spent_tickets_on(
&self,
date: Date,
@@ -181,75 +156,6 @@ pub trait EcashStorageExt {
#[async_trait]
impl EcashStorageExt for NymApiStorage {
async fn get_double_spending_filter_params(
&self,
) -> Result<(i64, BloomfilterParameters), NymApiStorageError> {
match self
.manager
.get_latest_double_spending_filter_params()
.await?
{
Some(raw) => Ok((raw.id, (&raw).try_into()?)),
None => {
let default = BloomfilterParameters::default_ecash();
info!("using default bloomfilter parameters: {default:?}");
let id = self
.manager
.insert_double_spending_filter_params(
default.num_hashes,
default.bitmap_size as u32,
&default.sip_keys[0].0.to_be_bytes(),
&default.sip_keys[0].1.to_be_bytes(),
&default.sip_keys[1].0.to_be_bytes(),
&default.sip_keys[1].1.to_be_bytes(),
)
.await?;
Ok((id, default))
}
}
}
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
new_bitmap: &[u8],
) -> Result<(), NymApiStorageError> {
Ok(self
.manager
.update_archived_partial_bloomfilter(date, new_bitmap)
.await?)
}
async fn try_load_partial_bloomfilter_bitmap(
&self,
date: Date,
params_id: i64,
) -> Result<Option<Vec<u8>>, NymApiStorageError> {
Ok(self
.manager
.try_load_partial_bloomfilter_bitmap(date, params_id)
.await?)
}
async fn insert_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
bitmap: &[u8],
) -> Result<(), NymApiStorageError> {
Ok(self
.manager
.insert_partial_bloomfilter(date, params_id, bitmap)
.await?)
}
async fn remove_old_partial_bloomfilters(
&self,
cutoff: Date,
) -> Result<(), NymApiStorageError> {
Ok(self.manager.remove_old_partial_bloomfilters(cutoff).await?)
}
async fn remove_expired_verified_tickets(
&self,
cutoff: Date,
@@ -354,11 +260,12 @@ impl EcashStorageExt for NymApiStorage {
.transpose()
}
/// Returns a boolean to indicate whether the ticket has actually been inserted
async fn store_verified_ticket(
&self,
ticket_data: &CredentialSpendingData,
gateway_addr: &AccountId,
) -> Result<(), NymApiStorageError> {
) -> Result<bool, NymApiStorageError> {
let provider_id = self
.get_or_create_ticket_provider_with_id(gateway_addr.as_ref())
.await?;
@@ -411,6 +318,7 @@ impl EcashStorageExt for NymApiStorage {
.await?)
}
#[allow(dead_code)]
async fn get_all_spent_tickets_on(
&self,
date: Date,
-48
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::NymApiStorageError;
use nym_config::defaults::BloomfilterParameters;
use nym_credentials_interface::TicketType;
use nym_ecash_contract_common::deposit::DepositId;
use nym_ticketbooks_merkle::IssuedTicketbook;
@@ -89,50 +88,3 @@ pub struct RawExpirationDateSignatures {
pub epoch_id: u32,
pub serialised_signatures: Vec<u8>,
}
#[derive(FromRow)]
pub(crate) struct StoredBloomfilterParams {
pub(crate) id: i64,
pub(crate) num_hashes: u32,
pub(crate) bitmap_size: u32,
pub(crate) sip0_key0: Vec<u8>,
pub(crate) sip0_key1: Vec<u8>,
pub(crate) sip1_key0: Vec<u8>,
pub(crate) sip1_key1: Vec<u8>,
}
impl<'a> TryFrom<&'a StoredBloomfilterParams> for BloomfilterParameters {
type Error = NymApiStorageError;
fn try_from(value: &'a StoredBloomfilterParams) -> Result<Self, Self::Error> {
let Ok(sip0_key0) = <[u8; 8]>::try_from(value.sip0_key0.as_ref()) else {
return Err(NymApiStorageError::database_inconsistency(
"malformed sip0 key0",
));
};
let Ok(sip0_key1) = <[u8; 8]>::try_from(value.sip0_key1.as_ref()) else {
return Err(NymApiStorageError::database_inconsistency(
"malformed sip0 key1",
));
};
let Ok(sip1_key0) = <[u8; 8]>::try_from(value.sip1_key0.as_ref()) else {
return Err(NymApiStorageError::database_inconsistency(
"malformed sip1 key0",
));
};
let Ok(sip1_key1) = <[u8; 8]>::try_from(value.sip1_key1.as_ref()) else {
return Err(NymApiStorageError::database_inconsistency(
"malformed sip1 key1",
));
};
Ok(BloomfilterParameters {
num_hashes: value.num_hashes,
bitmap_size: value.bitmap_size as u64,
sip_keys: [
(u64::from_be_bytes(sip0_key0), u64::from_be_bytes(sip0_key1)),
(u64::from_be_bytes(sip1_key0), u64::from_be_bytes(sip1_key1)),
],
})
}
}
+6 -6
View File
@@ -54,6 +54,7 @@ use nym_crypto::asymmetric::identity;
use nym_dkg::{NodeIndex, Threshold};
use nym_ecash_contract_common::blacklist::{BlacklistedAccountResponse, Blacklisting};
use nym_ecash_contract_common::deposit::{Deposit, DepositId, DepositResponse};
use nym_task::TaskClient;
use nym_validator_client::nym_api::routes::{
API_VERSION, ECASH_BLIND_SIGN, ECASH_ISSUED_TICKETBOOKS_CHALLENGE,
ECASH_ISSUED_TICKETBOOKS_FOR, ECASH_ROUTES,
@@ -1333,9 +1334,8 @@ impl TestFixture {
staged_key_pair,
comm_channel,
storage.clone(),
)
.await
.unwrap();
TaskClient::dummy(),
);
TestFixture {
axum: TestServer::new(
@@ -1459,6 +1459,7 @@ mod credential_tests {
use super::*;
use crate::ecash::storage::EcashStorageExt;
use axum::http::StatusCode;
use nym_task::TaskClient;
use nym_ticketbooks_merkle::MerkleLeaf;
#[tokio::test]
@@ -1547,9 +1548,8 @@ mod credential_tests {
staged_key_pair,
comm_channel,
storage.clone(),
)
.await
.unwrap();
TaskClient::dummy(),
);
let deposit_id = 42;
assert!(state.already_issued(deposit_id).await.unwrap().is_none());
+7
View File
@@ -329,6 +329,13 @@ pub(crate) struct AxumErrorResponse {
}
impl AxumErrorResponse {
pub(crate) fn new<S: Into<String>>(message: S, status: StatusCode) -> Self {
AxumErrorResponse {
message: RequestError::new(message),
status,
}
}
pub(crate) fn internal_msg(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
+6 -5
View File
@@ -106,6 +106,8 @@ pub(crate) struct Args {
}
async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHandles> {
let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S);
let nyxd_client = nyxd::Client::new(config);
let connected_nyxd = config.get_nyxd_url();
let nym_network_details = NymNetworkDetails::new_from_env();
@@ -148,7 +150,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
let comm_channel = QueryCommunicationChannel::new(nyxd_client.clone());
let encoded_identity = identity_keypair.public_key().to_base58_string();
let ecash_state = EcashState::new(
let mut ecash_state = EcashState::new(
config,
ecash_contract,
nyxd_client.clone(),
@@ -156,8 +158,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
ecash_keypair_wrapper.clone(),
comm_channel,
storage.clone(),
)
.await?;
task_manager.subscribe_named("ecash-state-data-cleaner"),
);
// if ecash signer is enabled, there are additional constraints on the nym-api,
// such as having sufficient token balance
@@ -187,6 +189,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
None
};
ecash_state.spawn_background_cleaner();
let router = router.with_state(AppState {
forced_refresh: ForcedRefresh::new(
config.topology_cacher.debug.node_describe_allow_illegal_ips,
@@ -202,8 +205,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
ecash_state: Arc::new(ecash_state),
});
let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S);
// start note describe cache refresher
// we should be doing the below, but can't due to our current startup structure
// let refresher = node_describe_cache::new_refresher(&config.topology_cacher);
+28 -7
View File
@@ -7,6 +7,7 @@ use crate::support::config::persistence::{
use crate::support::config::r#override::OverrideConfig;
use crate::support::config::template::CONFIG_TEMPLATE;
use anyhow::bail;
use nym_compact_ecash::constants;
use nym_config::defaults::mainnet::read_parsed_var_if_not_default;
use nym_config::defaults::var_names::{CONFIGURED, NYXD};
use nym_config::serde_helpers::de_maybe_stringified;
@@ -32,8 +33,6 @@ mod upgrade_helpers;
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(30);
const DEFAULT_GATEWAY_SENDING_RATE: usize = 200;
const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50;
const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20);
@@ -57,9 +56,6 @@ const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(
pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from_secs(4500);
pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50;
// keep them for 2 extra days beyond the specified expiration date
pub(crate) const DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS: u32 = 2;
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
@@ -560,15 +556,40 @@ pub struct EcashSignerDebug {
#[serde(with = "humantime_serde")]
pub dkg_contract_polling_rate: Duration,
/// Specifies interval at which the stale ecash data is removed from the storage.
#[serde(with = "humantime_serde")]
pub stale_data_cleaner_interval: Duration,
/// Specifies how long should the issued ticketbooks be kept (beyond the specified expiration date)
pub issued_ticketbooks_retention_period_days: u32,
/// Specifies how long should the full ticket data of verified gateway tickets be kept (beyond the spending date)
pub verified_tickets_retention_period_days: u32,
}
impl EcashSignerDebug {
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(30);
// it still operates at "day" cutoffs
pub const DEFAULT_STALE_DATA_CLEANER_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60);
// keep them for 2 extra days beyond the specified expiration date
pub(crate) const DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS: u32 = 2;
// keep the tickets for maximum theoretical validity (+1 day)
pub(crate) const DEFAULT_VERIFIED_TICKETS_RETENTION_PERIOD_DAYS: u32 =
constants::CRED_VALIDITY_PERIOD_DAYS + 1;
}
impl Default for EcashSignerDebug {
fn default() -> Self {
EcashSignerDebug {
dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE,
issued_ticketbooks_retention_period_days: DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS,
dkg_contract_polling_rate: Self::DEFAULT_DKG_CONTRACT_POLLING_RATE,
stale_data_cleaner_interval: Self::DEFAULT_STALE_DATA_CLEANER_INTERVAL,
issued_ticketbooks_retention_period_days:
Self::DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS,
verified_tickets_retention_period_days:
Self::DEFAULT_VERIFIED_TICKETS_RETENTION_PERIOD_DAYS,
}
}
}