Răsfoiți Sursa

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
thesimplekid 2 luni în urmă
părinte
comite
5d98fdf353

+ 3 - 0
CHANGELOG.md

@@ -18,8 +18,11 @@
 - 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: 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
+- 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: 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]).

+ 2 - 1
crates/cdk-cli/src/main.rs

@@ -211,7 +211,8 @@ async fn main() -> Result<()> {
             let wallet_clone = wallet.clone();
 
             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!(
                         "Could not get mint quote for {}, {}",
                         wallet_clone.mint_url,

+ 1 - 1
crates/cdk-common/src/database/wallet.rs

@@ -102,7 +102,7 @@ pub trait Database: Debug {
     /// Increment Keyset counter
     async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>;
     /// 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
     async fn add_transaction(&self, transaction: Transaction) -> Result<(), Self::Err>;

+ 49 - 1
crates/cdk-redb/src/wallet/migrations.rs

@@ -11,7 +11,7 @@ use redb::{
 };
 
 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>
 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))?;
     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)
+}

+ 8 - 4
crates/cdk-redb/src/wallet/mod.rs

@@ -21,7 +21,7 @@ use tracing::instrument;
 
 use super::error::Error;
 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;
 
@@ -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 DATABASE_VERSION: u32 = 3;
+const DATABASE_VERSION: u32 = 4;
 
 /// Wallet Redb Database
 #[derive(Debug, Clone)]
@@ -96,6 +96,10 @@ impl WalletRedbDatabase {
                                 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 {
                                 tracing::warn!(
                                     "Database upgrade did not complete at {} current is {}",
@@ -786,7 +790,7 @@ impl WalletDatabase for WalletRedbDatabase {
     }
 
     #[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 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())
             .map_err(Error::from)?;
 
-        Ok(counter.map(|c| c.value()))
+        Ok(counter.map_or(0, |c| c.value()))
     }
 
     #[instrument(skip(self))]

+ 1 - 0
crates/cdk-sql-common/src/wallet/migrations.rs

@@ -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", "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", "20250812084621_keyset_plus_one.sql", include_str!(r#"./migrations/sqlite/20250812084621_keyset_plus_one.sql"#)),
 ];

+ 2 - 0
crates/cdk-sql-common/src/wallet/migrations/sqlite/20250812084621_keyset_plus_one.sql

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

+ 3 - 2
crates/cdk-sql-common/src/wallet/mod.rs

@@ -857,7 +857,7 @@ ON CONFLICT(id) DO UPDATE SET
     }
 
     #[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)))?;
         Ok(query(
             r#"
@@ -873,7 +873,8 @@ ON CONFLICT(id) DO UPDATE SET
         .pluck(&*conn)
         .await?
         .map(|n| Ok::<_, Error>(column_as_number!(n)))
-        .transpose()?)
+        .transpose()?
+        .unwrap_or(0))
     }
 
     #[instrument(skip(self))]

+ 0 - 2
crates/cdk/src/wallet/issue/issue_bolt11.rs

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

+ 0 - 2
crates/cdk/src/wallet/issue/issue_bolt12.rs

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

+ 0 - 2
crates/cdk/src/wallet/melt/melt_bolt11.rs

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

+ 1 - 3
crates/cdk/src/wallet/swap.rs

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