Refactor mintd used in itest (#616)

This commit is contained in:
thesimplekid
2025-02-25 11:47:17 +00:00
committed by GitHub
parent 692e13ff16
commit 827e4aebde
17 changed files with 546 additions and 578 deletions

View File

@@ -1,33 +0,0 @@
use std::env;
use anyhow::Result;
use cdk::cdk_database::mint_memory::MintMemoryDatabase;
use cdk_integration_tests::init_fake_wallet::start_fake_mint;
use cdk_integration_tests::init_regtest::get_temp_dir;
use cdk_redb::MintRedbDatabase;
use cdk_sqlite::MintSqliteDatabase;
#[tokio::main]
async fn main() -> Result<()> {
let addr = "127.0.0.1";
let port = 8086;
let mint_db_kind = env::var("MINT_DATABASE")?;
match mint_db_kind.as_str() {
"MEMORY" => {
start_fake_mint(addr, port, MintMemoryDatabase::default()).await?;
}
"SQLITE" => {
let sqlite_db = MintSqliteDatabase::new(&get_temp_dir().join("mint")).await?;
sqlite_db.migrate().await;
start_fake_mint(addr, port, sqlite_db).await?;
}
"REDB" => {
let redb_db = MintRedbDatabase::new(&get_temp_dir().join("mint")).unwrap();
start_fake_mint(addr, port, redb_db).await?;
}
_ => panic!("Unknown mint db type: {}", mint_db_kind),
};
Ok(())
}

View File

@@ -1,209 +0,0 @@
use std::env;
use anyhow::Result;
use cdk::cdk_database::mint_memory::MintMemoryDatabase;
use cdk_integration_tests::init_regtest::{
create_cln_backend, create_lnd_backend, create_mint, fund_ln, generate_block, get_bitcoin_dir,
get_cln_dir, get_lnd_cert_file_path, get_lnd_dir, get_lnd_macaroon_path, get_temp_dir,
init_bitcoin_client, init_bitcoind, init_lnd, open_channel, BITCOIN_RPC_PASS, BITCOIN_RPC_USER,
LND_ADDR, LND_RPC_ADDR, LND_TWO_ADDR, LND_TWO_RPC_ADDR,
};
use cdk_redb::MintRedbDatabase;
use cdk_sqlite::MintSqliteDatabase;
use ln_regtest_rs::cln::Clnd;
use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
use tracing_subscriber::EnvFilter;
const CLN_ADDR: &str = "127.0.0.1:19846";
const CLN_TWO_ADDR: &str = "127.0.0.1:19847";
#[tokio::main]
async fn main() -> Result<()> {
let default_filter = "debug";
let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";
let h2_filter = "h2=warn";
let rustls_filter = "rustls=warn";
let env_filter = EnvFilter::new(format!(
"{},{},{},{},{}",
default_filter, sqlx_filter, hyper_filter, h2_filter, rustls_filter
));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let mut bitcoind = init_bitcoind();
bitcoind.start_bitcoind()?;
let bitcoin_client = init_bitcoin_client()?;
bitcoin_client.create_wallet().ok();
bitcoin_client.load_wallet()?;
let new_add = bitcoin_client.get_new_address()?;
bitcoin_client.generate_blocks(&new_add, 200).unwrap();
let cln_one_dir = get_cln_dir("one");
let mut clnd = Clnd::new(
get_bitcoin_dir(),
cln_one_dir.clone(),
CLN_ADDR.into(),
BITCOIN_RPC_USER.to_string(),
BITCOIN_RPC_PASS.to_string(),
);
clnd.start_clnd()?;
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
cln_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &cln_client).await.unwrap();
// Create second cln
let cln_two_dir = get_cln_dir("two");
let mut clnd_two = Clnd::new(
get_bitcoin_dir(),
cln_two_dir.clone(),
CLN_TWO_ADDR.into(),
BITCOIN_RPC_USER.to_string(),
BITCOIN_RPC_PASS.to_string(),
);
clnd_two.start_clnd()?;
let cln_two_client = ClnClient::new(cln_two_dir.clone(), None).await?;
cln_two_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &cln_two_client).await.unwrap();
let lnd_dir = get_lnd_dir("one");
println!("{}", lnd_dir.display());
let mut lnd = init_lnd(lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
lnd.start_lnd().unwrap();
tracing::info!("Started lnd node");
let lnd_client = LndClient::new(
format!("https://{}", LND_RPC_ADDR),
get_lnd_cert_file_path(&lnd_dir),
get_lnd_macaroon_path(&lnd_dir),
)
.await?;
lnd_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &lnd_client).await.unwrap();
// create second lnd node
let lnd_two_dir = get_lnd_dir("two");
let mut lnd_two = init_lnd(lnd_two_dir.clone(), LND_TWO_ADDR, LND_TWO_RPC_ADDR).await;
lnd_two.start_lnd().unwrap();
tracing::info!("Started second lnd node");
let lnd_two_client = LndClient::new(
format!("https://{}", LND_TWO_RPC_ADDR),
get_lnd_cert_file_path(&lnd_two_dir),
get_lnd_macaroon_path(&lnd_two_dir),
)
.await?;
lnd_two_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &lnd_two_client).await.unwrap();
// Open channels concurrently
// Open channels
{
open_channel(&cln_client, &lnd_client).await.unwrap();
tracing::info!("Opened channel between cln and lnd one");
generate_block(&bitcoin_client)?;
// open_channel(&bitcoin_client, &cln_client, &cln_two_client)
// .await
// .unwrap();
// tracing::info!("Opened channel between cln and cln two");
open_channel(&lnd_client, &lnd_two_client).await.unwrap();
tracing::info!("Opened channel between lnd and lnd two");
generate_block(&bitcoin_client)?;
// open_channel(&cln_client, &lnd_two_client).await.unwrap();
// tracing::info!("Opened channel between cln and lnd two");
open_channel(&cln_two_client, &lnd_client).await.unwrap();
tracing::info!("Opened channel between cln two and lnd");
generate_block(&bitcoin_client)?;
open_channel(&cln_client, &lnd_two_client).await.unwrap();
tracing::info!("Opened channel between cln and lnd two");
generate_block(&bitcoin_client)?;
cln_client.wait_channels_active().await?;
cln_two_client.wait_channels_active().await?;
lnd_client.wait_channels_active().await?;
lnd_two_client.wait_channels_active().await?;
}
let mint_addr = "127.0.0.1";
let cln_mint_port = 8085;
let mint_db_kind = env::var("MINT_DATABASE")?;
let lnd_mint_db_path = get_temp_dir().join("lnd_mint");
let cln_mint_db_path = get_temp_dir().join("cln_mint");
let cln_backend = create_cln_backend(&cln_client).await?;
let lnd_mint_port = 8087;
let lnd_backend = create_lnd_backend(&lnd_two_client).await?;
match mint_db_kind.as_str() {
"MEMORY" => {
tokio::spawn(async move {
create_mint(
mint_addr,
cln_mint_port,
MintMemoryDatabase::default(),
cln_backend,
)
.await
.expect("Could not start cln mint");
});
create_mint(
mint_addr,
lnd_mint_port,
MintMemoryDatabase::default(),
lnd_backend,
)
.await?;
}
"SQLITE" => {
tokio::spawn(async move {
let cln_sqlite_db = MintSqliteDatabase::new(&cln_mint_db_path)
.await
.expect("Could not create CLN mint db");
cln_sqlite_db.migrate().await;
create_mint(mint_addr, cln_mint_port, cln_sqlite_db, cln_backend)
.await
.expect("Could not start cln mint");
});
let lnd_sqlite_db = MintSqliteDatabase::new(&lnd_mint_db_path).await?;
lnd_sqlite_db.migrate().await;
create_mint(mint_addr, lnd_mint_port, lnd_sqlite_db, lnd_backend).await?;
}
"REDB" => {
tokio::spawn(async move {
let cln_redb_db = MintRedbDatabase::new(&cln_mint_db_path).unwrap();
create_mint(mint_addr, cln_mint_port, cln_redb_db, cln_backend)
.await
.expect("Could not start cln mint");
});
let lnd_redb_db = MintRedbDatabase::new(&lnd_mint_db_path).unwrap();
create_mint(mint_addr, lnd_mint_port, lnd_redb_db, lnd_backend).await?;
}
_ => panic!("Unknown mint db type: {}", mint_db_kind),
};
Ok(())
}

