change PK/FK on expiration date signatures tables (#5934)
* update nym-credential-proxy * update credential-storage * update nym-api * clippy
This commit is contained in:
committed by
GitHub
parent
7daa726626
commit
906dfb2fb0
Generated
-4
@@ -5563,14 +5563,11 @@ name = "nym-credential-proxy"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
"colored",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
"humantime",
|
||||
"nym-bin-common 0.6.0",
|
||||
@@ -5596,7 +5593,6 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.5.2",
|
||||
"tracing",
|
||||
"url",
|
||||
|
||||
@@ -207,7 +207,7 @@ where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(stored) = storage
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
{
|
||||
@@ -220,7 +220,7 @@ where
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
api.api_client
|
||||
.global_expiration_date_signatures(Some(expiration_date))
|
||||
.global_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await
|
||||
},
|
||||
format!("aggregated coin index signatures for date {expiration_date}"),
|
||||
|
||||
@@ -719,10 +719,11 @@ impl NymApiClient {
|
||||
pub async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -739,10 +740,11 @@ impl NymApiClient {
|
||||
pub async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.global_expiration_date_signatures(expiration_date)
|
||||
.global_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
@@ -1103,8 +1103,9 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
let mut params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
@@ -1112,6 +1113,10 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)],
|
||||
};
|
||||
|
||||
if let Some(epoch_id) = epoch_id {
|
||||
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
@@ -1148,8 +1153,9 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
let mut params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
@@ -1157,6 +1163,10 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)],
|
||||
};
|
||||
|
||||
if let Some(epoch_id) = epoch_id {
|
||||
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
|
||||
@@ -86,7 +86,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
anyhow!("ticketbook got incorrectly imported - the master verification key is missing")
|
||||
})?;
|
||||
let expiration_signatures = persistent_storage
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
|
||||
@@ -120,7 +120,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()>
|
||||
|
||||
if args.include_expiration_date_signatures {
|
||||
let signatures = credentials_store
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.ok_or(anyhow!("missing expiration date signatures!"))?;
|
||||
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
-- 1. add temporary `epoch_id` column
|
||||
ALTER TABLE pending_issuance
|
||||
ADD COLUMN epoch_id INTEGER;
|
||||
|
||||
-- 2. populate the value
|
||||
UPDATE pending_issuance
|
||||
SET epoch_id = (SELECT epoch_id
|
||||
FROM expiration_date_signatures
|
||||
WHERE expiration_date_signatures.expiration_date = pending_issuance.expiration_date);
|
||||
|
||||
-- 3. create new expiration_date_signatures table (with changed constraints)
|
||||
CREATE TABLE expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- combined signatures for all tuples issued for given day
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
-- 4. migrate the data
|
||||
INSERT INTO expiration_date_signatures_new (expiration_date, epoch_id, serialization_revision, serialised_signatures)
|
||||
SELECT expiration_date, epoch_id, serialization_revision, serialised_signatures
|
||||
FROM expiration_date_signatures;
|
||||
|
||||
-- 5. drop and recreate the table references (due to new FK)
|
||||
|
||||
-- 5.1.
|
||||
-- (data for ticketbooks that have an associated deposit, but failed to get issued)
|
||||
CREATE TABLE pending_issuance_new
|
||||
(
|
||||
deposit_id INTEGER NOT NULL PRIMARY KEY,
|
||||
|
||||
-- introduce a way for us to introduce breaking changes in serialization of data
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
pending_ticketbook_data BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
expiration_date DATE NOT NULL,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
FOREIGN KEY (epoch_id, expiration_date) REFERENCES expiration_date_signatures_new (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
INSERT INTO pending_issuance_new (deposit_id, serialization_revision, pending_ticketbook_data, expiration_date,
|
||||
epoch_id)
|
||||
SELECT deposit_id, serialization_revision, pending_ticketbook_data, expiration_date, epoch_id
|
||||
FROM pending_issuance;
|
||||
|
||||
|
||||
-- 5.2.
|
||||
CREATE TABLE ecash_ticketbook_new
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- introduce a way for us to introduce breaking changes in serialization of data
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- the type of the associated ticketbook
|
||||
ticketbook_type TEXT NOT NULL,
|
||||
|
||||
-- the actual crypto data of the ticketbook (wallet, keys, etc.)
|
||||
ticketbook_data BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding coin index signatures
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- the initial number of tickets the wallet has been created for
|
||||
total_tickets INTEGER NOT NULL,
|
||||
|
||||
-- how many tickets have been used so far (the `l` value of the wallet)
|
||||
used_tickets INTEGER NOT NULL,
|
||||
|
||||
|
||||
-- FOREIGN KEYS:
|
||||
|
||||
-- for each ticketbook we MUST have corresponding coin index signatures
|
||||
FOREIGN KEY (epoch_id) REFERENCES coin_indices_signatures (epoch_id),
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
FOREIGN KEY (expiration_date, epoch_id) REFERENCES expiration_date_signatures_new (expiration_date, epoch_id)
|
||||
);
|
||||
|
||||
INSERT INTO ecash_ticketbook_new (id, serialization_revision, ticketbook_type, ticketbook_data, expiration_date,
|
||||
epoch_id, total_tickets, used_tickets)
|
||||
SELECT id,
|
||||
serialization_revision,
|
||||
ticketbook_type,
|
||||
ticketbook_data,
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
total_tickets,
|
||||
used_tickets
|
||||
FROM ecash_ticketbook;
|
||||
|
||||
-- 6. finally swap out the old tables
|
||||
-- drop old tables
|
||||
DROP TABLE expiration_date_signatures;
|
||||
DROP TABLE pending_issuance;
|
||||
DROP TABLE ecash_ticketbook;
|
||||
|
||||
-- rename new tables
|
||||
ALTER TABLE expiration_date_signatures_new
|
||||
RENAME TO expiration_date_signatures;
|
||||
ALTER TABLE pending_issuance_new
|
||||
RENAME TO pending_issuance;
|
||||
ALTER TABLE ecash_ticketbook_new
|
||||
RENAME TO ecash_ticketbook;
|
||||
@@ -28,7 +28,7 @@ struct EcashCredentialManagerInner {
|
||||
pending: HashMap<i64, RetrievedPendingTicketbook>,
|
||||
master_vk: HashMap<u64, VerificationKeyAuth>,
|
||||
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
|
||||
expiration_date_sigs: HashMap<Date, Vec<AnnotatedExpirationDateSignature>>,
|
||||
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
|
||||
_next_id: i64,
|
||||
}
|
||||
|
||||
@@ -242,10 +242,14 @@ impl MemoryEcachTicketbookManager {
|
||||
pub(crate) async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Option<Vec<AnnotatedExpirationDateSignature>> {
|
||||
let guard = self.inner.read().await;
|
||||
|
||||
guard.expiration_date_sigs.get(&expiration_date).cloned()
|
||||
guard
|
||||
.expiration_date_sigs
|
||||
.get(&(epoch_id, expiration_date))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_expiration_date_signatures(
|
||||
@@ -254,8 +258,9 @@ impl MemoryEcachTicketbookManager {
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard
|
||||
.expiration_date_sigs
|
||||
.insert(sigs.expiration_date, sigs.signatures.clone());
|
||||
guard.expiration_date_sigs.insert(
|
||||
(sigs.epoch_id, sigs.expiration_date),
|
||||
sigs.signatures.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,15 +260,17 @@ impl SqliteEcashTicketbookManager {
|
||||
pub(crate) async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
SELECT serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
FROM expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
|
||||
@@ -166,10 +166,11 @@ impl Storage for EphemeralStorage {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError> {
|
||||
Ok(self
|
||||
.storage_manager
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ pub struct StoredPendingTicketbook {
|
||||
|
||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||
pub struct RawExpirationDateSignatures {
|
||||
pub epoch_id: u32,
|
||||
pub serialised_signatures: Vec<u8>,
|
||||
pub serialization_revision: u8,
|
||||
}
|
||||
|
||||
@@ -325,10 +325,11 @@ impl Storage for PersistentStorage {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError> {
|
||||
let Some(raw) = self
|
||||
.storage_manager
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -92,6 +92,7 @@ pub trait Storage: Clone + Send + Sync {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError>;
|
||||
|
||||
async fn insert_expiration_date_signatures(
|
||||
|
||||
@@ -51,7 +51,7 @@ pub async fn obtain_expiration_date_signatures(
|
||||
for ecash_api_client in ecash_api_clients.iter() {
|
||||
match ecash_api_client
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(None)
|
||||
.partial_expiration_date_signatures(None, None)
|
||||
.await
|
||||
{
|
||||
Ok(signature) => {
|
||||
|
||||
@@ -25,8 +25,8 @@ pub enum NymIdError {
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: Date },
|
||||
|
||||
#[error("could not import ticketbook expiring at {date} since we do not have corresponding expiration date signatures")]
|
||||
MissingExpirationDateSignatures { date: Date },
|
||||
#[error("could not import ticketbook expiring at {date} for epoch {epoch_id} since we do not have corresponding expiration date signatures")]
|
||||
MissingExpirationDateSignatures { date: Date, epoch_id: u64 },
|
||||
|
||||
#[error("could not import ticketbook for epoch {epoch_id} since we do not have corresponding coin index signatures")]
|
||||
MissingCoinIndexSignatures { epoch_id: u64 },
|
||||
|
||||
@@ -99,7 +99,7 @@ where
|
||||
|
||||
// in order to import the ticketbook we MUST have the appropriate signatures in the storage already
|
||||
if credentials_store
|
||||
.get_expiration_date_signatures(ticketbook.expiration_date())
|
||||
.get_expiration_date_signatures(ticketbook.expiration_date(), ticketbook.epoch_id())
|
||||
.await
|
||||
.map_err(|source| NymIdError::StorageError {
|
||||
source: Box::new(source),
|
||||
@@ -108,6 +108,7 @@ where
|
||||
{
|
||||
return Err(NymIdError::MissingExpirationDateSignatures {
|
||||
date: ticketbook.expiration_date(),
|
||||
epoch_id: ticketbook.epoch_id(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
-- Change performed in this migration:
|
||||
-- remove PK on expiration_date and instead use composite (epoch_id, expiration_date) PK
|
||||
|
||||
|
||||
CREATE TABLE global_expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- combined signatures for all tuples issued for given day
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
CREATE TABLE partial_expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
-- global
|
||||
INSERT INTO global_expiration_date_signatures_new
|
||||
SELECT *
|
||||
FROM global_expiration_date_signatures;
|
||||
|
||||
DROP TABLE global_expiration_date_signatures;
|
||||
|
||||
ALTER TABLE global_expiration_date_signatures_new
|
||||
RENAME TO global_expiration_date_signatures;
|
||||
|
||||
-- partial
|
||||
INSERT INTO partial_expiration_date_signatures_new
|
||||
SELECT *
|
||||
FROM partial_expiration_date_signatures;
|
||||
|
||||
DROP TABLE partial_expiration_date_signatures;
|
||||
|
||||
ALTER TABLE partial_expiration_date_signatures_new
|
||||
RENAME TO partial_expiration_date_signatures;
|
||||
@@ -12,6 +12,7 @@ use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_time::{cred_exp_date, EcashTime};
|
||||
use nym_http_api_common::{FormattedResponse, Output};
|
||||
use nym_validator_client::nym_api::rfc_3339_date;
|
||||
@@ -71,6 +72,7 @@ async fn master_verification_key(
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
epoch_id: Option<EpochId>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ async fn expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<AggregatedExpirationDateSignatureResponse>> {
|
||||
@@ -108,8 +111,13 @@ async fn expiration_date_signatures(
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().await?;
|
||||
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => state.current_dkg_epoch().await?,
|
||||
};
|
||||
|
||||
let expiration_date_signatures = state
|
||||
.master_expiration_date_signatures(expiration_date)
|
||||
.master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?;
|
||||
|
||||
Ok(
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
PartialExpirationDateSignatureResponse,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_time::{cred_exp_date, EcashTime};
|
||||
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
|
||||
use nym_validator_client::nym_api::rfc_3339_date;
|
||||
@@ -114,6 +115,7 @@ async fn post_blind_sign(
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
epoch_id: Option<EpochId>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
@@ -137,6 +139,7 @@ async fn partial_expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<PartialExpirationDateSignatureResponse>> {
|
||||
@@ -152,8 +155,13 @@ async fn partial_expiration_date_signatures(
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().await?;
|
||||
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => state.current_dkg_epoch().await?,
|
||||
};
|
||||
|
||||
let expiration_date_signatures = state
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?;
|
||||
|
||||
Ok(output.to_response(PartialExpirationDateSignatureResponse {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::ecash::helpers::{
|
||||
CachedImmutableEpochItem, CachedImmutableItems, IssuedCoinIndicesSignatures,
|
||||
IssuedExpirationDateSignatures,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use time::Date;
|
||||
@@ -18,7 +19,7 @@ pub(crate) struct GlobalEcachState {
|
||||
pub(crate) coin_index_signatures: CachedImmutableEpochItem<IssuedCoinIndicesSignatures>,
|
||||
|
||||
pub(crate) expiration_date_signatures:
|
||||
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
|
||||
CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>,
|
||||
}
|
||||
|
||||
impl GlobalEcachState {
|
||||
|
||||
@@ -9,6 +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_coconut_dkg_common::types::EpochId;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ticketbooks_merkle::{
|
||||
IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf,
|
||||
@@ -143,7 +144,7 @@ pub(crate) struct LocalEcashState {
|
||||
|
||||
pub(crate) partial_coin_index_signatures: CachedImmutableEpochItem<IssuedCoinIndicesSignatures>,
|
||||
pub(crate) partial_expiration_date_signatures:
|
||||
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
|
||||
CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>,
|
||||
|
||||
// merkle trees for ticketbooks issued for particular expiration dates
|
||||
pub(crate) issued_merkle_trees: Arc<RwLock<HashMap<Date, DailyMerkleTree>>>,
|
||||
|
||||
@@ -401,10 +401,11 @@ impl EcashState {
|
||||
pub(crate) async fn master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, IssuedExpirationDateSignatures>> {
|
||||
self.global
|
||||
.expiration_date_signatures
|
||||
.get_or_init(expiration_date, || async {
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
@@ -412,7 +413,7 @@ impl EcashState {
|
||||
if let Some(master_sigs) = self
|
||||
.aux
|
||||
.storage
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
@@ -435,13 +436,16 @@ impl EcashState {
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
self.partial_expiration_date_signatures(expiration_date)
|
||||
self.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.signatures
|
||||
.clone()
|
||||
} else {
|
||||
api.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date))
|
||||
.partial_expiration_date_signatures(
|
||||
Some(expiration_date),
|
||||
Some(epoch_id),
|
||||
)
|
||||
.await?
|
||||
.signatures
|
||||
};
|
||||
@@ -480,10 +484,11 @@ impl EcashState {
|
||||
pub(crate) async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, IssuedExpirationDateSignatures>> {
|
||||
self.local
|
||||
.partial_expiration_date_signatures
|
||||
.get_or_init(expiration_date, || async {
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
@@ -491,7 +496,7 @@ impl EcashState {
|
||||
if let Some(partial_sigs) = self
|
||||
.aux
|
||||
.storage
|
||||
.get_partial_expiration_date_signatures(expiration_date)
|
||||
.get_partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(partial_sigs);
|
||||
|
||||
@@ -128,6 +128,7 @@ pub trait EcashStorageManagerExt {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error>;
|
||||
async fn insert_partial_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -139,6 +140,7 @@ pub trait EcashStorageManagerExt {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error>;
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -501,15 +503,17 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures
|
||||
FROM partial_expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
@@ -535,15 +539,17 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures
|
||||
FROM global_expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
|
||||
@@ -143,6 +143,7 @@ pub trait EcashStorageExt {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError>;
|
||||
|
||||
async fn insert_partial_expiration_date_signatures(
|
||||
@@ -154,6 +155,7 @@ pub trait EcashStorageExt {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError>;
|
||||
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
@@ -456,10 +458,11 @@ impl EcashStorageExt for NymApiStorage {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError> {
|
||||
let Some(raw) = self
|
||||
.manager
|
||||
.get_partial_expiration_date_signatures(expiration_date)
|
||||
.get_partial_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
@@ -491,10 +494,11 @@ impl EcashStorageExt for NymApiStorage {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError> {
|
||||
let Some(raw) = self
|
||||
.manager
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -11,15 +11,12 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
axum.workspace = true
|
||||
anyhow.workspace = true
|
||||
bip39 = { workspace = true, features = ["zeroize"] }
|
||||
bs58.workspace = true
|
||||
cfg-if = { workspace = true }
|
||||
colored.workspace = true
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
dotenvy.workspace = true
|
||||
futures.workspace = true
|
||||
humantime.workspace = true
|
||||
rand.workspace = true
|
||||
@@ -33,7 +30,6 @@ time.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] }
|
||||
tokio-util = { workspace = true, features = ["rt"] }
|
||||
tower.workspace = true
|
||||
tower-http = { workspace = true, features = ["cors"], optional = true }
|
||||
tracing.workspace = true
|
||||
url.workspace = true
|
||||
@@ -49,7 +45,7 @@ nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa", "middleware"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
DROP TABLE global_expiration_date_signatures;
|
||||
|
||||
CREATE TABLE global_expiration_date_signatures
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- combined signatures for all tuples issued for given day
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
)
|
||||
@@ -83,7 +83,7 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
let _ = state.master_verification_key(Some(epoch)).await?;
|
||||
let _ = state.master_coin_index_signatures(Some(epoch)).await?;
|
||||
let _ = state
|
||||
.master_expiration_date_signatures(expiration_date)
|
||||
.master_expiration_date_signatures(epoch, expiration_date)
|
||||
.await?;
|
||||
let ecash_api_clients = state.ecash_clients(epoch).await?.clone();
|
||||
|
||||
|
||||
@@ -97,7 +97,9 @@ impl ApiState {
|
||||
let _ = self.ecash_threshold(epoch_id).await?;
|
||||
let _ = self.ecash_clients(epoch_id).await?;
|
||||
let _ = self.master_coin_index_signatures(Some(epoch_id)).await?;
|
||||
let _ = self.master_expiration_date_signatures(today).await?;
|
||||
let _ = self
|
||||
.master_expiration_date_signatures(epoch_id, today)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -253,7 +255,7 @@ impl ApiState {
|
||||
let aggregated_expiration_date_signatures = if include_expiration_date_signatures {
|
||||
debug!("including expiration date signatures in the response");
|
||||
Some(
|
||||
self.master_expiration_date_signatures(expiration_date)
|
||||
self.master_expiration_date_signatures(epoch_id, expiration_date)
|
||||
.await
|
||||
.map(|signatures| AggregatedExpirationDateSignaturesResponse {
|
||||
signatures: signatures.clone(),
|
||||
@@ -515,12 +517,13 @@ impl ApiState {
|
||||
|
||||
pub(crate) async fn master_expiration_date_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, VpnApiError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.expiration_date_signatures
|
||||
.get_or_init(expiration_date, || async {
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
@@ -528,7 +531,7 @@ impl ApiState {
|
||||
if let Some(master_sigs) = self
|
||||
.inner
|
||||
.storage
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
@@ -536,7 +539,7 @@ impl ApiState {
|
||||
|
||||
|
||||
info!(
|
||||
"attempting to establish master expiration date signatures for {expiration_date}..."
|
||||
"attempting to establish master expiration date signatures for {expiration_date} and epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 3. go around APIs and attempt to aggregate the data
|
||||
@@ -553,7 +556,7 @@ impl ApiState {
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date))
|
||||
.partial_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(ExpirationDateSignatureShare {
|
||||
@@ -697,7 +700,7 @@ pub(crate) struct EcashState {
|
||||
pub(crate) coin_index_signatures: CachedImmutableEpochItem<AggregatedCoinIndicesSignatures>,
|
||||
|
||||
pub(crate) expiration_date_signatures:
|
||||
CachedImmutableItems<Date, AggregatedExpirationDateSignatures>,
|
||||
CachedImmutableItems<(EpochId, Date), AggregatedExpirationDateSignatures>,
|
||||
}
|
||||
|
||||
pub(crate) type ChainReadPermit<'a> = RwLockReadGuard<'a, DirectSigningHttpRpcNyxdClient>;
|
||||
|
||||
@@ -332,18 +332,20 @@ impl SqliteStorageManager {
|
||||
pub(crate) async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
SELECT serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
FROM global_expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_master_expiration_date_signatures(
|
||||
|
||||
@@ -331,10 +331,11 @@ impl VpnApiStorage {
|
||||
pub(crate) async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedExpirationDateSignatures>, VpnApiError> {
|
||||
let Some(raw) = self
|
||||
.storage_manager
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -36,8 +36,6 @@ pub struct FullBlindedShares {
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct RawExpirationDateSignatures {
|
||||
#[allow(dead_code)]
|
||||
pub epoch_id: u32,
|
||||
pub serialised_signatures: Vec<u8>,
|
||||
pub serialization_revision: u8,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user