feat(wallet): update mint url

feat(cli): add change mint
This commit is contained in:
thesimplekid
2024-06-25 08:31:16 +01:00
parent e358401ec8
commit 54c50c3724
9 changed files with 302 additions and 0 deletions

View File

@@ -58,6 +58,8 @@ enum Commands {
Burn(sub_commands::burn::BurnSubCommand), Burn(sub_commands::burn::BurnSubCommand),
/// Restore proofs from seed /// Restore proofs from seed
Restore(sub_commands::restore::RestoreSubCommand), Restore(sub_commands::restore::RestoreSubCommand),
/// Update Mint Url
UpdateMintUrl(sub_commands::update_mint_url::UpdateMintUrlSubCommand),
} }
#[tokio::main] #[tokio::main]
@@ -145,5 +147,8 @@ async fn main() -> Result<()> {
Commands::Restore(sub_command_args) => { Commands::Restore(sub_command_args) => {
sub_commands::restore::restore(wallet, sub_command_args).await sub_commands::restore::restore(wallet, sub_command_args).await
} }
Commands::UpdateMintUrl(sub_command_args) => {
sub_commands::update_mint_url::update_mint_url(wallet, sub_command_args).await
}
} }
} }

View File

@@ -9,3 +9,4 @@ pub mod pending_mints;
pub mod receive; pub mod receive;
pub mod restore; pub mod restore;
pub mod send; pub mod send;
pub mod update_mint_url;

View File

@@ -0,0 +1,30 @@
use anyhow::Result;
use cdk::url::UncheckedUrl;
use cdk::wallet::Wallet;
use clap::Args;
#[derive(Args)]
pub struct UpdateMintUrlSubCommand {
/// Old Mint Url
old_mint_url: UncheckedUrl,
/// New Mint Url
new_mint_url: UncheckedUrl,
}
pub async fn update_mint_url(
wallet: Wallet,
sub_command_args: &UpdateMintUrlSubCommand,
) -> Result<()> {
let UpdateMintUrlSubCommand {
old_mint_url,
new_mint_url,
} = sub_command_args;
wallet
.update_mint_url(old_mint_url.clone(), new_mint_url.clone())
.await?;
println!("Mint Url changed from {} to {}", old_mint_url, new_mint_url);
Ok(())
}

View File

@@ -11,6 +11,7 @@ use cdk::nuts::{
}; };
use cdk::types::{MeltQuote, MintQuote, ProofInfo}; use cdk::types::{MeltQuote, MintQuote, ProofInfo};
use cdk::url::UncheckedUrl; use cdk::url::UncheckedUrl;
use cdk::util::unix_time;
use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition}; use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::instrument; use tracing::instrument;
@@ -116,6 +117,23 @@ impl WalletDatabase for RedbWalletDatabase {
Ok(()) Ok(())
} }
#[instrument(skip(self))]
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
let db = self.db.lock().await;
let write_txn = db.begin_write().map_err(Error::from)?;
{
let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
table
.remove(mint_url.to_string().as_str())
.map_err(Error::from)?;
}
write_txn.commit().map_err(Error::from)?;
Ok(())
}
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> { async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
let db = self.db.lock().await; let db = self.db.lock().await;
@@ -152,6 +170,62 @@ impl WalletDatabase for RedbWalletDatabase {
Ok(mints) Ok(mints)
} }
#[instrument(skip(self))]
async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err> {
// Update proofs table
{
let proofs = self
.get_proofs(Some(old_mint_url.clone()), None, None, None)
.await
.map_err(Error::from)?;
if let Some(proofs) = proofs {
// Proofs with new url
let updated_proofs: Vec<ProofInfo> = proofs
.clone()
.into_iter()
.map(|mut p| {
p.mint_url = new_mint_url.clone();
p
})
.collect();
println!("{:?}", updated_proofs);
self.add_proofs(updated_proofs).await?;
}
}
// Update mint quotes
{
let quotes = self.get_mint_quotes().await?;
let unix_time = unix_time();
let quotes: Vec<MintQuote> = quotes
.into_iter()
.filter_map(|mut q| {
if q.expiry < unix_time {
q.mint_url = new_mint_url.clone();
Some(q)
} else {
None
}
})
.collect();
for quote in quotes {
self.add_mint_quote(quote).await?;
}
}
Ok(())
}
#[instrument(skip(self))] #[instrument(skip(self))]
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,

View File

