mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-18 21:25:09 +01:00
fix: config overwrite on start up (#1081)
* fix: config overwrite on start up
This commit is contained in:
@@ -4,10 +4,9 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cashu::quote_id::QuoteId;
|
use cashu::quote_id::QuoteId;
|
||||||
use cashu::{Amount, MintInfo};
|
use cashu::Amount;
|
||||||
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use crate::common::QuoteTTL;
|
|
||||||
use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote};
|
use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote};
|
||||||
use crate::nuts::{
|
use crate::nuts::{
|
||||||
BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
|
BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
|
||||||
@@ -396,7 +395,6 @@ pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Base database writer
|
/// Base database writer
|
||||||
#[async_trait]
|
|
||||||
pub trait Transaction<'a, Error>:
|
pub trait Transaction<'a, Error>:
|
||||||
DbTransactionFinalizer<Err = Error>
|
DbTransactionFinalizer<Err = Error>
|
||||||
+ QuotesTransaction<'a, Err = Error>
|
+ QuotesTransaction<'a, Err = Error>
|
||||||
@@ -404,11 +402,6 @@ pub trait Transaction<'a, Error>:
|
|||||||
+ ProofsTransaction<'a, Err = Error>
|
+ ProofsTransaction<'a, Err = Error>
|
||||||
+ KVStoreTransaction<'a, Error>
|
+ KVStoreTransaction<'a, Error>
|
||||||
{
|
{
|
||||||
/// Set [`QuoteTTL`]
|
|
||||||
async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Set [`MintInfo`]
|
|
||||||
async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key-Value Store Database trait
|
/// Key-Value Store Database trait
|
||||||
@@ -457,12 +450,6 @@ pub trait Database<Error>:
|
|||||||
async fn begin_transaction<'a>(
|
async fn begin_transaction<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
|
) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
|
||||||
|
|
||||||
/// Get [`MintInfo`]
|
|
||||||
async fn get_mint_info(&self) -> Result<MintInfo, Error>;
|
|
||||||
|
|
||||||
/// Get [`QuoteTTL`]
|
|
||||||
async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias for Mint Database
|
/// Type alias for Mint Database
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ fn create_ldk_settings(
|
|||||||
) -> cdk_mintd::config::Settings {
|
) -> cdk_mintd::config::Settings {
|
||||||
cdk_mintd::config::Settings {
|
cdk_mintd::config::Settings {
|
||||||
info: cdk_mintd::config::Info {
|
info: cdk_mintd::config::Info {
|
||||||
|
quote_ttl: None,
|
||||||
url: format!("http://127.0.0.1:{port}"),
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
listen_host: "127.0.0.1".to_string(),
|
listen_host: "127.0.0.1".to_string(),
|
||||||
listen_port: port,
|
listen_port: port,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use bip39::Mnemonic;
|
|||||||
use cashu::quote_id::QuoteId;
|
use cashu::quote_id::QuoteId;
|
||||||
use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
|
use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
|
use cdk::cdk_database::{self, WalletDatabase};
|
||||||
use cdk::mint::{MintBuilder, MintMeltLimits};
|
use cdk::mint::{MintBuilder, MintMeltLimits};
|
||||||
use cdk::nuts::nut00::ProofsMethods;
|
use cdk::nuts::nut00::ProofsMethods;
|
||||||
use cdk::nuts::{
|
use cdk::nuts::{
|
||||||
@@ -271,17 +271,14 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
|
|||||||
.with_description("pure test mint".to_string())
|
.with_description("pure test mint".to_string())
|
||||||
.with_urls(vec!["https://aaa".to_string()]);
|
.with_urls(vec!["https://aaa".to_string()]);
|
||||||
|
|
||||||
let tx_localstore = localstore.clone();
|
|
||||||
let mut tx = tx_localstore.begin_transaction().await?;
|
|
||||||
|
|
||||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||||
tx.set_quote_ttl(quote_ttl).await?;
|
|
||||||
tx.commit().await?;
|
|
||||||
|
|
||||||
let mint = mint_builder
|
let mint = mint_builder
|
||||||
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
|
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
mint.set_quote_ttl(quote_ttl).await?;
|
||||||
|
|
||||||
mint.start().await?;
|
mint.start().await?;
|
||||||
|
|
||||||
Ok(mint)
|
Ok(mint)
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ pub fn create_fake_wallet_settings(
|
|||||||
cdk_mintd::config::Settings {
|
cdk_mintd::config::Settings {
|
||||||
info: cdk_mintd::config::Info {
|
info: cdk_mintd::config::Info {
|
||||||
url: format!("http://127.0.0.1:{port}"),
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
|
quote_ttl: None,
|
||||||
|
|
||||||
listen_host: "127.0.0.1".to_string(),
|
listen_host: "127.0.0.1".to_string(),
|
||||||
listen_port: port,
|
listen_port: port,
|
||||||
seed: None,
|
seed: None,
|
||||||
@@ -233,6 +235,8 @@ pub fn create_cln_settings(
|
|||||||
cdk_mintd::config::Settings {
|
cdk_mintd::config::Settings {
|
||||||
info: cdk_mintd::config::Info {
|
info: cdk_mintd::config::Info {
|
||||||
url: format!("http://127.0.0.1:{port}"),
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
|
quote_ttl: None,
|
||||||
|
|
||||||
listen_host: "127.0.0.1".to_string(),
|
listen_host: "127.0.0.1".to_string(),
|
||||||
listen_port: port,
|
listen_port: port,
|
||||||
seed: None,
|
seed: None,
|
||||||
@@ -278,6 +282,7 @@ pub fn create_lnd_settings(
|
|||||||
) -> cdk_mintd::config::Settings {
|
) -> cdk_mintd::config::Settings {
|
||||||
cdk_mintd::config::Settings {
|
cdk_mintd::config::Settings {
|
||||||
info: cdk_mintd::config::Info {
|
info: cdk_mintd::config::Info {
|
||||||
|
quote_ttl: None,
|
||||||
url: format!("http://127.0.0.1:{port}"),
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
listen_host: "127.0.0.1".to_string(),
|
listen_host: "127.0.0.1".to_string(),
|
||||||
listen_port: port,
|
listen_port: port,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bip39::Mnemonic;
|
use bip39::Mnemonic;
|
||||||
use cdk::cdk_database::MintDatabase;
|
|
||||||
use cdk::mint::{MintBuilder, MintMeltLimits};
|
use cdk::mint::{MintBuilder, MintMeltLimits};
|
||||||
use cdk::nuts::{CurrencyUnit, PaymentMethod};
|
use cdk::nuts::{CurrencyUnit, PaymentMethod};
|
||||||
use cdk::types::{FeeReserve, QuoteTTL};
|
use cdk::types::{FeeReserve, QuoteTTL};
|
||||||
@@ -64,12 +63,9 @@ async fn test_correct_keyset() {
|
|||||||
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
|
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut tx = localstore.begin_transaction().await.unwrap();
|
|
||||||
|
|
||||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||||
tx.set_quote_ttl(quote_ttl).await.unwrap();
|
mint.set_quote_ttl(quote_ttl).await.unwrap();
|
||||||
|
|
||||||
tx.commit().await.unwrap();
|
|
||||||
|
|
||||||
let active = mint.get_active_keysets();
|
let active = mint.get_active_keysets();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ mnemonic = ""
|
|||||||
# input_fee_ppk = 0
|
# input_fee_ppk = 0
|
||||||
# enable_swagger_ui = false
|
# enable_swagger_ui = false
|
||||||
|
|
||||||
|
[info.quote_ttl]
|
||||||
|
# Prefer explicit fields over inline tables for readability and ease of overrides
|
||||||
|
mint_ttl = 600
|
||||||
|
melt_ttl = 120
|
||||||
|
|
||||||
|
|
||||||
[info.logging]
|
[info.logging]
|
||||||
# Where to output logs: "stdout", "file", or "both" (default: "both")
|
# Where to output logs: "stdout", "file", or "both" (default: "both")
|
||||||
# Note: "stdout" actually outputs to stderr (standard error stream)
|
# Note: "stdout" actually outputs to stderr (standard error stream)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use bitcoin::hashes::{sha256, Hash};
|
|||||||
use cdk::nuts::{CurrencyUnit, PublicKey};
|
use cdk::nuts::{CurrencyUnit, PublicKey};
|
||||||
use cdk::Amount;
|
use cdk::Amount;
|
||||||
use cdk_axum::cache;
|
use cdk_axum::cache;
|
||||||
|
use cdk_common::common::QuoteTTL;
|
||||||
use config::{Config, ConfigError, File};
|
use config::{Config, ConfigError, File};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -68,6 +69,12 @@ pub struct Info {
|
|||||||
///
|
///
|
||||||
/// This requires `mintd` was built with the `swagger` feature flag.
|
/// This requires `mintd` was built with the `swagger` feature flag.
|
||||||
pub enable_swagger_ui: Option<bool>,
|
pub enable_swagger_ui: Option<bool>,
|
||||||
|
|
||||||
|
/// Optional persisted quote TTL values (seconds) to initialize the database with
|
||||||
|
/// when RPC is disabled or on first-run when RPC is enabled.
|
||||||
|
/// If not provided, defaults are used.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub quote_ttl: Option<QuoteTTL>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Info {
|
impl Default for Info {
|
||||||
@@ -84,6 +91,7 @@ impl Default for Info {
|
|||||||
http_cache: cache::Config::default(),
|
http_cache: cache::Config::default(),
|
||||||
enable_swagger_ui: None,
|
enable_swagger_ui: None,
|
||||||
logging: LoggingConfig::default(),
|
logging: LoggingConfig::default(),
|
||||||
|
quote_ttl: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ pub const ENV_SECONDS_QUOTE_VALID: &str = "CDK_MINTD_SECONDS_QUOTE_VALID";
|
|||||||
pub const ENV_CACHE_SECONDS: &str = "CDK_MINTD_CACHE_SECONDS";
|
pub const ENV_CACHE_SECONDS: &str = "CDK_MINTD_CACHE_SECONDS";
|
||||||
pub const ENV_EXTEND_CACHE_SECONDS: &str = "CDK_MINTD_EXTEND_CACHE_SECONDS";
|
pub const ENV_EXTEND_CACHE_SECONDS: &str = "CDK_MINTD_EXTEND_CACHE_SECONDS";
|
||||||
pub const ENV_INPUT_FEE_PPK: &str = "CDK_MINTD_INPUT_FEE_PPK";
|
pub const ENV_INPUT_FEE_PPK: &str = "CDK_MINTD_INPUT_FEE_PPK";
|
||||||
|
pub const ENV_QUOTE_TTL_MINT: &str = "CDK_MINTD_QUOTE_TTL_MINT";
|
||||||
|
pub const ENV_QUOTE_TTL_MELT: &str = "CDK_MINTD_QUOTE_TTL_MELT";
|
||||||
|
|
||||||
pub const ENV_ENABLE_SWAGGER: &str = "CDK_MINTD_ENABLE_SWAGGER";
|
pub const ENV_ENABLE_SWAGGER: &str = "CDK_MINTD_ENABLE_SWAGGER";
|
||||||
pub const ENV_LOGGING_OUTPUT: &str = "CDK_MINTD_LOGGING_OUTPUT";
|
pub const ENV_LOGGING_OUTPUT: &str = "CDK_MINTD_LOGGING_OUTPUT";
|
||||||
pub const ENV_LOGGING_CONSOLE_LEVEL: &str = "CDK_MINTD_LOGGING_CONSOLE_LEVEL";
|
pub const ENV_LOGGING_CONSOLE_LEVEL: &str = "CDK_MINTD_LOGGING_CONSOLE_LEVEL";
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use cdk_common::common::QuoteTTL;
|
||||||
|
|
||||||
use super::common::*;
|
use super::common::*;
|
||||||
use crate::config::{Info, LoggingOutput};
|
use crate::config::{Info, LoggingOutput};
|
||||||
|
|
||||||
@@ -85,6 +87,27 @@ impl Info {
|
|||||||
|
|
||||||
self.http_cache = self.http_cache.from_env();
|
self.http_cache = self.http_cache.from_env();
|
||||||
|
|
||||||
|
// Quote TTL from env
|
||||||
|
let mut mint_ttl_env: Option<u64> = None;
|
||||||
|
let mut melt_ttl_env: Option<u64> = None;
|
||||||
|
if let Ok(mint_ttl_str) = env::var(ENV_QUOTE_TTL_MINT) {
|
||||||
|
if let Ok(v) = mint_ttl_str.parse::<u64>() {
|
||||||
|
mint_ttl_env = Some(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(melt_ttl_str) = env::var(ENV_QUOTE_TTL_MELT) {
|
||||||
|
if let Ok(v) = melt_ttl_str.parse::<u64>() {
|
||||||
|
melt_ttl_env = Some(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mint_ttl_env.is_some() || melt_ttl_env.is_some() {
|
||||||
|
let current = self.quote_ttl.unwrap_or_default();
|
||||||
|
self.quote_ttl = Some(QuoteTTL {
|
||||||
|
mint_ttl: mint_ttl_env.unwrap_or(current.mint_ttl),
|
||||||
|
melt_ttl: melt_ttl_env.unwrap_or(current.melt_ttl),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ use cdk::nuts::CurrencyUnit;
|
|||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
|
use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
|
||||||
use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
|
use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
|
||||||
use cdk::types::QuoteTTL;
|
|
||||||
use cdk_axum::cache::HttpCache;
|
use cdk_axum::cache::HttpCache;
|
||||||
|
use cdk_common::common::QuoteTTL;
|
||||||
use cdk_common::database::DynMintDatabase;
|
use cdk_common::database::DynMintDatabase;
|
||||||
// internal crate modules
|
// internal crate modules
|
||||||
#[cfg(feature = "prometheus")]
|
#[cfg(feature = "prometheus")]
|
||||||
@@ -890,16 +890,21 @@ async fn start_services_with_shutdown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the desired QuoteTTL from config/env or fall back to defaults
|
||||||
|
let desired_quote_ttl: QuoteTTL = settings.info.quote_ttl.unwrap_or_default();
|
||||||
|
|
||||||
if rpc_enabled {
|
if rpc_enabled {
|
||||||
if mint.mint_info().await.is_err() {
|
if mint.mint_info().await.is_err() {
|
||||||
tracing::info!("Mint info not set on mint, setting.");
|
tracing::info!("Mint info not set on mint, setting.");
|
||||||
|
// First boot with RPC enabled: seed from config
|
||||||
mint.set_mint_info(mint_builder_info).await?;
|
mint.set_mint_info(mint_builder_info).await?;
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
mint.set_quote_ttl(desired_quote_ttl).await?;
|
||||||
} else {
|
} else {
|
||||||
if mint.localstore().get_quote_ttl().await.is_err() {
|
// If QuoteTTL has never been persisted, seed it now from config
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
if !mint.quote_ttl_is_persisted().await? {
|
||||||
|
mint.set_quote_ttl(desired_quote_ttl).await?;
|
||||||
}
|
}
|
||||||
// Add version information
|
// Add/refresh version information without altering stored mint_info fields
|
||||||
let mint_version = MintVersion::new(
|
let mint_version = MintVersion::new(
|
||||||
"cdk-mintd".to_string(),
|
"cdk-mintd".to_string(),
|
||||||
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
||||||
@@ -911,9 +916,18 @@ async fn start_services_with_shutdown(
|
|||||||
tracing::info!("Mint info already set, not using config file settings.");
|
tracing::info!("Mint info already set, not using config file settings.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("RPC not enabled, using mint info from config.");
|
// RPC disabled: config is source of truth on every boot
|
||||||
|
tracing::info!("RPC not enabled, using mint info and quote TTL from config.");
|
||||||
|
let mut mint_builder_info = mint_builder_info;
|
||||||
|
|
||||||
|
if let Ok(mint_info) = mint.mint_info().await {
|
||||||
|
if mint_builder_info.pubkey.is_none() {
|
||||||
|
mint_builder_info.pubkey = mint_info.pubkey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mint.set_mint_info(mint_builder_info).await?;
|
mint.set_mint_info(mint_builder_info).await?;
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
mint.set_quote_ttl(desired_quote_ttl).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint_info = mint.mint_info().await?;
|
let mint_info = mint.mint_info().await?;
|
||||||
@@ -1120,11 +1134,39 @@ pub async fn run_mintd_with_shutdown(
|
|||||||
|
|
||||||
let mint_builder = MintBuilder::new(localstore);
|
let mint_builder = MintBuilder::new(localstore);
|
||||||
|
|
||||||
|
// If RPC is enabled and DB contains mint_info already, initialize the builder from DB.
|
||||||
|
// This ensures subsequent builder modifications (like version injection) can respect stored values.
|
||||||
|
let maybe_mint_builder = {
|
||||||
|
#[cfg(feature = "management-rpc")]
|
||||||
|
{
|
||||||
|
if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
|
||||||
|
if rpc_settings.enabled {
|
||||||
|
// Best-effort: pull DB state into builder if present
|
||||||
|
let mut tmp = mint_builder;
|
||||||
|
if let Err(e) = tmp.init_from_db_if_present().await {
|
||||||
|
tracing::warn!("Failed to init builder from DB: {}", e);
|
||||||
|
}
|
||||||
|
tmp
|
||||||
|
} else {
|
||||||
|
mint_builder
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mint_builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "management-rpc"))]
|
||||||
|
{
|
||||||
|
mint_builder
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mint_builder =
|
let mint_builder =
|
||||||
configure_mint_builder(settings, mint_builder, runtime, work_dir, Some(kv)).await?;
|
configure_mint_builder(settings, maybe_mint_builder, runtime, work_dir, Some(kv)).await?;
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
|
let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
|
||||||
|
|
||||||
|
let config_mint_info = mint_builder.current_mint_info();
|
||||||
|
|
||||||
let mint = build_mint(settings, keystore, mint_builder).await?;
|
let mint = build_mint(settings, keystore, mint_builder).await?;
|
||||||
|
|
||||||
tracing::debug!("Mint built from builder.");
|
tracing::debug!("Mint built from builder.");
|
||||||
@@ -1136,19 +1178,13 @@ pub async fn run_mintd_with_shutdown(
|
|||||||
// Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
// Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
||||||
mint.check_pending_melt_quotes().await?;
|
mint.check_pending_melt_quotes().await?;
|
||||||
|
|
||||||
let result = start_services_with_shutdown(
|
start_services_with_shutdown(
|
||||||
mint.clone(),
|
mint.clone(),
|
||||||
settings,
|
settings,
|
||||||
work_dir,
|
work_dir,
|
||||||
mint.mint_info().await?,
|
config_mint_info,
|
||||||
shutdown_signal,
|
shutdown_signal,
|
||||||
routers,
|
routers,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
|
||||||
// Ensure any remaining tracing data is flushed
|
|
||||||
// This is particularly important for file-based logging
|
|
||||||
tracing::debug!("Flushing remaining trace data");
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
|||||||
("postgres", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/postgres/20250901090000_add_kv_store.sql"#)),
|
("postgres", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/postgres/20250901090000_add_kv_store.sql"#)),
|
||||||
("postgres", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/postgres/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
|
("postgres", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/postgres/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
|
||||||
("postgres", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/postgres/20250903200000_add_signatory_amounts.sql"#)),
|
("postgres", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/postgres/20250903200000_add_signatory_amounts.sql"#)),
|
||||||
|
("postgres", "20250916221000_drop_config_table.sql", include_str!(r#"./migrations/postgres/20250916221000_drop_config_table.sql"#)),
|
||||||
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
|
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
|
||||||
("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
|
("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
|
||||||
("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
|
("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
|
||||||
@@ -33,4 +34,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
|||||||
("sqlite", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/sqlite/20250901090000_add_kv_store.sql"#)),
|
("sqlite", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/sqlite/20250901090000_add_kv_store.sql"#)),
|
||||||
("sqlite", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/sqlite/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
|
("sqlite", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/sqlite/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
|
||||||
("sqlite", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/sqlite/20250903200000_add_signatory_amounts.sql"#)),
|
("sqlite", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/sqlite/20250903200000_add_signatory_amounts.sql"#)),
|
||||||
|
("sqlite", "20250916221000_drop_config_table.sql", include_str!(r#"./migrations/sqlite/20250916221000_drop_config_table.sql"#)),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS config;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS config;
|
||||||
@@ -15,7 +15,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bitcoin::bip32::DerivationPath;
|
use bitcoin::bip32::DerivationPath;
|
||||||
use cdk_common::common::QuoteTTL;
|
|
||||||
use cdk_common::database::mint::validate_kvstore_params;
|
use cdk_common::database::mint::validate_kvstore_params;
|
||||||
use cdk_common::database::{
|
use cdk_common::database::{
|
||||||
self, ConversionError, Error, MintDatabase, MintDbWriterFinalizer, MintKeyDatabaseTransaction,
|
self, ConversionError, Error, MintDatabase, MintDbWriterFinalizer, MintKeyDatabaseTransaction,
|
||||||
@@ -33,7 +32,7 @@ use cdk_common::state::check_state_transition;
|
|||||||
use cdk_common::util::unix_time;
|
use cdk_common::util::unix_time;
|
||||||
use cdk_common::{
|
use cdk_common::{
|
||||||
Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
|
Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
|
||||||
MintInfo, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
|
PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
|
||||||
};
|
};
|
||||||
use lightning_invoice::Bolt11Invoice;
|
use lightning_invoice::Bolt11Invoice;
|
||||||
use migrations::MIGRATIONS;
|
use migrations::MIGRATIONS;
|
||||||
@@ -102,26 +101,6 @@ where
|
|||||||
.collect::<Result<HashMap<_, _>, _>>()
|
.collect::<Result<HashMap<_, _>, _>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
async fn set_to_config<C, V>(conn: &C, id: &str, value: &V) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
C: DatabaseExecutor + Send + Sync,
|
|
||||||
V: ?Sized + serde::Serialize,
|
|
||||||
{
|
|
||||||
query(
|
|
||||||
r#"
|
|
||||||
INSERT INTO config (id, value) VALUES (:id, :value)
|
|
||||||
ON CONFLICT(id) DO UPDATE SET value = excluded.value
|
|
||||||
"#,
|
|
||||||
)?
|
|
||||||
.bind("id", id.to_owned())
|
|
||||||
.bind("value", serde_json::to_string(&value)?)
|
|
||||||
.execute(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<RM> SQLMintDatabase<RM>
|
impl<RM> SQLMintDatabase<RM>
|
||||||
where
|
where
|
||||||
RM: DatabasePool + 'static,
|
RM: DatabasePool + 'static,
|
||||||
@@ -145,21 +124,6 @@ where
|
|||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
async fn fetch_from_config<R>(&self, id: &str) -> Result<R, Error>
|
|
||||||
where
|
|
||||||
R: serde::de::DeserializeOwned,
|
|
||||||
{
|
|
||||||
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
|
|
||||||
let value = column_as_string!(query(r#"SELECT value FROM config WHERE id = :id LIMIT 1"#)?
|
|
||||||
.bind("id", id.to_owned())
|
|
||||||
.pluck(&*conn)
|
|
||||||
.await?
|
|
||||||
.ok_or(Error::UnknownQuoteTTL)?);
|
|
||||||
|
|
||||||
Ok(serde_json::from_str(&value)?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -307,18 +271,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<RM> database::MintTransaction<'_, Error> for SQLTransaction<RM>
|
impl<RM> database::MintTransaction<'_, Error> for SQLTransaction<RM> where RM: DatabasePool + 'static
|
||||||
where
|
{}
|
||||||
RM: DatabasePool + 'static,
|
|
||||||
{
|
|
||||||
async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error> {
|
|
||||||
Ok(set_to_config(&self.inner, "mint_info", &mint_info).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
|
||||||
Ok(set_to_config(&self.inner, "quote_ttl", "e_ttl).await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<RM> MintDbWriterFinalizer for SQLTransaction<RM>
|
impl<RM> MintDbWriterFinalizer for SQLTransaction<RM>
|
||||||
@@ -2048,56 +2002,6 @@ where
|
|||||||
|
|
||||||
Ok(Box::new(tx))
|
Ok(Box::new(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_mint_info(&self) -> Result<MintInfo, Error> {
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
METRICS.inc_in_flight_requests("get_mint_info");
|
|
||||||
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
|
|
||||||
let result = self.fetch_from_config("mint_info").await;
|
|
||||||
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
{
|
|
||||||
let success = result.is_ok();
|
|
||||||
|
|
||||||
METRICS.record_mint_operation("get_mint_info", success);
|
|
||||||
METRICS.record_mint_operation_histogram(
|
|
||||||
"get_mint_info",
|
|
||||||
success,
|
|
||||||
start_time.elapsed().as_secs_f64(),
|
|
||||||
);
|
|
||||||
METRICS.dec_in_flight_requests("get_mint_info");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error> {
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
METRICS.inc_in_flight_requests("get_quote_ttl");
|
|
||||||
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
|
|
||||||
let result = self.fetch_from_config("quote_ttl").await;
|
|
||||||
|
|
||||||
#[cfg(feature = "prometheus")]
|
|
||||||
{
|
|
||||||
let success = result.is_ok();
|
|
||||||
|
|
||||||
METRICS.record_mint_operation("get_quote_ttl", success);
|
|
||||||
METRICS.record_mint_operation_histogram(
|
|
||||||
"get_quote_ttl",
|
|
||||||
success,
|
|
||||||
start_time.elapsed().as_secs_f64(),
|
|
||||||
);
|
|
||||||
METRICS.dec_in_flight_requests("get_quote_ttl");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {
|
fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ use cdk_common::MintInfo;
|
|||||||
|
|
||||||
use super::MintSqliteDatabase;
|
use super::MintSqliteDatabase;
|
||||||
|
|
||||||
|
const CDK_MINT_PRIMARY_NAMESPACE: &str = "cdk_mint";
|
||||||
|
const CDK_MINT_CONFIG_SECONDARY_NAMESPACE: &str = "config";
|
||||||
|
const CDK_MINT_CONFIG_KV_KEY: &str = "mint_info";
|
||||||
|
|
||||||
/// Creates a new in-memory [`MintSqliteDatabase`] instance
|
/// Creates a new in-memory [`MintSqliteDatabase`] instance
|
||||||
pub async fn empty() -> Result<MintSqliteDatabase, database::Error> {
|
pub async fn empty() -> Result<MintSqliteDatabase, database::Error> {
|
||||||
#[cfg(not(feature = "sqlcipher"))]
|
#[cfg(not(feature = "sqlcipher"))]
|
||||||
@@ -54,7 +58,14 @@ pub async fn new_with_state(
|
|||||||
|
|
||||||
tx.add_proofs(pending_proofs, None).await?;
|
tx.add_proofs(pending_proofs, None).await?;
|
||||||
tx.add_proofs(spent_proofs, None).await?;
|
tx.add_proofs(spent_proofs, None).await?;
|
||||||
tx.set_mint_info(mint_info).await?;
|
let mint_info_bytes = serde_json::to_vec(&mint_info)?;
|
||||||
|
tx.kv_write(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
&mint_info_bytes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(db)
|
Ok(db)
|
||||||
|
|||||||
@@ -82,6 +82,31 @@ impl MintBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize builder's MintInfo from the database if present.
|
||||||
|
/// If not present or parsing fails, keeps the current MintInfo.
|
||||||
|
pub async fn init_from_db_if_present(&mut self) -> Result<(), cdk_database::Error> {
|
||||||
|
// Attempt to read existing mint_info from the KV store
|
||||||
|
let bytes_opt = self
|
||||||
|
.localstore
|
||||||
|
.kv_read(
|
||||||
|
super::CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
super::CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
super::CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(bytes) = bytes_opt {
|
||||||
|
if let Ok(info) = serde_json::from_slice::<MintInfo>(&bytes) {
|
||||||
|
self.mint_info = info;
|
||||||
|
} else {
|
||||||
|
// If parsing fails, leave the current builder state untouched
|
||||||
|
tracing::warn!("Failed to parse existing mint_info from DB; using builder state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Set blind auth settings
|
/// Set blind auth settings
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
pub fn with_blind_auth(
|
pub fn with_blind_auth(
|
||||||
@@ -128,6 +153,14 @@ impl MintBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a clone of the current MintInfo configured on the builder
|
||||||
|
/// This allows using config-derived settings to initialize persistent state
|
||||||
|
/// before any attempt to read from the database, which avoids first-run
|
||||||
|
/// failures when the DB is empty.
|
||||||
|
pub fn current_mint_info(&self) -> MintInfo {
|
||||||
|
self.mint_info.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Set terms of service URL
|
/// Set terms of service URL
|
||||||
pub fn with_tos_url(mut self, tos_url: String) -> Self {
|
pub fn with_tos_url(mut self, tos_url: String) -> Self {
|
||||||
self.mint_info.tos_url = Some(tos_url);
|
self.mint_info.tos_url = Some(tos_url);
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ impl Mint {
|
|||||||
&self,
|
&self,
|
||||||
mint_quote_request: &MintQuoteRequest,
|
mint_quote_request: &MintQuoteRequest,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mint_info = self.localstore.get_mint_info().await?;
|
let mint_info = self.mint_info().await?;
|
||||||
|
|
||||||
let unit = mint_quote_request.unit();
|
let unit = mint_quote_request.unit();
|
||||||
let amount = mint_quote_request.amount();
|
let amount = mint_quote_request.amount();
|
||||||
@@ -246,7 +246,7 @@ impl Mint {
|
|||||||
|
|
||||||
let payment_options = match mint_quote_request {
|
let payment_options = match mint_quote_request {
|
||||||
MintQuoteRequest::Bolt11(bolt11_request) => {
|
MintQuoteRequest::Bolt11(bolt11_request) => {
|
||||||
let mint_ttl = self.localstore.get_quote_ttl().await?.mint_ttl;
|
let mint_ttl = self.quote_ttl().await?.mint_ttl;
|
||||||
|
|
||||||
let quote_expiry = unix_time() + mint_ttl;
|
let quote_expiry = unix_time() + mint_ttl;
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl Mint {
|
|||||||
request: String,
|
request: String,
|
||||||
options: Option<MeltOptions>,
|
options: Option<MeltOptions>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mint_info = self.localstore.get_mint_info().await?;
|
let mint_info = self.mint_info().await?;
|
||||||
let nut05 = mint_info.nuts.nut05;
|
let nut05 = mint_info.nuts.nut05;
|
||||||
|
|
||||||
ensure_cdk!(!nut05.disabled, Error::MeltingDisabled);
|
ensure_cdk!(!nut05.disabled, Error::MeltingDisabled);
|
||||||
@@ -196,7 +196,7 @@ impl Mint {
|
|||||||
Error::UnsupportedUnit
|
Error::UnsupportedUnit
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let melt_ttl = self.localstore.get_quote_ttl().await?.melt_ttl;
|
let melt_ttl = self.quote_ttl().await?.melt_ttl;
|
||||||
|
|
||||||
let quote = MeltQuote::new(
|
let quote = MeltQuote::new(
|
||||||
MeltPaymentRequest::Bolt11 {
|
MeltPaymentRequest::Bolt11 {
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ pub use builder::{MintBuilder, MintMeltLimits};
|
|||||||
pub use cdk_common::mint::{MeltQuote, MintKeySetInfo, MintQuote};
|
pub use cdk_common::mint::{MeltQuote, MintKeySetInfo, MintQuote};
|
||||||
pub use verification::Verification;
|
pub use verification::Verification;
|
||||||
|
|
||||||
|
const CDK_MINT_PRIMARY_NAMESPACE: &str = "cdk_mint";
|
||||||
|
const CDK_MINT_CONFIG_SECONDARY_NAMESPACE: &str = "config";
|
||||||
|
const CDK_MINT_CONFIG_KV_KEY: &str = "mint_info";
|
||||||
|
const CDK_MINT_QUOTE_TTL_KV_KEY: &str = "quote_ttl";
|
||||||
|
|
||||||
/// Cashu Mint
|
/// Cashu Mint
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Mint {
|
pub struct Mint {
|
||||||
@@ -150,26 +155,60 @@ impl Mint {
|
|||||||
.count()
|
.count()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mint_info = if mint_info.pubkey.is_none() {
|
// Persist missing pubkey early to avoid losing it on next boot and ensure stable identity across restarts
|
||||||
let mut info = mint_info;
|
let mut computed_info = mint_info;
|
||||||
info.pubkey = Some(keysets.pubkey);
|
if computed_info.pubkey.is_none() {
|
||||||
info
|
computed_info.pubkey = Some(keysets.pubkey);
|
||||||
} else {
|
}
|
||||||
mint_info
|
|
||||||
};
|
|
||||||
|
|
||||||
let mint_store = localstore.clone();
|
match localstore
|
||||||
let mut tx = mint_store.begin_transaction().await?;
|
.kv_read(
|
||||||
tx.set_mint_info(mint_info.clone()).await?;
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
tx.set_quote_ttl(QuoteTTL::default()).await?;
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
tx.commit().await?;
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(bytes) => {
|
||||||
|
let mut stored: MintInfo = serde_json::from_slice(&bytes)?;
|
||||||
|
let mut mutated = false;
|
||||||
|
if stored.pubkey.is_none() && computed_info.pubkey.is_some() {
|
||||||
|
stored.pubkey = computed_info.pubkey;
|
||||||
|
mutated = true;
|
||||||
|
}
|
||||||
|
if mutated {
|
||||||
|
let updated = serde_json::to_vec(&stored)?;
|
||||||
|
let mut tx = localstore.begin_transaction().await?;
|
||||||
|
tx.kv_write(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
&updated,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let bytes = serde_json::to_vec(&computed_info)?;
|
||||||
|
let mut tx = localstore.begin_transaction().await?;
|
||||||
|
tx.kv_write(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
&bytes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
signatory,
|
signatory,
|
||||||
pubsub_manager: Arc::new(localstore.clone().into()),
|
pubsub_manager: Arc::new(localstore.clone().into()),
|
||||||
localstore,
|
localstore,
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
oidc_client: mint_info.nuts.nut21.as_ref().map(|nut21| {
|
oidc_client: computed_info.nuts.nut21.as_ref().map(|nut21| {
|
||||||
OidcClient::new(
|
OidcClient::new(
|
||||||
nut21.openid_discovery.clone(),
|
nut21.openid_discovery.clone(),
|
||||||
Some(nut21.client_id.clone()),
|
Some(nut21.client_id.clone()),
|
||||||
@@ -373,7 +412,17 @@ impl Mint {
|
|||||||
/// Get mint info
|
/// Get mint info
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn mint_info(&self) -> Result<MintInfo, Error> {
|
pub async fn mint_info(&self) -> Result<MintInfo, Error> {
|
||||||
let mint_info = self.localstore.get_mint_info().await?;
|
let mint_info = self
|
||||||
|
.localstore
|
||||||
|
.kv_read(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or(Error::CouldNotGetMintInfo)?;
|
||||||
|
|
||||||
|
let mint_info: MintInfo = serde_json::from_slice(&mint_info)?;
|
||||||
|
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
let mint_info = if let Some(auth_db) = self.auth_localstore.as_ref() {
|
let mint_info = if let Some(auth_db) = self.auth_localstore.as_ref() {
|
||||||
@@ -415,27 +464,78 @@ impl Mint {
|
|||||||
/// Set mint info
|
/// Set mint info
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
|
pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
|
||||||
|
tracing::info!("Updating mint info");
|
||||||
|
let mint_info_bytes = serde_json::to_vec(&mint_info)?;
|
||||||
let mut tx = self.localstore.begin_transaction().await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
tx.set_mint_info(mint_info).await?;
|
tx.kv_write(
|
||||||
Ok(tx.commit().await?)
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_KV_KEY,
|
||||||
|
&mint_info_bytes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get quote ttl
|
/// Get quote ttl
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn quote_ttl(&self) -> Result<QuoteTTL, Error> {
|
pub async fn quote_ttl(&self) -> Result<QuoteTTL, Error> {
|
||||||
Ok(self.localstore.get_quote_ttl().await?)
|
let quote_ttl_bytes = self
|
||||||
|
.localstore
|
||||||
|
.kv_read(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_QUOTE_TTL_KV_KEY,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match quote_ttl_bytes {
|
||||||
|
Some(bytes) => {
|
||||||
|
let quote_ttl: QuoteTTL = serde_json::from_slice(&bytes)?;
|
||||||
|
Ok(quote_ttl)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Return default if not found
|
||||||
|
Ok(QuoteTTL::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set quote ttl
|
/// Set quote ttl
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
||||||
|
let quote_ttl_bytes = serde_json::to_vec("e_ttl)?;
|
||||||
let mut tx = self.localstore.begin_transaction().await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
tx.set_quote_ttl(quote_ttl).await?;
|
tx.kv_write(
|
||||||
Ok(tx.commit().await?)
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_QUOTE_TTL_KV_KEY,
|
||||||
|
"e_ttl_bytes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each backend starts a task that waits for any invoice to be paid
|
/// For each backend starts a task that waits for any invoice to be paid
|
||||||
/// Once invoice is paid mint quote status is updated
|
/// Once invoice is paid mint quote status is updated
|
||||||
|
/// Returns true if a QuoteTTL is persisted in the database. This is used to avoid overwriting
|
||||||
|
/// explicit configuration with defaults when the TTL has already been set by an operator.
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn quote_ttl_is_persisted(&self) -> Result<bool, Error> {
|
||||||
|
let quote_ttl_bytes = self
|
||||||
|
.localstore
|
||||||
|
.kv_read(
|
||||||
|
CDK_MINT_PRIMARY_NAMESPACE,
|
||||||
|
CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
|
||||||
|
CDK_MINT_QUOTE_TTL_KV_KEY,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(quote_ttl_bytes.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn wait_for_paid_invoices(
|
async fn wait_for_paid_invoices(
|
||||||
payment_processors: &HashMap<PaymentProcessorKey, DynMintPayment>,
|
payment_processors: &HashMap<PaymentProcessorKey, DynMintPayment>,
|
||||||
@@ -627,13 +727,6 @@ impl Mint {
|
|||||||
return Err(Error::AmountUndefined);
|
return Err(Error::AmountUndefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if mint_quote.payment_method == PaymentMethod::Bolt11
|
|
||||||
&& mint_quote.amount != Some(payment_amount_quote_unit)
|
|
||||||
{
|
|
||||||
tracing::error!("Bolt11 incoming payment should equal mint quote.");
|
|
||||||
return Err(Error::IncorrectQuoteAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Payment received amount in quote unit {} {}",
|
"Payment received amount in quote unit {} {}",
|
||||||
mint_quote.unit,
|
mint_quote.unit,
|
||||||
|
|||||||
Reference in New Issue
Block a user