feat: add keyset u32 mapping migration (#926)

* feat: add keyset u32 mapping migration and duplicate handling

- Add new database migration (version 3) to include u32 representation for keysets
- Implement migration for both redb and SQL databases
- Add duplicate detection and handling for keyset entries
- Create unique index constraint for keyset_u32 column in SQL
- Update keyset storage to include u32 identifiers
- Handle backwards compatibility for existing databases

* chore: clippy

* refactor(cashu): simplify keyset ID verification logic

- Consolidate match expression into a single expression
- Use direct comparison with ensure_cdk macro
- Improve readability of keyset ID validation

* refactor(cdk): rename `fetch_keyset_keys` to `load_keyset_keys` for clarity

- Renamed `fetch_keyset_keys` to `load_keyset_keys` across multiple modules to better reflect its behavior of loading keys from local storage or fetching from mint when missing.
- Added debug logging to indicate when keys are being fetched from the mint.
- Simplified key loading logic in `update_mint_keysets` by removing redundant existence checks.

* chore: remove unused vec
This commit is contained in:
thesimplekid
2025-07-31 10:04:38 -04:00
committed by GitHub
parent 3a3cd88ee9
commit 3c4fce5c45
12 changed files with 266 additions and 42 deletions

View File

@@ -19,4 +19,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("sqlite", "20250401120000_add_transactions_table.sql", include_str!(r#"./migrations/sqlite/20250401120000_add_transactions_table.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", "20250729111701_keyset_v2_u32.sql", include_str!(r#"./migrations/sqlite/20250729111701_keyset_v2_u32.sql"#)),
];

View File

@@ -0,0 +1,11 @@
-- Add u32 representation column to key table with unique constraint
ALTER TABLE key ADD COLUMN keyset_u32 INTEGER;
-- Add unique constraint on the new column
CREATE UNIQUE INDEX IF NOT EXISTS keyset_u32_unique ON key(keyset_u32);
-- Add u32 representation column to keyset table with unique constraint
ALTER TABLE keyset ADD COLUMN keyset_u32 INTEGER;
-- Add unique constraint on the new column
CREATE UNIQUE INDEX IF NOT EXISTS keyset_u32_unique_keyset ON keyset(keyset_u32);

View File

@@ -53,6 +53,74 @@ where
/// Migrate [`WalletSqliteDatabase`]
async fn migrate(conn: &DB) -> Result<(), Error> {
migrate(conn, DB::name(), migrations::MIGRATIONS).await?;
// Update any existing keys with missing keyset_u32 values
Self::add_keyset_u32(conn).await?;
Ok(())
}
async fn add_keyset_u32(conn: &DB) -> Result<(), Error> {
// First get the keysets where keyset_u32 on key is null
let keys_without_u32: Vec<Vec<Column>> = query(
r#"
SELECT
id
FROM key
WHERE keyset_u32 IS NULL
"#,
)?
.fetch_all(conn)
.await?;
for id in keys_without_u32 {
let id = column_as_string!(id.first().unwrap());
if let Ok(id) = Id::from_str(&id) {
query(
r#"
UPDATE
key
SET keyset_u32 = :u32_keyset
WHERE id = :keyset_id
"#,
)?
.bind("u32_keyset", u32::from(id))
.bind("keyset_id", id.to_string())
.execute(conn)
.await?;
}
}
// Also update keysets where keyset_u32 is null
let keysets_without_u32: Vec<Vec<Column>> = query(
r#"
SELECT
id
FROM keyset
WHERE keyset_u32 IS NULL
"#,
)?
.fetch_all(conn)
.await?;
for id in keysets_without_u32 {
let id = column_as_string!(id.first().unwrap());
if let Ok(id) = Id::from_str(&id) {
query(
r#"
UPDATE
keyset
SET keyset_u32 = :u32_keyset
WHERE id = :keyset_id
"#,
)?
.bind("u32_keyset", u32::from(id))
.bind("keyset_id", id.to_string())
.execute(conn)
.await?;
}
}
Ok(())
}
}
@@ -301,15 +369,12 @@ ON CONFLICT(mint_url) DO UPDATE SET
query(
r#"
INSERT INTO keyset
(mint_url, id, unit, active, input_fee_ppk, final_expiry)
(mint_url, id, unit, active, input_fee_ppk, final_expiry, keyset_u32)
VALUES
(:mint_url, :id, :unit, :active, :input_fee_ppk, :final_expiry)
(:mint_url, :id, :unit, :active, :input_fee_ppk, :final_expiry, :keyset_u32)
ON CONFLICT(id) DO UPDATE SET
mint_url = excluded.mint_url,
unit = excluded.unit,
active = excluded.active,
input_fee_ppk = excluded.input_fee_ppk,
final_expiry = excluded.final_expiry;
input_fee_ppk = excluded.input_fee_ppk
"#,
)?
.bind("mint_url", mint_url.to_string())
@@ -318,6 +383,7 @@ ON CONFLICT(mint_url) DO UPDATE SET
.bind("active", keyset.active)
.bind("input_fee_ppk", keyset.input_fee_ppk as i64)
.bind("final_expiry", keyset.final_expiry.map(|v| v as i64))
.bind("keyset_u32", u32::from(keyset.id))
.execute(&self.db)
.await?;
}
@@ -554,11 +620,9 @@ ON CONFLICT(id) DO UPDATE SET
query(
r#"
INSERT INTO key
(id, keys)
(id, keys, keyset_u32)
VALUES
(:id, :keys)
ON CONFLICT(id) DO UPDATE SET
keys = excluded.keys
(:id, :keys, :keyset_u32)
"#,
)?
.bind("id", keyset.id.to_string())
@@ -566,6 +630,7 @@ ON CONFLICT(id) DO UPDATE SET
"keys",
serde_json::to_string(&keyset.keys).map_err(Error::from)?,
)
.bind("keyset_u32", u32::from(keyset.id))
.execute(&self.db)
.await?;