Fix invalid ticket spend (#6683)
* Fix * PR feedback * Bump version * Update sqlx files
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.6.0"
|
||||
version = "4.6.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -68,8 +68,8 @@ impl TicketbookManagerState {
|
||||
for typ in &self.buffered_ticket_types {
|
||||
debug!("attempting to get materials for ticket of type {typ}");
|
||||
if let Some(ticket) = self.storage.next_ticket(*typ, testrun_id).await? {
|
||||
let epoch_id = ticket.ticketbook.epoch_id();
|
||||
let expiration_date = ticket.ticketbook.expiration_date();
|
||||
let epoch_id = ticket.epoch_id();
|
||||
let expiration_date = ticket.expiration_date();
|
||||
|
||||
debug!(
|
||||
"retrieved ticket corresponds to epoch {epoch_id} and expiration date {expiration_date}"
|
||||
|
||||
+31
-6
@@ -1,25 +1,50 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use anyhow::bail;
|
||||
use nym_credentials::IssuedTicketBook;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise;
|
||||
use nym_gateway_probe::types::AttachedTicket;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use sqlx::FromRow;
|
||||
use time::Date;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub struct RetrievedTicketbook {
|
||||
pub ticketbook_id: i32,
|
||||
pub total_tickets: u32,
|
||||
pub spent_tickets: u32,
|
||||
pub ticketbook: IssuedTicketBook,
|
||||
pub(crate) struct RetrievedTicketbook {
|
||||
usable_index: u32,
|
||||
ticketbook: IssuedTicketBook,
|
||||
}
|
||||
|
||||
impl RetrievedTicketbook {
|
||||
pub fn new(ticketbook: IssuedTicketBook) -> anyhow::Result<Self> {
|
||||
let usable_index = ticketbook.spent_tickets() as u32 - 1;
|
||||
// spent_tickets is the post-increment number from the DB: the ticket we're
|
||||
// handing out has already been counted as "used" in the DB, but has NOT YET
|
||||
// been spent yet by the recipient. To get its 0-based index in the ticketbook,
|
||||
// subtract 1 (e.g. spent_tickets=1, the ticket at index 0).
|
||||
if usable_index < 1 {
|
||||
bail!("Malformed ticket: cannot convert from ticket with spent_tickets=0");
|
||||
}
|
||||
Ok(Self {
|
||||
usable_index,
|
||||
ticketbook,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> EpochId {
|
||||
self.ticketbook.epoch_id()
|
||||
}
|
||||
|
||||
pub fn expiration_date(&self) -> time::Date {
|
||||
self.ticketbook.expiration_date()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RetrievedTicketbook> for AttachedTicket {
|
||||
fn from(retrieved: RetrievedTicketbook) -> Self {
|
||||
AttachedTicket {
|
||||
ticketbook: retrieved.ticketbook.pack(),
|
||||
usable_index: retrieved.spent_tickets,
|
||||
usable_index: retrieved.usable_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::db::Storage;
|
||||
use crate::ticketbook_manager::storage::auxiliary_models::RetrievedTicketbook;
|
||||
use anyhow::{Context, anyhow};
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use auxiliary_models::RetrievedTicketbook;
|
||||
use nym_credential_proxy_lib::error::CredentialProxyError;
|
||||
use nym_credential_proxy_lib::shared_state::ecash_state::{
|
||||
IssuanceTicketBook, IssuedTicketBook, TicketType,
|
||||
@@ -16,6 +16,7 @@ use nym_crypto::aes::cipher::zeroize::Zeroizing;
|
||||
use nym_ecash_time::{EcashTime, ecash_today};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use time::Date;
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) mod auxiliary_models;
|
||||
|
||||
@@ -85,7 +86,7 @@ impl TicketbookManagerStorage {
|
||||
/// Tries to retrieve one of the stored ticketbook that has not yet expired
|
||||
/// it immediately updated the on-disk number of used tickets so that another task
|
||||
/// could obtain their own tickets at the same time
|
||||
pub(crate) async fn next_ticket(
|
||||
pub(super) async fn next_ticket(
|
||||
&self,
|
||||
ticket_type: TicketType,
|
||||
testrun_id: i32,
|
||||
@@ -113,16 +114,33 @@ impl TicketbookManagerStorage {
|
||||
.set_distributed_ticketbook(testrun_id, raw.id, raw.used_tickets)
|
||||
.await?;
|
||||
|
||||
deserialised.update_spent_tickets(raw.used_tickets as u64);
|
||||
Ok(Some(RetrievedTicketbook {
|
||||
ticketbook_id: raw.id,
|
||||
total_tickets: raw
|
||||
.total_tickets
|
||||
.try_into()
|
||||
.context("failed to convert i32 total tickets into u32")?,
|
||||
spent_tickets: deserialised.spent_tickets() as u32,
|
||||
ticketbook: deserialised,
|
||||
}))
|
||||
deserialised.update_spent_tickets((raw.used_tickets) as u64);
|
||||
|
||||
let total = deserialised.params_total_tickets();
|
||||
let spent = raw.used_tickets as u64;
|
||||
|
||||
if spent > total {
|
||||
// should never happen: implies a bug in DB fetching
|
||||
bail!(
|
||||
"testrun_id={testrun_id}, ticketbook_id={}, ticket_type={ticket_type}, \
|
||||
marked as used_tickets = {spent} > params_total_tickets = {total}. \
|
||||
Cannot have spent more than is in the ticketbook",
|
||||
raw.id,
|
||||
);
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"testrun_id={testrun_id}, ticketbook_id={}, ticket_type={ticket_type}, \
|
||||
db_used_tickets={} / total_tickets={total}",
|
||||
raw.id,
|
||||
raw.used_tickets,
|
||||
);
|
||||
}
|
||||
|
||||
let retrieved_ticketbook = RetrievedTicketbook::new(deserialised)
|
||||
.inspect_err(|e| warn!("Failed to convert retrieved ticketbook: {e}"))
|
||||
.ok();
|
||||
|
||||
Ok(retrieved_ticketbook)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user