View File

@@ -0,0 +1,63 @@
use std::fs::OpenOptions;
use std::io::Write;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{bail, Result};
use cdk_integration_tests::init_regtest::{get_temp_dir, start_regtest_end};
use tokio::signal;
use tokio::sync::{oneshot, Notify};
use tokio::time::timeout;
use tracing_subscriber::EnvFilter;
fn signal_progress() {
let temp_dir = get_temp_dir();
let mut pipe = OpenOptions::new()
.write(true)
.open(temp_dir.join("progress_pipe"))
.expect("Failed to open pipe");
pipe.write_all(b"checkpoint1\n")
.expect("Failed to write to pipe");
}
#[tokio::main]
async fn main() -> Result<()> {
let default_filter = "debug";
let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";
let h2_filter = "h2=warn";
let rustls_filter = "rustls=warn";
let env_filter = EnvFilter::new(format!(
"{},{},{},{},{}",
default_filter, sqlx_filter, hyper_filter, h2_filter, rustls_filter
));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let shutdown_regtest = Arc::new(Notify::new());
let shutdown_clone = shutdown_regtest.clone();
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
start_regtest_end(tx, shutdown_clone)
.await
.expect("Error starting regtest");
});
match timeout(Duration::from_secs(300), rx).await {
Ok(_) => {
tracing::info!("Regtest set up");
signal_progress();
}
Err(_) => {
tracing::error!("regtest setup timed out after 5 minutes");
bail!("Could not set up regtest");
}
}
signal::ctrl_c().await?;
Ok(())
}

