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:
Jędrzej Stuczyński
2025-08-12 09:03:53 +01:00
committed by GitHub
parent 7daa726626
commit 906dfb2fb0
31 changed files with 307 additions and 64 deletions
Generated
-4
View File
@@ -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",
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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!"))?;
@@ -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)
}
-1
View File
@@ -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);
+1
View File
@@ -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(
+1 -1
View File
@@ -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) => {
+2 -2
View File
@@ -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;
+9 -1
View File
@@ -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 {
+2 -1
View File
@@ -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 {
+2 -1
View File
@@ -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>>>,
+11 -6
View File
@@ -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);
+10 -4
View File
@@ -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
+6 -2
View File
@@ -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" }
@@ -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,
}