Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions common/bandwidth-controller/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?
{
Expand All @@ -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}"),
Expand Down
6 changes: 4 additions & 2 deletions common/client-libs/validator-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
}

Expand All @@ -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?)
}

Expand Down
14 changes: 12 additions & 2 deletions common/client-libs/validator-client/src/nym_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,15 +1103,20 @@ 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,
exp.format(&rfc_3339_date()).unwrap(),
)],
};

if let Some(epoch_id) = epoch_id {
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
}

self.get_json(
&[
routes::V1_API_VERSION,
Expand Down Expand Up @@ -1148,15 +1153,20 @@ 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,
exp.format(&rfc_3339_date()).unwrap(),
)],
};

if let Some(epoch_id) = epoch_id {
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
}

self.get_json(
&[
routes::V1_API_VERSION,
Expand Down
2 changes: 1 addition & 1 deletion common/commands/src/ecash/generate_ticket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
2 changes: 1 addition & 1 deletion common/commands/src/ecash/issue_ticket_book.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!"))?;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2025 - Nym Technologies SA <[email protected]>
* 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;
15 changes: 10 additions & 5 deletions common/credential-storage/src/backends/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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(
Expand All @@ -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(),
);
}
}
8 changes: 5 additions & 3 deletions common/credential-storage/src/backends/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion common/credential-storage/src/ephemeral_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
1 change: 0 additions & 1 deletion common/credential-storage/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
3 changes: 2 additions & 1 deletion common/credential-storage/src/persistent_storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions common/credential-storage/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion common/credentials/src/ecash/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
4 changes: 2 additions & 2 deletions common/nym-id/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
3 changes: 2 additions & 1 deletion common/nym-id/src/import_credential/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -108,6 +108,7 @@ where
{
return Err(NymIdError::MissingExpirationDateSignatures {
date: ticketbook.expiration_date(),
epoch_id: ticketbook.epoch_id(),
});
}

Expand Down
Loading
Loading