View File

@@ -1,83 +0,0 @@
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use anyhow::Result;
use bip39::Mnemonic;
use cdk::cdk_database::{self, MintDatabase};
use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits};
use cdk::nuts::{CurrencyUnit, PaymentMethod};
use cdk::types::QuoteTTL;
use cdk_fake_wallet::FakeWallet;
use tracing_subscriber::EnvFilter;
use crate::init_mint::start_mint;
pub async fn start_fake_mint<D>(addr: &str, port: u16, database: D) -> Result<()>
where
D: MintDatabase<Err = cdk_database::Error> + Send + Sync + 'static,
{
let default_filter = "debug";
let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";
let env_filter = EnvFilter::new(format!(
"{},{},{}",
default_filter, sqlx_filter, hyper_filter
));
// Parse input
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let fee_reserve = FeeReserve {
min_fee_reserve: 1.into(),
percent_fee_reserve: 0.0,
};
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
let mut mint_builder = MintBuilder::new();
let localstore = Arc::new(database);
mint_builder = mint_builder.with_localstore(localstore.clone());
mint_builder = mint_builder.add_ln_backend(
CurrencyUnit::Sat,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 5_000),
Arc::new(fake_wallet),
);
let fee_reserve = FeeReserve {
min_fee_reserve: 1.into(),
percent_fee_reserve: 0.0,
};
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
mint_builder = mint_builder.add_ln_backend(
CurrencyUnit::Usd,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 5_000),
Arc::new(fake_wallet),
);
let mnemonic = Mnemonic::generate(12)?;
mint_builder = mint_builder
.with_name("fake test mint".to_string())
.with_description("fake test mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
localstore
.set_mint_info(mint_builder.mint_info.clone())
.await?;
let quote_ttl = QuoteTTL::new(10000, 10000);
localstore.set_quote_ttl(quote_ttl).await?;
let mint = mint_builder.build().await?;
start_mint(addr, port, mint).await?;
Ok(())
}

View File

@@ -1,37 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use axum::Router;
use cdk::mint::Mint;
use tokio::sync::Notify;
use tower_http::cors::CorsLayer;
use tracing::instrument;
#[instrument(skip_all)]
pub async fn start_mint(addr: &str, port: u16, mint: Mint) -> Result<()> {
let mint_arc = Arc::new(mint);
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc))
.await
.unwrap();
let mint_service = Router::new()
.merge(v1_service)
.layer(CorsLayer::permissive());
let mint = Arc::clone(&mint_arc);
let shutdown = Arc::new(Notify::new());
tokio::spawn({
let shutdown = Arc::clone(&shutdown);
async move { mint.wait_for_paid_invoices(shutdown).await }
});
tracing::info!("Staring Axum server");
axum::Server::bind(&format!("{}:{}", addr, port).as_str().parse().unwrap())
.serve(mint_service.into_make_service())
.await?;
Ok(())
}

View File

@@ -3,21 +3,16 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Result;
use bip39::Mnemonic;
use cdk::cdk_database::{self, MintDatabase};
use cdk::cdk_lightning::{self, MintLightning};
use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits};
use cdk::nuts::{CurrencyUnit, PaymentMethod};
use cdk::types::QuoteTTL;
use cdk::mint::FeeReserve;
use cdk_cln::Cln as CdkCln;
use cdk_lnd::Lnd as CdkLnd;
use ln_regtest_rs::bitcoin_client::BitcoinClient;
use ln_regtest_rs::bitcoind::Bitcoind;
use ln_regtest_rs::cln::Clnd;
use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
use ln_regtest_rs::lnd::Lnd;
use tracing::instrument;
use crate::init_mint::start_mint;
use tokio::sync::oneshot::Sender;
use tokio::sync::Notify;
pub const BITCOIND_ADDR: &str = "127.0.0.1:18443";
pub const ZMQ_RAW_BLOCK: &str = "tcp://127.0.0.1:28332";
@@ -33,6 +28,9 @@ pub const LND_RPC_ADDR: &str = "localhost:10009";
pub const LND_TWO_ADDR: &str = "0.0.0.0:18410";
pub const LND_TWO_RPC_ADDR: &str = "localhost:10010";
pub const CLN_ADDR: &str = "127.0.0.1:19846";
pub const CLN_TWO_ADDR: &str = "127.0.0.1:19847";
pub fn get_mint_addr() -> String {
env::var("cdk_itests_mint_addr").expect("Temp dir set")
}
@@ -149,43 +147,6 @@ pub async fn create_lnd_backend(lnd_client: &LndClient) -> Result<CdkLnd> {
.await?)
}
#[instrument(skip_all)]
pub async fn create_mint<D, L>(addr: &str, port: u16, database: D, lighting: L) -> Result<()>
where
D: MintDatabase<Err = cdk_database::Error> + Send + Sync + 'static,
L: MintLightning<Err = cdk_lightning::Error> + Send + Sync + 'static,
{
let mut mint_builder = MintBuilder::new();
let localstore = Arc::new(database);
mint_builder = mint_builder.with_localstore(localstore.clone());
mint_builder = mint_builder.add_ln_backend(
CurrencyUnit::Sat,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 5_000),
Arc::new(lighting),
);
let mnemonic = Mnemonic::generate(12)?;
mint_builder = mint_builder
.with_name("regtest mint".to_string())
.with_description("regtest mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
let mint = mint_builder.build().await?;
localstore
.set_mint_info(mint_builder.mint_info.clone())
.await?;
let quote_ttl = QuoteTTL::new(10000, 10000);
localstore.set_quote_ttl(quote_ttl).await?;
start_mint(addr, port, mint).await?;
Ok(())
}
pub async fn fund_ln<C>(bitcoin_client: &BitcoinClient, ln_client: &C) -> Result<()>
where
C: LightningClient,
@@ -230,3 +191,123 @@ where
Ok(())
}
pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyhow::Result<()> {
let mut bitcoind = init_bitcoind();
bitcoind.start_bitcoind()?;
let bitcoin_client = init_bitcoin_client()?;
bitcoin_client.create_wallet().ok();
bitcoin_client.load_wallet()?;
let new_add = bitcoin_client.get_new_address()?;
bitcoin_client.generate_blocks(&new_add, 200).unwrap();
let cln_one_dir = get_cln_dir("one");
let mut clnd = Clnd::new(
get_bitcoin_dir(),
cln_one_dir.clone(),
CLN_ADDR.into(),
BITCOIN_RPC_USER.to_string(),
BITCOIN_RPC_PASS.to_string(),
);
clnd.start_clnd()?;
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
cln_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &cln_client).await.unwrap();
// Create second cln
let cln_two_dir = get_cln_dir("two");
let mut clnd_two = Clnd::new(
get_bitcoin_dir(),
cln_two_dir.clone(),
CLN_TWO_ADDR.into(),
BITCOIN_RPC_USER.to_string(),
BITCOIN_RPC_PASS.to_string(),
);
clnd_two.start_clnd()?;
let cln_two_client = ClnClient::new(cln_two_dir.clone(), None).await?;
cln_two_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &cln_two_client).await.unwrap();
let lnd_dir = get_lnd_dir("one");
println!("{}", lnd_dir.display());
let mut lnd = init_lnd(lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
lnd.start_lnd().unwrap();
tracing::info!("Started lnd node");
let lnd_client = LndClient::new(
format!("https://{}", LND_RPC_ADDR),
get_lnd_cert_file_path(&lnd_dir),
get_lnd_macaroon_path(&lnd_dir),
)
.await?;
lnd_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &lnd_client).await.unwrap();
// create second lnd node
let lnd_two_dir = get_lnd_dir("two");
let mut lnd_two = init_lnd(lnd_two_dir.clone(), LND_TWO_ADDR, LND_TWO_RPC_ADDR).await;
lnd_two.start_lnd().unwrap();
tracing::info!("Started second lnd node");
let lnd_two_client = LndClient::new(
format!("https://{}", LND_TWO_RPC_ADDR),
get_lnd_cert_file_path(&lnd_two_dir),
get_lnd_macaroon_path(&lnd_two_dir),
)
.await?;
lnd_two_client.wait_chain_sync().await.unwrap();
fund_ln(&bitcoin_client, &lnd_two_client).await.unwrap();
// Open channels concurrently
// Open channels
{
open_channel(&cln_client, &lnd_client).await.unwrap();
tracing::info!("Opened channel between cln and lnd one");
generate_block(&bitcoin_client)?;
// open_channel(&bitcoin_client, &cln_client, &cln_two_client)
// .await
// .unwrap();
// tracing::info!("Opened channel between cln and cln two");
open_channel(&lnd_client, &lnd_two_client).await.unwrap();
tracing::info!("Opened channel between lnd and lnd two");
generate_block(&bitcoin_client)?;
// open_channel(&cln_client, &lnd_two_client).await.unwrap();
// tracing::info!("Opened channel between cln and lnd two");
open_channel(&cln_two_client, &lnd_client).await.unwrap();
tracing::info!("Opened channel between cln two and lnd");
generate_block(&bitcoin_client)?;
open_channel(&cln_client, &lnd_two_client).await.unwrap();
tracing::info!("Opened channel between cln and lnd two");
generate_block(&bitcoin_client)?;
cln_client.wait_channels_active().await?;
cln_two_client.wait_channels_active().await?;
lnd_client.wait_channels_active().await?;
lnd_two_client.wait_channels_active().await?;
}
// Send notification that regtest set up is complete
sender.send(()).expect("Could not send oneshot");
// Wait until we are told to shutdown
// If we return the bitcoind, lnd, and cln will be dropped and shutdown
notify.notified().await;
Ok(())
}