@@ -9,6 +9,7 @@ use cdk::nuts::{
}; };
use cdk::types::{MeltQuote, MintQuote, ProofInfo}; use cdk::types::{MeltQuote, MintQuote, ProofInfo};
use cdk::url::UncheckedUrl; use cdk::url::UncheckedUrl;
use cdk::util::unix_time;
use rexie::*; use rexie::*;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@@ -128,6 +129,24 @@ impl WalletDatabase for RexieWalletDatabase {
Ok(()) Ok(())
} }
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
let rexie = self.db.lock().await;
let transaction = rexie
.transaction(&[MINTS], TransactionMode::ReadWrite)
.map_err(Error::from)?;
let mints_store = transaction.store(MINTS).map_err(Error::from)?;
let mint_url = serde_wasm_bindgen::to_value(&mint_url).map_err(Error::from)?;
mints_store.delete(&mint_url).await.map_err(Error::from)?;
transaction.done().await.map_err(Error::from)?;
Ok(())
}
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> { async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
let rexie = self.db.lock().await; let rexie = self.db.lock().await;
@@ -173,6 +192,55 @@ impl WalletDatabase for RexieWalletDatabase {
Ok(mints) Ok(mints)
} }
async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err> {
let proofs = self
.get_proofs(Some(old_mint_url), None, None, None)
.await
.map_err(Error::from)?;
if let Some(proofs) = proofs {
let updated_proofs: Vec<ProofInfo> = proofs
.clone()
.into_iter()
.map(|mut p| {
p.mint_url = new_mint_url.clone();
p
})
.collect();
self.add_proofs(updated_proofs).await?;
}
// Update mint quotes
{
let quotes = self.get_mint_quotes().await?;
let unix_time = unix_time();
let quotes: Vec<MintQuote> = quotes
.into_iter()
.filter_map(|mut q| {
if q.expiry < unix_time {
q.mint_url = new_mint_url.clone();
Some(q)
} else {
None
}
})
.collect();
for quote in quotes {
self.add_mint_quote(quote).await?;
}
}
Ok(())
}
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,

View File

@@ -108,6 +108,22 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
Ok(()) Ok(())
} }
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
sqlx::query(
r#"
DELETE FROM mint
WHERE mint_url=?
"#,
)
.bind(mint_url.to_string())
.execute(&self.pool)
.await
.map_err(Error::from)?;
Ok(())
}
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> { async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
let rec = sqlx::query( let rec = sqlx::query(
r#" r#"
@@ -155,6 +171,32 @@ FROM mint
Ok(mints) Ok(mints)
} }
async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err> {
let tables = ["mint_quote", "proof"];
for table in &tables {
let query = format!(
r#"
UPDATE {}
SET mint_url = ?
WHERE mint_url = ?;
"#,
table
);
sqlx::query(&query)
.bind(new_mint_url.to_string())
.bind(old_mint_url.to_string())
.execute(&self.pool)
.await
.map_err(Error::from)?;
}
Ok(())
}
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,

View File

@@ -56,8 +56,14 @@ pub trait WalletDatabase: Debug {
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
mint_info: Option<MintInfo>, mint_info: Option<MintInfo>,
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err>;
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err>; async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err>;
async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err>; async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err>;
async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err>;
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,

View File

@@ -13,6 +13,7 @@ use crate::nuts::{
}; };
use crate::types::{MeltQuote, MintQuote, ProofInfo}; use crate::types::{MeltQuote, MintQuote, ProofInfo};
use crate::url::UncheckedUrl; use crate::url::UncheckedUrl;
use crate::util::unix_time;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct WalletMemoryDatabase { pub struct WalletMemoryDatabase {
@@ -71,6 +72,13 @@ impl WalletDatabase for WalletMemoryDatabase {
Ok(()) Ok(())
} }
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err> {
let mut mints = self.mints.write().await;
mints.remove(&mint_url);
Ok(())
}
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> { async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
Ok(self.mints.read().await.get(&mint_url).cloned().flatten()) Ok(self.mints.read().await.get(&mint_url).cloned().flatten())
} }
@@ -79,6 +87,55 @@ impl WalletDatabase for WalletMemoryDatabase {
Ok(self.mints.read().await.clone()) Ok(self.mints.read().await.clone())
} }
async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err> {
let proofs = self
.get_proofs(Some(old_mint_url), None, None, None)
.await
.map_err(Error::from)?;
if let Some(proofs) = proofs {
let updated_proofs: Vec<ProofInfo> = proofs
.clone()
.into_iter()
.map(|mut p| {
p.mint_url = new_mint_url.clone();
p
})
.collect();
self.add_proofs(updated_proofs).await?;
}
// Update mint quotes
{
let quotes = self.get_mint_quotes().await?;
let unix_time = unix_time();
let quotes: Vec<MintQuote> = quotes
.into_iter()
.filter_map(|mut q| {
if q.expiry < unix_time {
q.mint_url = new_mint_url.clone();
Some(q)
} else {
None
}
})
.collect();
for quote in quotes {
self.add_mint_quote(quote).await?;
}
}
Ok(())
}
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,

View File

@@ -204,6 +204,25 @@ impl Wallet {
Ok(mint_balances) Ok(mint_balances)
} }
/// Update Mint information and related entries in the event a mint changes its URL
#[instrument(skip(self), fields(old_mint_url = %old_mint_url, new_mint_url = %new_mint_url))]
pub async fn update_mint_url(
&self,
old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl,
) -> Result<(), Error> {
// Adding the new url as a new mint will get the current keysets of the mint
self.add_mint(new_mint_url.clone()).await?;
// Where the mint_url is in the database it must be updated
self.localstore
.update_mint_url(old_mint_url.clone(), new_mint_url)
.await?;
self.localstore.remove_mint(old_mint_url).await?;
Ok(())
}
/// Get unspent proofs for mint /// Get unspent proofs for mint
#[instrument(skip(self), fields(mint_url = %mint_url))] #[instrument(skip(self), fields(mint_url = %mint_url))]
pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> { pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {