Keyset counter (#950)

* feat: refresh keysets

* fix(cdk): resolve keyset counter skipping index 0 in deterministic secret generation

- Modified Database::get_keyset_counter to return u32 instead of Option<u32>
- Added database migrations to increment existing keyset counters by 1
- Removed counter increment logic from wallet operations to use actual counter value
- Ensures deterministic secret generation starts from index 0 instead of skipping it
This commit is contained in:
thesimplekid
2025-08-13 08:54:45 +01:00
committed by GitHub
parent 69d0cf0818
commit 5d98fdf353
12 changed files with 70 additions and 18 deletions

View File

@@ -18,8 +18,11 @@
- cdk-integration-tests: New binary `start_fake_mint` for testing fake mint instances ([thesimplekid]). - cdk-integration-tests: New binary `start_fake_mint` for testing fake mint instances ([thesimplekid]).
- cdk-integration-tests: New binary `start_regtest_mints` for testing regtest mints ([thesimplekid]). - cdk-integration-tests: New binary `start_regtest_mints` for testing regtest mints ([thesimplekid]).
- cdk-integration-tests: Shared utilities module for common integration test functionality ([thesimplekid]). - cdk-integration-tests: Shared utilities module for common integration test functionality ([thesimplekid]).
- cdk-redb: Database migration to increment keyset counters by 1 for existing keysets with counter > 0 ([thesimplekid]).
- cdk-sql-common: Database migration to increment keyset counters by 1 for existing keysets with counter > 0 ([thesimplekid]).
### Changed ### Changed
- cdk-common: Modified `Database::get_keyset_counter` trait method to return `u32` instead of `Option<u32>` for simpler keyset counter handling ([thesimplekid]).
- cdk: Refactored wallet keyset management methods for better clarity and separation of concerns ([thesimplekid]). - cdk: Refactored wallet keyset management methods for better clarity and separation of concerns ([thesimplekid]).
- cdk: Renamed `get_keyset_keys` to `fetch_keyset_keys` to indicate network operation ([thesimplekid]). - cdk: Renamed `get_keyset_keys` to `fetch_keyset_keys` to indicate network operation ([thesimplekid]).
- cdk: Renamed `get_active_mint_keyset` to `fetch_active_keyset` for consistency ([thesimplekid]). - cdk: Renamed `get_active_mint_keyset` to `fetch_active_keyset` for consistency ([thesimplekid]).

View File

@@ -211,7 +211,8 @@ async fn main() -> Result<()> {
let wallet_clone = wallet.clone(); let wallet_clone = wallet.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = wallet_clone.get_mint_info().await { // We refresh keysets, this internally gets mint info
if let Err(err) = wallet_clone.refresh_keysets().await {
tracing::error!( tracing::error!(
"Could not get mint quote for {}, {}", "Could not get mint quote for {}, {}",
wallet_clone.mint_url, wallet_clone.mint_url,

View File

@@ -102,7 +102,7 @@ pub trait Database: Debug {
/// Increment Keyset counter /// Increment Keyset counter
async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>; async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>;
/// Get current Keyset counter /// Get current Keyset counter
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err>; async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<u32, Self::Err>;
/// Add transaction to storage /// Add transaction to storage
async fn add_transaction(&self, transaction: Transaction) -> Result<(), Self::Err>; async fn add_transaction(&self, transaction: Transaction) -> Result<(), Self::Err>;

View File

@@ -11,7 +11,7 @@ use redb::{
}; };
use super::Error; use super::Error;
use crate::wallet::{KEYSETS_TABLE, KEYSET_U32_MAPPING, MINT_KEYS_TABLE}; use crate::wallet::{KEYSETS_TABLE, KEYSET_COUNTER, KEYSET_U32_MAPPING, MINT_KEYS_TABLE};
// <Mint_url, Info> // <Mint_url, Info>
const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table"); const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table");
@@ -152,3 +152,51 @@ fn migrate_trim_mint_urls_01_to_02(db: Arc<Database>) -> Result<(), Error> {
migrate_mint_keyset_table_01_to_02(Arc::clone(&db))?; migrate_mint_keyset_table_01_to_02(Arc::clone(&db))?;
Ok(()) Ok(())
} }
pub(crate) fn migrate_03_to_04(db: Arc<Database>) -> Result<u32, Error> {
let write_txn = db.begin_write().map_err(Error::from)?;
// Get all existing keyset IDs from the KEYSET_COUNTER table that have a counter > 0
let keyset_ids_to_increment: Vec<(String, u32)>;
{
let table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
keyset_ids_to_increment = table
.iter()
.map_err(Error::from)?
.flatten()
.filter_map(|(keyset_id, counter)| {
let counter_value = counter.value();
// Only include keysets where counter > 0
if counter_value > 0 {
Some((keyset_id.value().to_string(), counter_value))
} else {
None
}
})
.collect();
}
// Increment counter by 1 for all keysets where counter > 0
{
let mut table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
for (keyset_id, current_counter) in keyset_ids_to_increment {
let new_counter = current_counter + 1;
table
.insert(keyset_id.as_str(), new_counter)
.map_err(Error::from)?;
tracing::info!(
"Incremented counter for keyset {} from {} to {}",
keyset_id,
current_counter,
new_counter
);
}
}
write_txn.commit()?;
Ok(4)
}

View File

@@ -21,7 +21,7 @@ use tracing::instrument;
use super::error::Error; use super::error::Error;
use crate::migrations::migrate_00_to_01; use crate::migrations::migrate_00_to_01;
use crate::wallet::migrations::{migrate_01_to_02, migrate_02_to_03}; use crate::wallet::migrations::{migrate_01_to_02, migrate_02_to_03, migrate_03_to_04};
mod migrations; mod migrations;
@@ -46,7 +46,7 @@ const TRANSACTIONS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("t
const KEYSET_U32_MAPPING: TableDefinition<u32, &str> = TableDefinition::new("keyset_u32_mapping"); const KEYSET_U32_MAPPING: TableDefinition<u32, &str> = TableDefinition::new("keyset_u32_mapping");
const DATABASE_VERSION: u32 = 3; const DATABASE_VERSION: u32 = 4;
/// Wallet Redb Database /// Wallet Redb Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -96,6 +96,10 @@ impl WalletRedbDatabase {
current_file_version = migrate_02_to_03(Arc::clone(&db))?; current_file_version = migrate_02_to_03(Arc::clone(&db))?;
} }
if current_file_version == 3 {
current_file_version = migrate_03_to_04(Arc::clone(&db))?;
}
if current_file_version != DATABASE_VERSION { if current_file_version != DATABASE_VERSION {
tracing::warn!( tracing::warn!(
"Database upgrade did not complete at {} current is {}", "Database upgrade did not complete at {} current is {}",
@@ -786,7 +790,7 @@ impl WalletDatabase for WalletRedbDatabase {
} }
#[instrument(skip(self), fields(keyset_id = %keyset_id))] #[instrument(skip(self), fields(keyset_id = %keyset_id))]
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> { async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<u32, Self::Err> {
let read_txn = self.db.begin_read().map_err(Error::from)?; let read_txn = self.db.begin_read().map_err(Error::from)?;
let table = read_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?; let table = read_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
@@ -794,7 +798,7 @@ impl WalletDatabase for WalletRedbDatabase {
.get(keyset_id.to_string().as_str()) .get(keyset_id.to_string().as_str())
.map_err(Error::from)?; .map_err(Error::from)?;
Ok(counter.map(|c| c.value())) Ok(counter.map_or(0, |c| c.value()))
} }
#[instrument(skip(self))] #[instrument(skip(self))]

View File

@@ -20,4 +20,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("sqlite", "20250616144830_add_keyset_expiry.sql", include_str!(r#"./migrations/sqlite/20250616144830_add_keyset_expiry.sql"#)), ("sqlite", "20250616144830_add_keyset_expiry.sql", include_str!(r#"./migrations/sqlite/20250616144830_add_keyset_expiry.sql"#)),
("sqlite", "20250707093445_bolt12.sql", include_str!(r#"./migrations/sqlite/20250707093445_bolt12.sql"#)), ("sqlite", "20250707093445_bolt12.sql", include_str!(r#"./migrations/sqlite/20250707093445_bolt12.sql"#)),
("sqlite", "20250729111701_keyset_v2_u32.sql", include_str!(r#"./migrations/sqlite/20250729111701_keyset_v2_u32.sql"#)), ("sqlite", "20250729111701_keyset_v2_u32.sql", include_str!(r#"./migrations/sqlite/20250729111701_keyset_v2_u32.sql"#)),
("sqlite", "20250812084621_keyset_plus_one.sql", include_str!(r#"./migrations/sqlite/20250812084621_keyset_plus_one.sql"#)),
]; ];

View File

@@ -0,0 +1,2 @@
-- Increment keyset counter by 1 where counter > 0
UPDATE keyset SET counter = counter + 1 WHERE counter > 0;

View File

@@ -857,7 +857,7 @@ ON CONFLICT(id) DO UPDATE SET
} }
#[instrument(skip(self), fields(keyset_id = %keyset_id))] #[instrument(skip(self), fields(keyset_id = %keyset_id))]
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> { async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<u32, Self::Err> {
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?; let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
Ok(query( Ok(query(
r#" r#"
@@ -873,7 +873,8 @@ ON CONFLICT(id) DO UPDATE SET
.pluck(&*conn) .pluck(&*conn)
.await? .await?
.map(|n| Ok::<_, Error>(column_as_number!(n))) .map(|n| Ok::<_, Error>(column_as_number!(n)))
.transpose()?) .transpose()?
.unwrap_or(0))
} }
#[instrument(skip(self))] #[instrument(skip(self))]

View File

@@ -234,8 +234,6 @@ impl Wallet {
.get_keyset_counter(&active_keyset_id) .get_keyset_counter(&active_keyset_id)
.await?; .await?;
let count = count.map_or(0, |c| c + 1);
let premint_secrets = match &spending_conditions { let premint_secrets = match &spending_conditions {
Some(spending_conditions) => PreMintSecrets::with_conditions( Some(spending_conditions) => PreMintSecrets::with_conditions(
active_keyset_id, active_keyset_id,

View File

@@ -112,8 +112,6 @@ impl Wallet {
.get_keyset_counter(&active_keyset_id) .get_keyset_counter(&active_keyset_id)
.await?; .await?;
let count = count.map_or(0, |c| c + 1);
let amount = match amount { let amount = match amount {
Some(amount) => amount, Some(amount) => amount,
None => { None => {

View File

@@ -153,8 +153,6 @@ impl Wallet {
.get_keyset_counter(&active_keyset_id) .get_keyset_counter(&active_keyset_id)
.await?; .await?;
let count = count.map_or(0, |c| c + 1);
let premint_secrets = PreMintSecrets::from_seed_blank( let premint_secrets = PreMintSecrets::from_seed_blank(
active_keyset_id, active_keyset_id,
count, count,

View File

@@ -248,13 +248,11 @@ impl Wallet {
let derived_secret_count; let derived_secret_count;
let count = self let mut count = self
.localstore .localstore
.get_keyset_counter(&active_keyset_id) .get_keyset_counter(&active_keyset_id)
.await?; .await?;
let mut count = count.map_or(0, |c| c + 1);
let (mut desired_messages, change_messages) = match spending_conditions { let (mut desired_messages, change_messages) = match spending_conditions {
Some(conditions) => { Some(conditions) => {
let change_premint_secrets = PreMintSecrets::from_seed( let change_premint_secrets = PreMintSecrets::from_seed(