View File

@@ -1,24 +1,13 @@
use std::str::FromStr;
use std::sync::Arc;
use anyhow::{bail, Result};
use cdk::amount::{Amount, SplitTarget};
use cdk::dhke::construct_proofs;
use cdk::mint_url::MintUrl;
use cdk::nuts::nut00::ProofsMethods;
use cdk::nuts::nut17::Params;
use cdk::nuts::{
CurrencyUnit, Id, KeySet, MintBolt11Request, MintQuoteBolt11Request, MintQuoteState,
NotificationPayload, PreMintSecrets, Proofs, State,
};
use cdk::wallet::client::{HttpClient, MintConnector};
use cdk::wallet::subscription::SubscriptionManager;
use cdk::nuts::{MintQuoteState, NotificationPayload, State};
use cdk::wallet::WalletSubscription;
use cdk::Wallet;
use tokio::time::{timeout, Duration};
use tokio::time::{sleep, timeout, Duration};
pub mod init_fake_wallet;
pub mod init_mint;
pub mod init_pure_tests;
pub mod init_regtest;
@@ -30,19 +19,7 @@ pub async fn wallet_mint(
) -> Result<()> {
let quote = wallet.mint_quote(amount, description).await?;
let mut subscription = wallet
.subscribe(WalletSubscription::Bolt11MintQuoteState(vec![quote
.id
.clone()]))
.await;
while let Some(msg) = subscription.recv().await {
if let NotificationPayload::MintQuoteBolt11Response(response) = msg {
if response.state == MintQuoteState::Paid {
break;
}
}
}
wait_for_mint_to_be_paid(&wallet, &quote.id, 60).await?;
let proofs = wallet.mint(&quote.id, split_target, None).await?;
@@ -53,70 +30,6 @@ pub async fn wallet_mint(
Ok(())
}
pub async fn mint_proofs(
mint_url: &str,
amount: Amount,
keyset_id: Id,
mint_keys: &KeySet,
description: Option<String>,
) -> anyhow::Result<Proofs> {
println!("Minting for ecash");
println!();
let wallet_client = HttpClient::new(MintUrl::from_str(mint_url)?);
let request = MintQuoteBolt11Request {
amount,
unit: CurrencyUnit::Sat,
description,
pubkey: None,
};
let mint_quote = wallet_client.post_mint_quote(request).await?;
println!("Please pay: {}", mint_quote.request);
let subscription_client = SubscriptionManager::new(Arc::new(wallet_client.clone()));
let mut subscription = subscription_client
.subscribe(
mint_url.parse()?,
Params {
filters: vec![mint_quote.quote.clone()],
kind: cdk::nuts::nut17::Kind::Bolt11MintQuote,
id: "sub".into(),
},
)
.await;
while let Some(msg) = subscription.recv().await {
if let NotificationPayload::MintQuoteBolt11Response(response) = msg {
if response.state == MintQuoteState::Paid {
break;
}
}
}
let premint_secrets = PreMintSecrets::random(keyset_id, amount, &SplitTarget::default())?;
let request = MintBolt11Request {
quote: mint_quote.quote,
outputs: premint_secrets.blinded_messages(),
signature: None,
};
let mint_response = wallet_client.post_mint(request).await?;
let pre_swap_proofs = construct_proofs(
mint_response.signatures,
premint_secrets.rs(),
premint_secrets.secrets(),
&mint_keys.clone().keys,
)?;
Ok(pre_swap_proofs)
}
// Get all pending from wallet and attempt to swap
// Will panic if there are no pending
// Will return Ok if swap fails as expected
@@ -154,6 +67,7 @@ pub async fn attempt_to_swap_pending(wallet: &Wallet) -> Result<()> {
Ok(())
}
#[allow(clippy::incompatible_msrv)]
pub async fn wait_for_mint_to_be_paid(
wallet: &Wallet,
mint_quote_id: &str,
@@ -164,7 +78,6 @@ pub async fn wait_for_mint_to_be_paid(
mint_quote_id.to_owned(),
]))
.await;
// Create the timeout future
let wait_future = async {
while let Some(msg) = subscription.recv().await {
@@ -177,9 +90,36 @@ pub async fn wait_for_mint_to_be_paid(
Ok(())
};
// Wait for either the payment to complete or timeout
match timeout(Duration::from_secs(timeout_secs), wait_future).await {
Ok(result) => result,
Err(_) => Err(anyhow::anyhow!("Timeout waiting for mint quote to be paid")),
let timeout_future = timeout(Duration::from_secs(timeout_secs), wait_future);
let check_interval = Duration::from_secs(5);
let periodic_task = async {
loop {
match wallet.mint_quote_state(mint_quote_id).await {
Ok(result) => {
if result.state == MintQuoteState::Paid {
tracing::info!("mint quote paid via poll");
return Ok(());
}
}
Err(e) => {
tracing::error!("Could not check mint quote status: {:?}", e);
}
}
sleep(check_interval).await;
}
};
tokio::select! {
result = timeout_future => {
match result {
Ok(payment_result) => payment_result,
Err(_) => Err(anyhow::anyhow!("Timeout waiting for mint quote to be paid")),
}
}
result = periodic_task => {
result // Now propagates the result from periodic checks
}
}
}

View File

@@ -377,6 +377,41 @@ async fn test_fake_melt_change_in_quote() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_database_type() -> Result<()> {
// Get the database type and work dir from environment
let db_type = std::env::var("MINT_DATABASE").expect("MINT_DATABASE env var should be set");
let work_dir =
std::env::var("CDK_MINTD_WORK_DIR").expect("CDK_MINTD_WORK_DIR env var should be set");
// Check that the correct database file exists
match db_type.as_str() {
"REDB" => {
let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.redb");
assert!(
db_path.exists(),
"Expected redb database file to exist at {:?}",
db_path
);
}
"SQLITE" => {
let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.sqlite");
assert!(
db_path.exists(),
"Expected sqlite database file to exist at {:?}",
db_path
);
}
"MEMORY" => {
// Memory database has no file to check
println!("Memory database in use - no file to check");
}
_ => bail!("Unknown database type: {}", db_type),
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_mint_with_witness() -> Result<()> {
let wallet = Wallet::new(
@@ -803,6 +838,7 @@ async fn test_fake_mint_multiple_unit_melt() -> Result<()> {
};
let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_melt(melt_request.clone()).await;
match response {

View File

@@ -14,7 +14,8 @@ use cdk::nuts::{
PreMintSecrets, State,
};
use cdk::wallet::client::{HttpClient, MintConnector};
use cdk::wallet::{Wallet, WalletSubscription};
use cdk::wallet::Wallet;
use cdk::WalletSubscription;
use cdk_integration_tests::init_regtest::{
get_cln_dir, get_lnd_cert_file_path, get_lnd_dir, get_lnd_macaroon_path, get_mint_port,
get_mint_url, get_mint_ws_url, LND_RPC_ADDR, LND_TWO_RPC_ADDR,
@@ -140,7 +141,8 @@ async fn test_regtest_mint_melt_round_trip() -> Result<()> {
NotificationPayload::MeltQuoteBolt11Response(melt) => melt,
_ => panic!("Wrong payload"),
};
assert_eq!(payload.amount + payload.fee_reserve, 100.into());
assert_eq!(payload.amount + payload.fee_reserve, 50.into());
assert_eq!(payload.quote.to_string(), melt.id);
assert_eq!(payload.state, MeltQuoteState::Unpaid);
@@ -151,7 +153,7 @@ async fn test_regtest_mint_melt_round_trip() -> Result<()> {
NotificationPayload::MeltQuoteBolt11Response(melt) => melt,
_ => panic!("Wrong payload"),
};
assert_eq!(payload.amount + payload.fee_reserve, 100.into());
assert_eq!(payload.amount + payload.fee_reserve, 50.into());
assert_eq!(payload.quote.to_string(), melt.id);
assert_eq!(payload.state, MeltQuoteState::Paid);
@@ -422,19 +424,7 @@ async fn test_cached_mint() -> Result<()> {
let quote = wallet.mint_quote(mint_amount, None).await?;
lnd_client.pay_invoice(quote.request).await?;
let mut subscription = wallet
.subscribe(WalletSubscription::Bolt11MintQuoteState(vec![quote
.id
.clone()]))
.await;
while let Some(msg) = subscription.recv().await {
if let NotificationPayload::MintQuoteBolt11Response(response) = msg {
if response.state == MintQuoteState::Paid {
break;
}
}
}
wait_for_mint_to_be_paid(&wallet, &quote.id, 60).await?;
let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
let http_client = HttpClient::new(get_mint_url("0").as_str().parse()?);
@@ -458,6 +448,59 @@ async fn test_cached_mint() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_websocket_connection() -> Result<()> {
let wallet = Wallet::new(
&get_mint_url("0"),
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
// Create a small mint quote to test notifications
let mint_quote = wallet.mint_quote(10.into(), None).await?;
// Subscribe to notifications for this quote
let mut subscription = wallet
.subscribe(WalletSubscription::Bolt11MintQuoteState(vec![mint_quote
.id
.clone()]))
.await;
// First check we get the unpaid state
let msg = timeout(Duration::from_secs(10), subscription.recv())
.await
.expect("timeout waiting for unpaid notification")
.ok_or_else(|| anyhow::anyhow!("No unpaid notification received"))?;
match msg {
NotificationPayload::MintQuoteBolt11Response(response) => {
assert_eq!(response.quote.to_string(), mint_quote.id);
assert_eq!(response.state, MintQuoteState::Unpaid);
}
_ => bail!("Unexpected notification type"),
}
let lnd_client = init_lnd_client().await;
lnd_client.pay_invoice(mint_quote.request).await?;
// Wait for paid notification with 10 second timeout
let msg = timeout(Duration::from_secs(10), subscription.recv())
.await
.expect("timeout waiting for paid notification")
.ok_or_else(|| anyhow::anyhow!("No paid notification received"))?;
match msg {
NotificationPayload::MintQuoteBolt11Response(response) => {
assert_eq!(response.quote.to_string(), mint_quote.id);
assert_eq!(response.state, MintQuoteState::Paid);
Ok(())
}
_ => bail!("Unexpected notification type"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_multimint_melt() -> Result<()> {
let lnd_client = init_lnd_client().await;
@@ -482,26 +525,14 @@ async fn test_multimint_melt() -> Result<()> {
// Fund the wallets
let quote = wallet1.mint_quote(mint_amount, None).await?;
lnd_client.pay_invoice(quote.request.clone()).await?;
loop {
let quote_status = wallet1.mint_quote_state(&quote.id).await?;
if quote_status.state == MintQuoteState::Paid {
break;
}
tracing::debug!("Quote not yet paid");
}
wait_for_mint_to_be_paid(&wallet1, &quote.id, 60).await?;
wallet1
.mint(&quote.id, SplitTarget::default(), None)
.await?;
let quote = wallet2.mint_quote(mint_amount, None).await?;
lnd_client.pay_invoice(quote.request.clone()).await?;
loop {
let quote_status = wallet2.mint_quote_state(&quote.id).await?;
if quote_status.state == MintQuoteState::Paid {
break;
}
tracing::debug!("Quote not yet paid");
}
wait_for_mint_to_be_paid(&wallet2, &quote.id, 60).await?;
wallet2
.mint(&quote.id, SplitTarget::default(), None)
.await?;
@@ -538,3 +569,38 @@ async fn test_multimint_melt() -> Result<()> {
assert!(result1.state == MeltQuoteState::Paid);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_database_type() -> Result<()> {
// Get the database type and work dir from environment
let db_type = std::env::var("MINT_DATABASE").expect("MINT_DATABASE env var should be set");
let work_dir =
std::env::var("CDK_MINTD_WORK_DIR").expect("CDK_MINTD_WORK_DIR env var should be set");
// Check that the correct database file exists
match db_type.as_str() {
"REDB" => {
let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.redb");
assert!(
db_path.exists(),
"Expected redb database file to exist at {:?}",
db_path
);
}
"SQLITE" => {
let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.sqlite");
assert!(
db_path.exists(),
"Expected sqlite database file to exist at {:?}",
db_path
);
}
"MEMORY" => {
// Memory database has no file to check
println!("Memory database in use - no file to check");
}
_ => bail!("Unknown database type: {}", db_type),
}
Ok(())
}