mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
Mintd lib (#914)
* feat(cdk-integration-tests): refactor regtest setup and mintd integration - Replace shell-based regtest setup with Rust binary (start_regtest_mints) - Add cdk-mintd crate to workspace and integration tests - Improve environment variable handling for test configurations - Update integration tests to use proper temp directory management - Remove deprecated start_regtest.rs binary - Enhance CLN client connection with retry logic - Simplify regtest shell script (itests.sh) to use new binary - Fix tracing filters and improve error handling in setup - Update dependencies and configurations for integration tests fix: killing chore: comment tests for ci debugging chore: compile Revert "chore: comment tests for ci debugging" This reverts commit bfc594c11cf37caeaa6445cb854ae5567d2da6bd. * chore: sql cipher * fix: removal of sqlite cipher * fix: auth password * refactor(cdk-mintd): improve database password handling and function signatures - Pass database password as parameter instead of parsing CLI args in setup_database - Update function signatures for run_mintd and run_mintd_with_shutdown to accept db_password - Remove direct CLI parsing from database setup logic - Fix auth database initialization to use correct type when sqlcipher feature enabled
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -14,6 +14,10 @@
|
|||||||
- cashu: NUT-19 support in the wallet ([crodas]).
|
- cashu: NUT-19 support in the wallet ([crodas]).
|
||||||
- cdk: SIG_ALL support for swap and melt operations ([thesimplekid]).
|
- cdk: SIG_ALL support for swap and melt operations ([thesimplekid]).
|
||||||
- cdk-sql-common: Add cache to SQL statements for better performance ([crodas]).
|
- cdk-sql-common: Add cache to SQL statements for better performance ([crodas]).
|
||||||
|
- cdk-integration-tests: New binary `start_fake_auth_mint` for testing fake mint with authentication ([thesimplekid]).
|
||||||
|
- 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]).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- cdk: Refactored wallet keyset management methods for better clarity and separation of concerns ([thesimplekid]).
|
- cdk: Refactored wallet keyset management methods for better clarity and separation of concerns ([thesimplekid]).
|
||||||
@@ -28,6 +32,12 @@
|
|||||||
- cdk-integration-tests: Updated test utilities to use new mint lifecycle management ([thesimplekid]).
|
- cdk-integration-tests: Updated test utilities to use new mint lifecycle management ([thesimplekid]).
|
||||||
- cdk-sqlite: Introduce `cdk-sql-common` crate for shared SQL storage codebase ([crodas]).
|
- cdk-sqlite: Introduce `cdk-sql-common` crate for shared SQL storage codebase ([crodas]).
|
||||||
- cdk-sqlite: Rename `still_active` to `stale` for better clarity ([crodas]).
|
- cdk-sqlite: Rename `still_active` to `stale` for better clarity ([crodas]).
|
||||||
|
- cdk-integration-tests: Refactored regtest setup to use Rust binaries instead of shell scripts ([thesimplekid]).
|
||||||
|
- cdk-integration-tests: Improved environment variable handling for test configurations ([thesimplekid]).
|
||||||
|
- cdk-integration-tests: Enhanced CLN client connection with retry logic ([thesimplekid]).
|
||||||
|
- cdk-integration-tests: Updated integration tests to use proper temp directory management ([thesimplekid]).
|
||||||
|
- cdk-integration-tests: Simplified regtest shell scripts to use new binaries ([thesimplekid]).
|
||||||
|
- crates/cdk-mintd: Moved mintd library functions to separate module for better organization and testability ([thesimplekid]).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- cashu: Fixed CurrencyUnit custom units preserving original case instead of being converted to uppercase ([thesimplekid]).
|
- cashu: Fixed CurrencyUnit custom units preserving original case instead of being converted to uppercase ([thesimplekid]).
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ cdk-redb = { path = "./crates/cdk-redb", default-features = true, version = "=0.
|
|||||||
cdk-sql-common = { path = "./crates/cdk-sql-common", default-features = true, version = "=0.11.0" }
|
cdk-sql-common = { path = "./crates/cdk-sql-common", default-features = true, version = "=0.11.0" }
|
||||||
cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version = "=0.11.0" }
|
cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version = "=0.11.0" }
|
||||||
cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.11.0", default-features = false }
|
cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.11.0", default-features = false }
|
||||||
|
cdk-mintd = { path = "./crates/cdk-mintd", version = "=0.11.0", default-features = false }
|
||||||
clap = { version = "4.5.31", features = ["derive"] }
|
clap = { version = "4.5.31", features = ["derive"] }
|
||||||
ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
|
ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
|
||||||
cbor-diag = "0.1.12"
|
cbor-diag = "0.1.12"
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ cashu = { workspace = true, features = ["mint", "wallet"] }
|
|||||||
cdk = { workspace = true, features = ["mint", "wallet", "auth"] }
|
cdk = { workspace = true, features = ["mint", "wallet", "auth"] }
|
||||||
cdk-cln = { workspace = true }
|
cdk-cln = { workspace = true }
|
||||||
cdk-lnd = { workspace = true }
|
cdk-lnd = { workspace = true }
|
||||||
cdk-axum = { workspace = true }
|
cdk-axum = { workspace = true, features = ["auth"] }
|
||||||
cdk-sqlite = { workspace = true }
|
cdk-sqlite = { workspace = true }
|
||||||
cdk-redb = { workspace = true }
|
cdk-redb = { workspace = true }
|
||||||
cdk-fake-wallet = { workspace = true }
|
cdk-fake-wallet = { workspace = true }
|
||||||
|
cdk-common = { workspace = true, features = ["mint", "wallet", "auth"] }
|
||||||
|
cdk-mintd = { workspace = true, features = ["cln", "lnd", "fakewallet", "grpc-processor", "auth", "lnbits", "management-rpc"] }
|
||||||
futures = { workspace = true, default-features = false, features = [
|
futures = { workspace = true, default-features = false, features = [
|
||||||
"executor",
|
"executor",
|
||||||
] }
|
] }
|
||||||
@@ -44,6 +46,7 @@ tower-http = { workspace = true, features = ["cors"] }
|
|||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
bitcoin = "0.32.0"
|
bitcoin = "0.32.0"
|
||||||
|
clap = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
163
crates/cdk-integration-tests/src/bin/start_fake_auth_mint.rs
Normal file
163
crates/cdk-integration-tests/src/bin/start_fake_auth_mint.rs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
//! Binary for starting a fake mint with authentication for testing
|
||||||
|
//!
|
||||||
|
//! This binary provides a programmatic way to start a fake mint instance with authentication for testing purposes:
|
||||||
|
//! 1. Sets up a fake mint instance with authentication using the cdk-mintd library
|
||||||
|
//! 2. Configures OpenID Connect authentication settings
|
||||||
|
//! 3. Waits for the mint to be ready and responsive
|
||||||
|
//! 4. Keeps it running until interrupted (Ctrl+C)
|
||||||
|
//! 5. Gracefully shuts down on receiving shutdown signal
|
||||||
|
//!
|
||||||
|
//! This approach offers better control and integration compared to external scripts,
|
||||||
|
//! making it easier to run authentication integration tests with consistent configuration.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use bip39::Mnemonic;
|
||||||
|
use cdk_integration_tests::cli::CommonArgs;
|
||||||
|
use cdk_integration_tests::shared;
|
||||||
|
use clap::Parser;
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "start-fake-auth-mint")]
|
||||||
|
#[command(about = "Start a fake mint with authentication for testing", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
|
/// Database type (sqlite)
|
||||||
|
database_type: String,
|
||||||
|
|
||||||
|
/// Working directory path
|
||||||
|
work_dir: String,
|
||||||
|
|
||||||
|
/// OpenID discovery URL
|
||||||
|
openid_discovery: String,
|
||||||
|
|
||||||
|
/// Port to listen on (default: 8087)
|
||||||
|
#[arg(default_value_t = 8087)]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a fake mint with authentication using the library
|
||||||
|
async fn start_fake_auth_mint(
|
||||||
|
temp_dir: &Path,
|
||||||
|
port: u16,
|
||||||
|
openid_discovery: String,
|
||||||
|
shutdown: Arc<Notify>,
|
||||||
|
) -> Result<tokio::task::JoinHandle<()>> {
|
||||||
|
println!("Starting fake auth mintd on port {port}");
|
||||||
|
|
||||||
|
// Create settings struct for fake mint with auth using shared function
|
||||||
|
let fake_wallet_config = cdk_mintd::config::FakeWallet {
|
||||||
|
supported_units: vec![cdk::nuts::CurrencyUnit::Sat, cdk::nuts::CurrencyUnit::Usd],
|
||||||
|
fee_percent: 0.0,
|
||||||
|
reserve_fee_min: cdk::Amount::from(1),
|
||||||
|
min_delay_time: 1,
|
||||||
|
max_delay_time: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut settings = shared::create_fake_wallet_settings(
|
||||||
|
port,
|
||||||
|
Some(Mnemonic::generate(12)?.to_string()),
|
||||||
|
None,
|
||||||
|
Some(fake_wallet_config),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enable authentication
|
||||||
|
settings.auth = Some(cdk_mintd::config::Auth {
|
||||||
|
openid_discovery,
|
||||||
|
openid_client_id: "cashu-client".to_string(),
|
||||||
|
mint_max_bat: 50,
|
||||||
|
enabled_mint: true,
|
||||||
|
enabled_melt: true,
|
||||||
|
enabled_swap: true,
|
||||||
|
enabled_check_mint_quote: true,
|
||||||
|
enabled_check_melt_quote: true,
|
||||||
|
enabled_restore: true,
|
||||||
|
enabled_check_proof_state: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set description for the mint
|
||||||
|
settings.mint_info.description = "fake test mint with auth".to_string();
|
||||||
|
|
||||||
|
let temp_dir = temp_dir.to_path_buf();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
// Run the mint in a separate task
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
// Create a future that resolves when the shutdown signal is received
|
||||||
|
let shutdown_future = async move {
|
||||||
|
shutdown_clone.notified().await;
|
||||||
|
println!("Fake auth mint shutdown signal received");
|
||||||
|
};
|
||||||
|
|
||||||
|
match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
|
||||||
|
{
|
||||||
|
Ok(_) => println!("Fake auth mint exited normally"),
|
||||||
|
Err(e) => eprintln!("Fake auth mint exited with error: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// Initialize logging based on CLI arguments
|
||||||
|
shared::setup_logging(&args.common);
|
||||||
|
|
||||||
|
let temp_dir = shared::init_working_directory(&args.work_dir)?;
|
||||||
|
|
||||||
|
// Start fake auth mint
|
||||||
|
let shutdown = shared::create_shutdown_handler();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
let handle = start_fake_auth_mint(
|
||||||
|
&temp_dir,
|
||||||
|
args.port,
|
||||||
|
args.openid_discovery.clone(),
|
||||||
|
shutdown_clone,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Wait for fake auth mint to be ready
|
||||||
|
if let Err(e) = shared::wait_for_mint_ready(args.port, 100).await {
|
||||||
|
eprintln!("Error waiting for fake auth mint: {e}");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Fake auth mint started successfully!");
|
||||||
|
println!("Fake auth mint: http://127.0.0.1:{}", args.port);
|
||||||
|
println!("Temp directory: {temp_dir:?}");
|
||||||
|
println!("Database type: {}", args.database_type);
|
||||||
|
println!("OpenID Discovery: {}", args.openid_discovery);
|
||||||
|
println!();
|
||||||
|
println!("Environment variables needed for tests:");
|
||||||
|
println!(" CDK_TEST_OIDC_USER=<username>");
|
||||||
|
println!(" CDK_TEST_OIDC_PASSWORD=<password>");
|
||||||
|
println!();
|
||||||
|
println!("You can now run auth integration tests with:");
|
||||||
|
println!(" cargo test -p cdk-integration-tests --test fake_auth");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop the mint...");
|
||||||
|
|
||||||
|
// Wait for Ctrl+C signal
|
||||||
|
shared::wait_for_shutdown_signal(shutdown).await;
|
||||||
|
|
||||||
|
println!("\nReceived Ctrl+C, shutting down mint...");
|
||||||
|
|
||||||
|
// Wait for mint to finish gracefully
|
||||||
|
if let Err(e) = handle.await {
|
||||||
|
eprintln!("Error waiting for mint to shut down: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Mint shut down successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
178
crates/cdk-integration-tests/src/bin/start_fake_mint.rs
Normal file
178
crates/cdk-integration-tests/src/bin/start_fake_mint.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
//! Binary for starting a fake mint for testing
|
||||||
|
//!
|
||||||
|
//! This binary provides a programmatic way to start a fake mint instance for testing purposes:
|
||||||
|
//! 1. Sets up a fake mint instance using the cdk-mintd library
|
||||||
|
//! 2. Configures the mint with fake wallet backend for testing Lightning Network interactions
|
||||||
|
//! 3. Waits for the mint to be ready and responsive
|
||||||
|
//! 4. Keeps it running until interrupted (Ctrl+C)
|
||||||
|
//! 5. Gracefully shuts down on receiving shutdown signal
|
||||||
|
//!
|
||||||
|
//! This approach offers better control and integration compared to external scripts,
|
||||||
|
//! making it easier to run integration tests with consistent configuration.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cdk::nuts::CurrencyUnit;
|
||||||
|
use cdk_integration_tests::cli::CommonArgs;
|
||||||
|
use cdk_integration_tests::shared;
|
||||||
|
use clap::Parser;
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "start-fake-mint")]
|
||||||
|
#[command(about = "Start a fake mint for testing", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
|
/// Database type (sqlite)
|
||||||
|
database_type: String,
|
||||||
|
|
||||||
|
/// Working directory path
|
||||||
|
work_dir: String,
|
||||||
|
|
||||||
|
/// Port to listen on (default: 8086)
|
||||||
|
#[arg(default_value_t = 8086)]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
/// Use external signatory
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
external_signatory: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a fake mint using the library
|
||||||
|
async fn start_fake_mint(
|
||||||
|
temp_dir: &Path,
|
||||||
|
port: u16,
|
||||||
|
shutdown: Arc<Notify>,
|
||||||
|
external_signatory: bool,
|
||||||
|
) -> Result<tokio::task::JoinHandle<()>> {
|
||||||
|
let signatory_config = if external_signatory {
|
||||||
|
println!("Configuring external signatory");
|
||||||
|
Some((
|
||||||
|
"https://127.0.0.1:15060".to_string(), // Default signatory URL
|
||||||
|
temp_dir.to_string_lossy().to_string(), // Certs directory as string
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mnemonic = if external_signatory {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
"eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fake_wallet_config = Some(cdk_mintd::config::FakeWallet {
|
||||||
|
supported_units: vec![CurrencyUnit::Sat, CurrencyUnit::Usd],
|
||||||
|
fee_percent: 0.0,
|
||||||
|
reserve_fee_min: 1.into(),
|
||||||
|
min_delay_time: 1,
|
||||||
|
max_delay_time: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create settings struct for fake mint using shared function
|
||||||
|
let settings =
|
||||||
|
shared::create_fake_wallet_settings(port, mnemonic, signatory_config, fake_wallet_config);
|
||||||
|
|
||||||
|
println!("Starting fake mintd on port {port}");
|
||||||
|
|
||||||
|
let temp_dir = temp_dir.to_path_buf();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
// Run the mint in a separate task
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
// Create a future that resolves when the shutdown signal is received
|
||||||
|
let shutdown_future = async move {
|
||||||
|
shutdown_clone.notified().await;
|
||||||
|
println!("Fake mint shutdown signal received");
|
||||||
|
};
|
||||||
|
|
||||||
|
match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
|
||||||
|
{
|
||||||
|
Ok(_) => println!("Fake mint exited normally"),
|
||||||
|
Err(e) => eprintln!("Fake mint exited with error: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// Initialize logging based on CLI arguments
|
||||||
|
shared::setup_logging(&args.common);
|
||||||
|
|
||||||
|
let temp_dir = shared::init_working_directory(&args.work_dir)?;
|
||||||
|
|
||||||
|
// Write environment variables to a .env file in the temp_dir BEFORE starting the mint
|
||||||
|
let mint_url = format!("http://127.0.0.1:{}", args.port);
|
||||||
|
let itests_dir = temp_dir.display().to_string();
|
||||||
|
let env_vars: Vec<(&str, &str)> = vec![
|
||||||
|
("CDK_TEST_MINT_URL", &mint_url),
|
||||||
|
("CDK_ITESTS_DIR", &itests_dir),
|
||||||
|
];
|
||||||
|
|
||||||
|
shared::write_env_file(&temp_dir, &env_vars)?;
|
||||||
|
|
||||||
|
// Start fake mint
|
||||||
|
let shutdown = shared::create_shutdown_handler();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
let handle = start_fake_mint(
|
||||||
|
&temp_dir,
|
||||||
|
args.port,
|
||||||
|
shutdown_clone,
|
||||||
|
args.external_signatory,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Wait for fake mint to be ready
|
||||||
|
if let Err(e) = shared::wait_for_mint_ready(args.port, 100).await {
|
||||||
|
eprintln!("Error waiting for fake mint: {e}");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
shared::display_mint_info(args.port, &temp_dir, &args.database_type);
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"Environment variables written to: {}/.env",
|
||||||
|
temp_dir.display()
|
||||||
|
);
|
||||||
|
println!("You can source these variables with:");
|
||||||
|
println!(" source {}/.env", temp_dir.display());
|
||||||
|
println!();
|
||||||
|
println!("Environment variables set:");
|
||||||
|
println!(" CDK_TEST_MINT_URL=http://127.0.0.1:{}", args.port);
|
||||||
|
println!(" CDK_ITESTS_DIR={}", temp_dir.display());
|
||||||
|
println!();
|
||||||
|
println!("You can now run integration tests with:");
|
||||||
|
println!(" cargo test -p cdk-integration-tests --test fake_wallet");
|
||||||
|
println!(" cargo test -p cdk-integration-tests --test happy_path_mint_wallet");
|
||||||
|
println!(" etc.");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop the mint...");
|
||||||
|
|
||||||
|
// Wait for Ctrl+C signal
|
||||||
|
shared::wait_for_shutdown_signal(shutdown).await;
|
||||||
|
|
||||||
|
println!("\nReceived Ctrl+C, shutting down mint...");
|
||||||
|
|
||||||
|
// Wait for mint to finish gracefully
|
||||||
|
if let Err(e) = handle.await {
|
||||||
|
eprintln!("Error waiting for mint to shut down: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Mint shut down successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,20 +1,33 @@
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use cdk_integration_tests::init_regtest::{get_temp_dir, start_regtest_end};
|
use cdk_integration_tests::cli::{init_logging, CommonArgs};
|
||||||
|
use cdk_integration_tests::init_regtest::start_regtest_end;
|
||||||
|
use clap::Parser;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tokio::sync::{oneshot, Notify};
|
use tokio::sync::{oneshot, Notify};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
|
|
||||||
fn signal_progress() {
|
#[derive(Parser)]
|
||||||
let temp_dir = get_temp_dir();
|
#[command(name = "start-regtest")]
|
||||||
|
#[command(about = "Start regtest environment", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
|
/// Working directory path
|
||||||
|
work_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signal_progress(work_dir: &Path) {
|
||||||
let mut pipe = OpenOptions::new()
|
let mut pipe = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(temp_dir.join("progress_pipe"))
|
.open(work_dir.join("progress_pipe"))
|
||||||
.expect("Failed to open pipe");
|
.expect("Failed to open pipe");
|
||||||
|
|
||||||
pipe.write_all(b"checkpoint1\n")
|
pipe.write_all(b"checkpoint1\n")
|
||||||
@@ -23,24 +36,21 @@ fn signal_progress() {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let default_filter = "debug";
|
let args = Args::parse();
|
||||||
|
|
||||||
let sqlx_filter = "sqlx=warn";
|
// Initialize logging based on CLI arguments
|
||||||
let hyper_filter = "hyper=warn";
|
init_logging(args.common.enable_logging, args.common.log_level);
|
||||||
let h2_filter = "h2=warn";
|
|
||||||
let rustls_filter = "rustls=warn";
|
|
||||||
|
|
||||||
let env_filter = EnvFilter::new(format!(
|
let temp_dir = PathBuf::from_str(&args.work_dir)?;
|
||||||
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter}"
|
let temp_dir_clone = temp_dir.clone();
|
||||||
));
|
|
||||||
|
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
|
||||||
|
|
||||||
let shutdown_regtest = Arc::new(Notify::new());
|
let shutdown_regtest = Arc::new(Notify::new());
|
||||||
let shutdown_clone = shutdown_regtest.clone();
|
let shutdown_clone = Arc::clone(&shutdown_regtest);
|
||||||
|
let shutdown_clone_two = Arc::clone(&shutdown_regtest);
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
start_regtest_end(tx, shutdown_clone)
|
start_regtest_end(&temp_dir_clone, tx, shutdown_clone)
|
||||||
.await
|
.await
|
||||||
.expect("Error starting regtest");
|
.expect("Error starting regtest");
|
||||||
});
|
});
|
||||||
@@ -48,15 +58,25 @@ async fn main() -> Result<()> {
|
|||||||
match timeout(Duration::from_secs(300), rx).await {
|
match timeout(Duration::from_secs(300), rx).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::info!("Regtest set up");
|
tracing::info!("Regtest set up");
|
||||||
signal_progress();
|
signal_progress(&temp_dir);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
tracing::error!("regtest setup timed out after 5 minutes");
|
tracing::error!("regtest setup timed out after 5 minutes");
|
||||||
bail!("Could not set up regtest");
|
anyhow::bail!("Could not set up regtest");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signal::ctrl_c().await?;
|
let shutdown_future = async {
|
||||||
|
// Wait for Ctrl+C signal
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install CTRL+C handler");
|
||||||
|
tracing::info!("Shutdown signal received");
|
||||||
|
println!("\nReceived Ctrl+C, shutting down mints...");
|
||||||
|
shutdown_clone_two.notify_waiters();
|
||||||
|
};
|
||||||
|
|
||||||
|
shutdown_future.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
305
crates/cdk-integration-tests/src/bin/start_regtest_mints.rs
Normal file
305
crates/cdk-integration-tests/src/bin/start_regtest_mints.rs
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
//! Binary for starting regtest mints
|
||||||
|
//!
|
||||||
|
//! This binary provides a programmatic way to start regtest mints for testing purposes:
|
||||||
|
//! 1. Sets up a regtest environment with CLN and LND nodes
|
||||||
|
//! 2. Starts CLN and LND mint instances using the cdk-mintd library
|
||||||
|
//! 3. Configures the mints to connect to the respective Lightning Network backends
|
||||||
|
//! 4. Waits for both mints to be ready and responsive
|
||||||
|
//! 5. Keeps them running until interrupted (Ctrl+C)
|
||||||
|
//! 6. Gracefully shuts down all services on receiving shutdown signal
|
||||||
|
//!
|
||||||
|
//! This approach offers better control and integration compared to external scripts,
|
||||||
|
//! making it easier to run integration tests with consistent configuration.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cdk_integration_tests::cli::CommonArgs;
|
||||||
|
use cdk_integration_tests::init_regtest::start_regtest_end;
|
||||||
|
use cdk_integration_tests::shared;
|
||||||
|
use clap::Parser;
|
||||||
|
use tokio::signal::unix::SignalKind;
|
||||||
|
use tokio::signal::{self};
|
||||||
|
use tokio::sync::{oneshot, Notify};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "start-regtest-mints")]
|
||||||
|
#[command(about = "Start regtest mints", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
|
/// Database type (sqlite)
|
||||||
|
database_type: String,
|
||||||
|
|
||||||
|
/// Working directory path
|
||||||
|
work_dir: String,
|
||||||
|
|
||||||
|
/// Mint address (default: 127.0.0.1)
|
||||||
|
#[arg(default_value = "127.0.0.1")]
|
||||||
|
mint_addr: String,
|
||||||
|
|
||||||
|
/// CLN port (default: 8085)
|
||||||
|
#[arg(default_value_t = 8085)]
|
||||||
|
cln_port: u16,
|
||||||
|
|
||||||
|
/// LND port (default: 8087)
|
||||||
|
#[arg(default_value_t = 8087)]
|
||||||
|
lnd_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start regtest CLN mint using the library
|
||||||
|
async fn start_cln_mint(
|
||||||
|
temp_dir: &Path,
|
||||||
|
port: u16,
|
||||||
|
shutdown: Arc<Notify>,
|
||||||
|
) -> Result<tokio::task::JoinHandle<()>> {
|
||||||
|
let cln_rpc_path = temp_dir
|
||||||
|
.join("cln")
|
||||||
|
.join("one")
|
||||||
|
.join("regtest")
|
||||||
|
.join("lightning-rpc");
|
||||||
|
|
||||||
|
let cln_config = cdk_mintd::config::Cln {
|
||||||
|
rpc_path: cln_rpc_path,
|
||||||
|
bolt12: false,
|
||||||
|
fee_percent: 0.0,
|
||||||
|
reserve_fee_min: 0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create settings struct for CLN mint using shared function
|
||||||
|
let settings = shared::create_cln_settings(
|
||||||
|
port,
|
||||||
|
temp_dir
|
||||||
|
.join("cln")
|
||||||
|
.join("one")
|
||||||
|
.join("regtest")
|
||||||
|
.join("lightning-rpc"),
|
||||||
|
"eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal".to_string(),
|
||||||
|
cln_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Starting CLN mintd on port {port}");
|
||||||
|
|
||||||
|
let temp_dir = temp_dir.to_path_buf();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
// Run the mint in a separate task
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
// Create a future that resolves when the shutdown signal is received
|
||||||
|
let shutdown_future = async move {
|
||||||
|
shutdown_clone.notified().await;
|
||||||
|
println!("CLN mint shutdown signal received");
|
||||||
|
};
|
||||||
|
|
||||||
|
match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
|
||||||
|
{
|
||||||
|
Ok(_) => println!("CLN mint exited normally"),
|
||||||
|
Err(e) => eprintln!("CLN mint exited with error: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start regtest LND mint using the library
|
||||||
|
async fn start_lnd_mint(
|
||||||
|
temp_dir: &Path,
|
||||||
|
port: u16,
|
||||||
|
shutdown: Arc<Notify>,
|
||||||
|
) -> Result<tokio::task::JoinHandle<()>> {
|
||||||
|
let lnd_cert_file = temp_dir.join("lnd").join("two").join("tls.cert");
|
||||||
|
let lnd_macaroon_file = temp_dir
|
||||||
|
.join("lnd")
|
||||||
|
.join("two")
|
||||||
|
.join("data")
|
||||||
|
.join("chain")
|
||||||
|
.join("bitcoin")
|
||||||
|
.join("regtest")
|
||||||
|
.join("admin.macaroon");
|
||||||
|
let lnd_work_dir = temp_dir.join("lnd_mint");
|
||||||
|
|
||||||
|
// Create work directory for LND mint
|
||||||
|
fs::create_dir_all(&lnd_work_dir)?;
|
||||||
|
|
||||||
|
let lnd_config = cdk_mintd::config::Lnd {
|
||||||
|
address: "https://localhost:10010".to_string(),
|
||||||
|
cert_file: lnd_cert_file,
|
||||||
|
macaroon_file: lnd_macaroon_file,
|
||||||
|
fee_percent: 0.0,
|
||||||
|
reserve_fee_min: 0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create settings struct for LND mint using shared function
|
||||||
|
let settings = shared::create_lnd_settings(
|
||||||
|
port,
|
||||||
|
lnd_config,
|
||||||
|
"cattle gold bind busy sound reduce tone addict baby spend february strategy".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Starting LND mintd on port {port}");
|
||||||
|
|
||||||
|
let lnd_work_dir = lnd_work_dir.clone();
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
// Run the mint in a separate task
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
// Create a future that resolves when the shutdown signal is received
|
||||||
|
let shutdown_future = async move {
|
||||||
|
shutdown_clone.notified().await;
|
||||||
|
println!("LND mint shutdown signal received");
|
||||||
|
};
|
||||||
|
|
||||||
|
match cdk_mintd::run_mintd_with_shutdown(&lnd_work_dir, &settings, shutdown_future, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => println!("LND mint exited normally"),
|
||||||
|
Err(e) => eprintln!("LND mint exited with error: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// Initialize logging based on CLI arguments
|
||||||
|
shared::setup_logging(&args.common);
|
||||||
|
|
||||||
|
let temp_dir = shared::init_working_directory(&args.work_dir)?;
|
||||||
|
|
||||||
|
// Write environment variables to a .env file in the temp_dir
|
||||||
|
let mint_url_1 = format!("http://{}:{}", args.mint_addr, args.cln_port);
|
||||||
|
let mint_url_2 = format!("http://{}:{}", args.mint_addr, args.lnd_port);
|
||||||
|
let env_vars: Vec<(&str, &str)> = vec![
|
||||||
|
("CDK_TEST_MINT_URL", &mint_url_1),
|
||||||
|
("CDK_TEST_MINT_URL_2", &mint_url_2),
|
||||||
|
];
|
||||||
|
|
||||||
|
shared::write_env_file(&temp_dir, &env_vars)?;
|
||||||
|
|
||||||
|
// Start regtest
|
||||||
|
println!("Starting regtest...");
|
||||||
|
|
||||||
|
let shutdown_regtest = shared::create_shutdown_handler();
|
||||||
|
let shutdown_clone = shutdown_regtest.clone();
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let shutdown_clone_one = Arc::clone(&shutdown_clone);
|
||||||
|
|
||||||
|
let temp_dir_clone = temp_dir.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
start_regtest_end(&temp_dir_clone, tx, shutdown_clone_one)
|
||||||
|
.await
|
||||||
|
.expect("Error starting regtest");
|
||||||
|
});
|
||||||
|
|
||||||
|
match timeout(Duration::from_secs(300), rx).await {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::info!("Regtest set up");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!("regtest setup timed out after 5 minutes");
|
||||||
|
anyhow::bail!("Could not set up regtest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start CLN mint
|
||||||
|
let cln_handle = start_cln_mint(&temp_dir, args.cln_port, shutdown_clone.clone()).await?;
|
||||||
|
|
||||||
|
// Wait for CLN mint to be ready
|
||||||
|
if let Err(e) = shared::wait_for_mint_ready(args.cln_port, 100).await {
|
||||||
|
eprintln!("Error waiting for CLN mint: {e}");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start LND mint
|
||||||
|
let lnd_handle = start_lnd_mint(&temp_dir, args.lnd_port, shutdown_clone.clone()).await?;
|
||||||
|
|
||||||
|
// Wait for LND mint to be ready
|
||||||
|
if let Err(e) = shared::wait_for_mint_ready(args.lnd_port, 100).await {
|
||||||
|
eprintln!("Error waiting for LND mint: {e}");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All regtest mints started successfully!");
|
||||||
|
println!("CLN mint: http://{}:{}", args.mint_addr, args.cln_port);
|
||||||
|
println!("LND mint: http://{}:{}", args.mint_addr, args.lnd_port);
|
||||||
|
shared::display_mint_info(args.cln_port, &temp_dir, &args.database_type); // Using CLN port for display
|
||||||
|
println!();
|
||||||
|
println!("Environment variables set:");
|
||||||
|
println!(
|
||||||
|
" CDK_TEST_MINT_URL=http://{}:{}",
|
||||||
|
args.mint_addr, args.cln_port
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" CDK_TEST_MINT_URL_2=http://{}:{}",
|
||||||
|
args.mint_addr, args.lnd_port
|
||||||
|
);
|
||||||
|
println!(" CDK_ITESTS_DIR={}", temp_dir.display());
|
||||||
|
println!();
|
||||||
|
println!("You can now run integration tests with:");
|
||||||
|
println!(" cargo test -p cdk-integration-tests --test regtest");
|
||||||
|
println!(" cargo test -p cdk-integration-tests --test happy_path_mint_wallet");
|
||||||
|
println!(" etc.");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop the mints...");
|
||||||
|
|
||||||
|
// Create a future to wait for either Ctrl+C signal or unexpected mint termination
|
||||||
|
let shutdown_future = async {
|
||||||
|
// Wait for either SIGINT (Ctrl+C) or SIGTERM
|
||||||
|
let mut sigterm = signal::unix::signal(SignalKind::terminate())
|
||||||
|
.expect("Failed to create SIGTERM signal handler");
|
||||||
|
tokio::select! {
|
||||||
|
_ = signal::ctrl_c() => {
|
||||||
|
tracing::info!("Received SIGINT (Ctrl+C), shutting down mints...");
|
||||||
|
}
|
||||||
|
_ = sigterm.recv() => {
|
||||||
|
tracing::info!("Received SIGTERM, shutting down mints...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("\nShutdown signal received, shutting down mints...");
|
||||||
|
shutdown_clone.notify_waiters();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Monitor mint handles for unexpected termination
|
||||||
|
let monitor_mints = async {
|
||||||
|
loop {
|
||||||
|
if cln_handle.is_finished() {
|
||||||
|
println!("CLN mint finished unexpectedly");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if lnd_handle.is_finished() {
|
||||||
|
println!("LND mint finished unexpectedly");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for either shutdown signal or mint termination
|
||||||
|
tokio::select! {
|
||||||
|
_ = shutdown_future => {
|
||||||
|
println!("Shutdown signal received, waiting for mints to stop...");
|
||||||
|
}
|
||||||
|
_ = monitor_mints => {
|
||||||
|
println!("One or more mints terminated unexpectedly");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for mints to finish gracefully
|
||||||
|
if let Err(e) = tokio::try_join!(cln_handle, lnd_handle) {
|
||||||
|
eprintln!("Error waiting for mints to shut down: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All services shut down successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
42
crates/cdk-integration-tests/src/cli.rs
Normal file
42
crates/cdk-integration-tests/src/cli.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//! Common CLI and logging utilities for CDK integration test binaries
|
||||||
|
//!
|
||||||
|
//! This module provides standardized CLI argument parsing and logging setup
|
||||||
|
//! for integration test binaries.
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
/// Common CLI arguments for CDK integration test binaries
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct CommonArgs {
|
||||||
|
/// Enable logging (default is false)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub enable_logging: bool,
|
||||||
|
|
||||||
|
/// Logging level when enabled (default is debug)
|
||||||
|
#[arg(long, default_value = "debug")]
|
||||||
|
pub log_level: tracing::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize logging based on CLI arguments
|
||||||
|
pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
|
||||||
|
if enable_logging {
|
||||||
|
let default_filter = log_level.to_string();
|
||||||
|
|
||||||
|
// Common filters to reduce noise
|
||||||
|
let sqlx_filter = "sqlx=warn";
|
||||||
|
let hyper_filter = "hyper=warn";
|
||||||
|
let h2_filter = "h2=warn";
|
||||||
|
let rustls_filter = "rustls=warn";
|
||||||
|
let reqwest_filter = "reqwest=warn";
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::new(format!(
|
||||||
|
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Ok if successful, Err if already initialized
|
||||||
|
let _ = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(env_filter)
|
||||||
|
.try_init();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -211,10 +211,10 @@ impl MintConnector for DirectMintConnection {
|
|||||||
pub fn setup_tracing() {
|
pub fn setup_tracing() {
|
||||||
let default_filter = "debug";
|
let default_filter = "debug";
|
||||||
|
|
||||||
let sqlx_filter = "sqlx=warn";
|
let h2_filter = "h2=warn";
|
||||||
let hyper_filter = "hyper=warn";
|
let hyper_filter = "hyper=warn";
|
||||||
|
|
||||||
let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter},{hyper_filter}"));
|
let env_filter = EnvFilter::new(format!("{default_filter},{h2_filter},{hyper_filter}"));
|
||||||
|
|
||||||
// Ok if successful, Err if already initialized
|
// Ok if successful, Err if already initialized
|
||||||
// Allows us to setup tracing at the start of several parallel tests
|
// Allows us to setup tracing at the start of several parallel tests
|
||||||
|
|||||||
@@ -31,21 +31,41 @@ pub const LND_TWO_RPC_ADDR: &str = "localhost:10010";
|
|||||||
pub const CLN_ADDR: &str = "127.0.0.1:19846";
|
pub const CLN_ADDR: &str = "127.0.0.1:19846";
|
||||||
pub const CLN_TWO_ADDR: &str = "127.0.0.1:19847";
|
pub const CLN_TWO_ADDR: &str = "127.0.0.1:19847";
|
||||||
|
|
||||||
pub fn get_mint_addr() -> String {
|
/// Configuration for regtest environment
|
||||||
env::var("CDK_ITESTS_MINT_ADDR").expect("Mint address not set")
|
pub struct RegtestConfig {
|
||||||
|
pub mint_addr: String,
|
||||||
|
pub cln_port: u16,
|
||||||
|
pub lnd_port: u16,
|
||||||
|
pub temp_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mint_port(which: &str) -> u16 {
|
impl Default for RegtestConfig {
|
||||||
let dir = env::var(format!("CDK_ITESTS_MINT_PORT_{which}")).expect("Mint port not set");
|
fn default() -> Self {
|
||||||
dir.parse().unwrap()
|
Self {
|
||||||
|
mint_addr: "127.0.0.1".to_string(),
|
||||||
|
cln_port: 8085,
|
||||||
|
lnd_port: 8087,
|
||||||
|
temp_dir: std::env::temp_dir().join("cdk-itests-default"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mint_url(which: &str) -> String {
|
pub fn get_mint_url_with_config(config: &RegtestConfig, which: &str) -> String {
|
||||||
format!("http://{}:{}", get_mint_addr(), get_mint_port(which))
|
let port = match which {
|
||||||
|
"0" => config.cln_port,
|
||||||
|
"1" => config.lnd_port,
|
||||||
|
_ => panic!("Unknown mint identifier: {which}"),
|
||||||
|
};
|
||||||
|
format!("http://{}:{}", config.mint_addr, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mint_ws_url(which: &str) -> String {
|
pub fn get_mint_ws_url_with_config(config: &RegtestConfig, which: &str) -> String {
|
||||||
format!("ws://{}:{}/v1/ws", get_mint_addr(), get_mint_port(which))
|
let port = match which {
|
||||||
|
"0" => config.cln_port,
|
||||||
|
"1" => config.lnd_port,
|
||||||
|
_ => panic!("Unknown mint identifier: {which}"),
|
||||||
|
};
|
||||||
|
format!("ws://{}:{}/v1/ws", config.mint_addr, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temp_dir() -> PathBuf {
|
pub fn get_temp_dir() -> PathBuf {
|
||||||
@@ -54,15 +74,19 @@ pub fn get_temp_dir() -> PathBuf {
|
|||||||
dir.parse().expect("Valid path buf")
|
dir.parse().expect("Valid path buf")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bitcoin_dir() -> PathBuf {
|
pub fn get_temp_dir_with_config(config: &RegtestConfig) -> &PathBuf {
|
||||||
let dir = get_temp_dir().join(BITCOIN_DIR);
|
&config.temp_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bitcoin_dir(temp_dir: &Path) -> PathBuf {
|
||||||
|
let dir = temp_dir.join(BITCOIN_DIR);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_bitcoind() -> Bitcoind {
|
pub fn init_bitcoind(work_dir: &Path) -> Bitcoind {
|
||||||
Bitcoind::new(
|
Bitcoind::new(
|
||||||
get_bitcoin_dir(),
|
get_bitcoin_dir(work_dir),
|
||||||
BITCOIND_ADDR.parse().unwrap(),
|
BITCOIND_ADDR.parse().unwrap(),
|
||||||
BITCOIN_RPC_USER.to_string(),
|
BITCOIN_RPC_USER.to_string(),
|
||||||
BITCOIN_RPC_PASS.to_string(),
|
BITCOIN_RPC_PASS.to_string(),
|
||||||
@@ -81,14 +105,14 @@ pub fn init_bitcoin_client() -> Result<BitcoinClient> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cln_dir(name: &str) -> PathBuf {
|
pub fn get_cln_dir(work_dir: &Path, name: &str) -> PathBuf {
|
||||||
let dir = get_temp_dir().join("cln").join(name);
|
let dir = work_dir.join("cln").join(name);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_lnd_dir(name: &str) -> PathBuf {
|
pub fn get_lnd_dir(work_dir: &Path, name: &str) -> PathBuf {
|
||||||
let dir = get_temp_dir().join("lnd").join(name);
|
let dir = work_dir.join("lnd").join(name);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
@@ -101,9 +125,14 @@ pub fn get_lnd_macaroon_path(lnd_dir: &Path) -> PathBuf {
|
|||||||
lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon")
|
lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_lnd(lnd_dir: PathBuf, lnd_addr: &str, lnd_rpc_addr: &str) -> Lnd {
|
pub async fn init_lnd(
|
||||||
|
work_dir: &Path,
|
||||||
|
lnd_dir: PathBuf,
|
||||||
|
lnd_addr: &str,
|
||||||
|
lnd_rpc_addr: &str,
|
||||||
|
) -> Lnd {
|
||||||
Lnd::new(
|
Lnd::new(
|
||||||
get_bitcoin_dir(),
|
get_bitcoin_dir(work_dir),
|
||||||
lnd_dir,
|
lnd_dir,
|
||||||
lnd_addr.parse().unwrap(),
|
lnd_addr.parse().unwrap(),
|
||||||
lnd_rpc_addr.to_string(),
|
lnd_rpc_addr.to_string(),
|
||||||
@@ -192,8 +221,12 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyhow::Result<()> {
|
pub async fn start_regtest_end(
|
||||||
let mut bitcoind = init_bitcoind();
|
work_dir: &Path,
|
||||||
|
sender: Sender<()>,
|
||||||
|
notify: Arc<Notify>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut bitcoind = init_bitcoind(work_dir);
|
||||||
bitcoind.start_bitcoind()?;
|
bitcoind.start_bitcoind()?;
|
||||||
|
|
||||||
let bitcoin_client = init_bitcoin_client()?;
|
let bitcoin_client = init_bitcoin_client()?;
|
||||||
@@ -203,9 +236,9 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
|
|||||||
let new_add = bitcoin_client.get_new_address()?;
|
let new_add = bitcoin_client.get_new_address()?;
|
||||||
bitcoin_client.generate_blocks(&new_add, 200).unwrap();
|
bitcoin_client.generate_blocks(&new_add, 200).unwrap();
|
||||||
|
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let cln_one_dir = get_cln_dir(work_dir, "one");
|
||||||
let mut clnd = Clnd::new(
|
let mut clnd = Clnd::new(
|
||||||
get_bitcoin_dir(),
|
get_bitcoin_dir(work_dir),
|
||||||
cln_one_dir.clone(),
|
cln_one_dir.clone(),
|
||||||
CLN_ADDR.into(),
|
CLN_ADDR.into(),
|
||||||
BITCOIN_RPC_USER.to_string(),
|
BITCOIN_RPC_USER.to_string(),
|
||||||
@@ -220,9 +253,9 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
|
|||||||
fund_ln(&bitcoin_client, &cln_client).await.unwrap();
|
fund_ln(&bitcoin_client, &cln_client).await.unwrap();
|
||||||
|
|
||||||
// Create second cln
|
// Create second cln
|
||||||
let cln_two_dir = get_cln_dir("two");
|
let cln_two_dir = get_cln_dir(work_dir, "two");
|
||||||
let mut clnd_two = Clnd::new(
|
let mut clnd_two = Clnd::new(
|
||||||
get_bitcoin_dir(),
|
get_bitcoin_dir(work_dir),
|
||||||
cln_two_dir.clone(),
|
cln_two_dir.clone(),
|
||||||
CLN_TWO_ADDR.into(),
|
CLN_TWO_ADDR.into(),
|
||||||
BITCOIN_RPC_USER.to_string(),
|
BITCOIN_RPC_USER.to_string(),
|
||||||
@@ -236,10 +269,10 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
|
|||||||
|
|
||||||
fund_ln(&bitcoin_client, &cln_two_client).await.unwrap();
|
fund_ln(&bitcoin_client, &cln_two_client).await.unwrap();
|
||||||
|
|
||||||
let lnd_dir = get_lnd_dir("one");
|
let lnd_dir = get_lnd_dir(work_dir, "one");
|
||||||
println!("{}", lnd_dir.display());
|
println!("{}", lnd_dir.display());
|
||||||
|
|
||||||
let mut lnd = init_lnd(lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
|
let mut lnd = init_lnd(work_dir, lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
|
||||||
lnd.start_lnd().unwrap();
|
lnd.start_lnd().unwrap();
|
||||||
tracing::info!("Started lnd node");
|
tracing::info!("Started lnd node");
|
||||||
|
|
||||||
@@ -255,8 +288,15 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
|
|||||||
fund_ln(&bitcoin_client, &lnd_client).await.unwrap();
|
fund_ln(&bitcoin_client, &lnd_client).await.unwrap();
|
||||||
|
|
||||||
// create second lnd node
|
// create second lnd node
|
||||||
let lnd_two_dir = get_lnd_dir("two");
|
let work_dir = get_temp_dir();
|
||||||
let mut lnd_two = init_lnd(lnd_two_dir.clone(), LND_TWO_ADDR, LND_TWO_RPC_ADDR).await;
|
let lnd_two_dir = get_lnd_dir(&work_dir, "two");
|
||||||
|
let mut lnd_two = init_lnd(
|
||||||
|
&work_dir,
|
||||||
|
lnd_two_dir.clone(),
|
||||||
|
LND_TWO_ADDR,
|
||||||
|
LND_TWO_RPC_ADDR,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
lnd_two.start_lnd().unwrap();
|
lnd_two.start_lnd().unwrap();
|
||||||
tracing::info!("Started second lnd node");
|
tracing::info!("Started second lnd node");
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,23 @@
|
|||||||
|
//! Integration Test Library
|
||||||
|
//!
|
||||||
|
//! This crate provides shared functionality for CDK integration tests.
|
||||||
|
//! It includes utilities for setting up test environments, funding wallets,
|
||||||
|
//! and common test operations across different test scenarios.
|
||||||
|
//!
|
||||||
|
//! Test Categories Supported:
|
||||||
|
//! - Pure in-memory tests (no external dependencies)
|
||||||
|
//! - Regtest environment tests (with actual Lightning nodes)
|
||||||
|
//! - Authenticated mint tests
|
||||||
|
//! - Multi-mint scenarios
|
||||||
|
//!
|
||||||
|
//! Key Components:
|
||||||
|
//! - Test environment initialization
|
||||||
|
//! - Wallet funding utilities
|
||||||
|
//! - Lightning Network client helpers
|
||||||
|
//! - Proof state management utilities
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
@@ -8,13 +27,15 @@ use cdk::nuts::{MintQuoteState, NotificationPayload, State};
|
|||||||
use cdk::wallet::WalletSubscription;
|
use cdk::wallet::WalletSubscription;
|
||||||
use cdk::Wallet;
|
use cdk::Wallet;
|
||||||
use cdk_fake_wallet::create_fake_invoice;
|
use cdk_fake_wallet::create_fake_invoice;
|
||||||
use init_regtest::{get_lnd_dir, get_mint_url, LND_RPC_ADDR};
|
use init_regtest::{get_lnd_dir, LND_RPC_ADDR};
|
||||||
use ln_regtest_rs::ln_client::{LightningClient, LndClient};
|
use ln_regtest_rs::ln_client::{LightningClient, LndClient};
|
||||||
use tokio::time::{sleep, timeout, Duration};
|
use tokio::time::{sleep, timeout, Duration};
|
||||||
|
|
||||||
|
pub mod cli;
|
||||||
pub mod init_auth_mint;
|
pub mod init_auth_mint;
|
||||||
pub mod init_pure_tests;
|
pub mod init_pure_tests;
|
||||||
pub mod init_regtest;
|
pub mod init_regtest;
|
||||||
|
pub mod shared;
|
||||||
|
|
||||||
pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
|
pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
|
||||||
let quote = wallet
|
let quote = wallet
|
||||||
@@ -32,6 +53,20 @@ pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
|
|||||||
.expect("Could not mint");
|
.expect("Could not mint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mint_url_from_env() -> String {
|
||||||
|
match env::var("CDK_TEST_MINT_URL") {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(_) => panic!("Mint url not set"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_second_mint_url_from_env() -> String {
|
||||||
|
match env::var("CDK_TEST_MINT_URL_2") {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(_) => panic!("Mint url not set"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get all pending from wallet and attempt to swap
|
// Get all pending from wallet and attempt to swap
|
||||||
// Will panic if there are no pending
|
// Will panic if there are no pending
|
||||||
// Will return Ok if swap fails as expected
|
// Will return Ok if swap fails as expected
|
||||||
@@ -151,33 +186,9 @@ pub async fn wait_for_mint_to_be_paid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the mint URL from environment variable or falls back to default
|
|
||||||
///
|
|
||||||
/// Checks the CDK_TEST_MINT_URL environment variable:
|
|
||||||
/// - If set, returns that URL
|
|
||||||
/// - Otherwise falls back to the default URL from get_mint_url("0")
|
|
||||||
pub fn get_mint_url_from_env() -> String {
|
|
||||||
match env::var("CDK_TEST_MINT_URL") {
|
|
||||||
Ok(url) => url,
|
|
||||||
Err(_) => get_mint_url("0"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the second mint URL from environment variable or falls back to default
|
|
||||||
///
|
|
||||||
/// Checks the CDK_TEST_MINT_URL_2 environment variable:
|
|
||||||
/// - If set, returns that URL
|
|
||||||
/// - Otherwise falls back to the default URL from get_mint_url("1")
|
|
||||||
pub fn get_second_mint_url_from_env() -> String {
|
|
||||||
match env::var("CDK_TEST_MINT_URL_2") {
|
|
||||||
Ok(url) => url,
|
|
||||||
Err(_) => get_mint_url("1"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the ln wallet we use to send/receive ln payements as the wallet
|
// This is the ln wallet we use to send/receive ln payements as the wallet
|
||||||
pub async fn init_lnd_client() -> LndClient {
|
pub async fn init_lnd_client(work_dir: &Path) -> LndClient {
|
||||||
let lnd_dir = get_lnd_dir("one");
|
let lnd_dir = get_lnd_dir(work_dir, "one");
|
||||||
let cert_file = lnd_dir.join("tls.cert");
|
let cert_file = lnd_dir.join("tls.cert");
|
||||||
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
||||||
LndClient::new(format!("https://{LND_RPC_ADDR}"), cert_file, macaroon_file)
|
LndClient::new(format!("https://{LND_RPC_ADDR}"), cert_file, macaroon_file)
|
||||||
@@ -189,10 +200,10 @@ pub async fn init_lnd_client() -> LndClient {
|
|||||||
///
|
///
|
||||||
/// This is useful for tests that need to pay invoices in regtest mode but
|
/// This is useful for tests that need to pay invoices in regtest mode but
|
||||||
/// should be skipped in other environments.
|
/// should be skipped in other environments.
|
||||||
pub async fn pay_if_regtest(invoice: &Bolt11Invoice) -> Result<()> {
|
pub async fn pay_if_regtest(work_dir: &Path, invoice: &Bolt11Invoice) -> Result<()> {
|
||||||
// Check if the invoice is for the regtest network
|
// Check if the invoice is for the regtest network
|
||||||
if invoice.network() == bitcoin::Network::Regtest {
|
if invoice.network() == bitcoin::Network::Regtest {
|
||||||
let lnd_client = init_lnd_client().await;
|
let lnd_client = init_lnd_client(work_dir).await;
|
||||||
lnd_client.pay_invoice(invoice.to_string()).await?;
|
lnd_client.pay_invoice(invoice.to_string()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -220,10 +231,10 @@ pub fn is_regtest_env() -> bool {
|
|||||||
///
|
///
|
||||||
/// Uses the is_regtest_env() function to determine whether to
|
/// Uses the is_regtest_env() function to determine whether to
|
||||||
/// create a real regtest invoice or a fake one for testing.
|
/// create a real regtest invoice or a fake one for testing.
|
||||||
pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
|
pub async fn create_invoice_for_env(work_dir: &Path, amount_sat: Option<u64>) -> Result<String> {
|
||||||
if is_regtest_env() {
|
if is_regtest_env() {
|
||||||
// In regtest mode, create a real invoice
|
// In regtest mode, create a real invoice
|
||||||
let lnd_client = init_lnd_client().await;
|
let lnd_client = init_lnd_client(work_dir).await;
|
||||||
lnd_client
|
lnd_client
|
||||||
.create_invoice(amount_sat)
|
.create_invoice(amount_sat)
|
||||||
.await
|
.await
|
||||||
|
|||||||
266
crates/cdk-integration-tests/src/shared.rs
Normal file
266
crates/cdk-integration-tests/src/shared.rs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
//! Shared utilities for mint integration tests
|
||||||
|
//!
|
||||||
|
//! This module provides common functionality used across different
|
||||||
|
//! integration test binaries to reduce code duplication.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cdk_axum::cache;
|
||||||
|
use tokio::signal;
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
use crate::cli::{init_logging, CommonArgs};
|
||||||
|
|
||||||
|
/// Default minimum mint amount for test mints
|
||||||
|
const DEFAULT_MIN_MINT: u64 = 1;
|
||||||
|
/// Default maximum mint amount for test mints
|
||||||
|
const DEFAULT_MAX_MINT: u64 = 500_000;
|
||||||
|
/// Default minimum melt amount for test mints
|
||||||
|
const DEFAULT_MIN_MELT: u64 = 1;
|
||||||
|
/// Default maximum melt amount for test mints
|
||||||
|
const DEFAULT_MAX_MELT: u64 = 500_000;
|
||||||
|
|
||||||
|
/// Wait for mint to be ready by checking its info endpoint
|
||||||
|
pub async fn wait_for_mint_ready(port: u16, timeout_secs: u64) -> Result<()> {
|
||||||
|
let url = format!("http://127.0.0.1:{port}/v1/info");
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
println!("Waiting for mint on port {port} to be ready...");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Check if timeout has been reached
|
||||||
|
if start_time.elapsed().as_secs() > timeout_secs {
|
||||||
|
return Err(anyhow::anyhow!("Timeout waiting for mint on port {}", port));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to make a request to the mint info endpoint
|
||||||
|
match reqwest::get(&url).await {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status().is_success() {
|
||||||
|
println!("Mint on port {port} is ready");
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Mint on port {} returned status: {}",
|
||||||
|
port,
|
||||||
|
response.status()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error connecting to mint on port {port}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize working directory
|
||||||
|
pub fn init_working_directory(work_dir: &str) -> Result<PathBuf> {
|
||||||
|
let temp_dir = PathBuf::from_str(work_dir)?;
|
||||||
|
|
||||||
|
// Create the temp directory if it doesn't exist
|
||||||
|
fs::create_dir_all(&temp_dir)?;
|
||||||
|
|
||||||
|
Ok(temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write environment variables to .env file
|
||||||
|
pub fn write_env_file(temp_dir: &Path, env_vars: &[(&str, &str)]) -> Result<()> {
|
||||||
|
let mut env_content = String::new();
|
||||||
|
for (key, value) in env_vars {
|
||||||
|
env_content.push_str(&format!("{key}={value}\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let env_file_path = temp_dir.join(".env");
|
||||||
|
|
||||||
|
fs::write(&env_file_path, &env_content)
|
||||||
|
.map(|_| {
|
||||||
|
println!(
|
||||||
|
"Environment variables written to: {}",
|
||||||
|
env_file_path.display()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Could not write .env file: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for .env file to be created
|
||||||
|
pub async fn wait_for_env_file(temp_dir: &Path, timeout_secs: u64) -> Result<()> {
|
||||||
|
let env_file_path = temp_dir.join(".env");
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Waiting for .env file to be created at: {}",
|
||||||
|
env_file_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Check if timeout has been reached
|
||||||
|
if start_time.elapsed().as_secs() > timeout_secs {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Timeout waiting for .env file at {}",
|
||||||
|
env_file_path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the file exists
|
||||||
|
if env_file_path.exists() {
|
||||||
|
println!(".env file found at: {}", env_file_path.display());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup common logging based on CLI arguments
|
||||||
|
pub fn setup_logging(common_args: &CommonArgs) {
|
||||||
|
init_logging(common_args.enable_logging, common_args.log_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create shutdown handler for graceful termination
|
||||||
|
pub fn create_shutdown_handler() -> Arc<Notify> {
|
||||||
|
Arc::new(Notify::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for Ctrl+C signal
|
||||||
|
pub async fn wait_for_shutdown_signal(shutdown: Arc<Notify>) {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install CTRL+C handler");
|
||||||
|
|
||||||
|
println!("\nReceived Ctrl+C, shutting down...");
|
||||||
|
shutdown.notify_waiters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common mint information display
|
||||||
|
pub fn display_mint_info(port: u16, temp_dir: &Path, database_type: &str) {
|
||||||
|
println!("Mint started successfully!");
|
||||||
|
println!("Mint URL: http://127.0.0.1:{port}");
|
||||||
|
println!("Temp directory: {temp_dir:?}");
|
||||||
|
println!("Database type: {database_type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create settings for a fake wallet mint
|
||||||
|
pub fn create_fake_wallet_settings(
|
||||||
|
port: u16,
|
||||||
|
mnemonic: Option<String>,
|
||||||
|
signatory_config: Option<(String, String)>, // (url, certs_dir)
|
||||||
|
fake_wallet_config: Option<cdk_mintd::config::FakeWallet>,
|
||||||
|
) -> cdk_mintd::config::Settings {
|
||||||
|
cdk_mintd::config::Settings {
|
||||||
|
info: cdk_mintd::config::Info {
|
||||||
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
|
listen_host: "127.0.0.1".to_string(),
|
||||||
|
listen_port: port,
|
||||||
|
mnemonic,
|
||||||
|
signatory_url: signatory_config.as_ref().map(|(url, _)| url.clone()),
|
||||||
|
signatory_certs: signatory_config
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, certs_dir)| certs_dir.clone()),
|
||||||
|
input_fee_ppk: None,
|
||||||
|
http_cache: cache::Config::default(),
|
||||||
|
enable_swagger_ui: None,
|
||||||
|
},
|
||||||
|
mint_info: cdk_mintd::config::MintInfo::default(),
|
||||||
|
ln: cdk_mintd::config::Ln {
|
||||||
|
ln_backend: cdk_mintd::config::LnBackend::FakeWallet,
|
||||||
|
invoice_description: None,
|
||||||
|
min_mint: DEFAULT_MIN_MINT.into(),
|
||||||
|
max_mint: DEFAULT_MAX_MINT.into(),
|
||||||
|
min_melt: DEFAULT_MIN_MELT.into(),
|
||||||
|
max_melt: DEFAULT_MAX_MELT.into(),
|
||||||
|
},
|
||||||
|
cln: None,
|
||||||
|
lnbits: None,
|
||||||
|
lnd: None,
|
||||||
|
fake_wallet: fake_wallet_config,
|
||||||
|
grpc_processor: None,
|
||||||
|
database: cdk_mintd::config::Database::default(),
|
||||||
|
mint_management_rpc: None,
|
||||||
|
auth: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create settings for a CLN mint
|
||||||
|
pub fn create_cln_settings(
|
||||||
|
port: u16,
|
||||||
|
_cln_rpc_path: PathBuf,
|
||||||
|
mnemonic: String,
|
||||||
|
cln_config: cdk_mintd::config::Cln,
|
||||||
|
) -> cdk_mintd::config::Settings {
|
||||||
|
cdk_mintd::config::Settings {
|
||||||
|
info: cdk_mintd::config::Info {
|
||||||
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
|
listen_host: "127.0.0.1".to_string(),
|
||||||
|
listen_port: port,
|
||||||
|
mnemonic: Some(mnemonic),
|
||||||
|
signatory_url: None,
|
||||||
|
signatory_certs: None,
|
||||||
|
input_fee_ppk: None,
|
||||||
|
http_cache: cache::Config::default(),
|
||||||
|
enable_swagger_ui: None,
|
||||||
|
},
|
||||||
|
mint_info: cdk_mintd::config::MintInfo::default(),
|
||||||
|
ln: cdk_mintd::config::Ln {
|
||||||
|
ln_backend: cdk_mintd::config::LnBackend::Cln,
|
||||||
|
invoice_description: None,
|
||||||
|
min_mint: DEFAULT_MIN_MINT.into(),
|
||||||
|
max_mint: DEFAULT_MAX_MINT.into(),
|
||||||
|
min_melt: DEFAULT_MIN_MELT.into(),
|
||||||
|
max_melt: DEFAULT_MAX_MELT.into(),
|
||||||
|
},
|
||||||
|
cln: Some(cln_config),
|
||||||
|
lnbits: None,
|
||||||
|
lnd: None,
|
||||||
|
fake_wallet: None,
|
||||||
|
grpc_processor: None,
|
||||||
|
database: cdk_mintd::config::Database::default(),
|
||||||
|
mint_management_rpc: None,
|
||||||
|
auth: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create settings for an LND mint
|
||||||
|
pub fn create_lnd_settings(
|
||||||
|
port: u16,
|
||||||
|
lnd_config: cdk_mintd::config::Lnd,
|
||||||
|
mnemonic: String,
|
||||||
|
) -> cdk_mintd::config::Settings {
|
||||||
|
cdk_mintd::config::Settings {
|
||||||
|
info: cdk_mintd::config::Info {
|
||||||
|
url: format!("http://127.0.0.1:{port}"),
|
||||||
|
listen_host: "127.0.0.1".to_string(),
|
||||||
|
listen_port: port,
|
||||||
|
mnemonic: Some(mnemonic),
|
||||||
|
signatory_url: None,
|
||||||
|
signatory_certs: None,
|
||||||
|
input_fee_ppk: None,
|
||||||
|
http_cache: cache::Config::default(),
|
||||||
|
enable_swagger_ui: None,
|
||||||
|
},
|
||||||
|
mint_info: cdk_mintd::config::MintInfo::default(),
|
||||||
|
ln: cdk_mintd::config::Ln {
|
||||||
|
ln_backend: cdk_mintd::config::LnBackend::Lnd,
|
||||||
|
invoice_description: None,
|
||||||
|
min_mint: DEFAULT_MIN_MINT.into(),
|
||||||
|
max_mint: DEFAULT_MAX_MINT.into(),
|
||||||
|
min_melt: DEFAULT_MIN_MELT.into(),
|
||||||
|
max_melt: DEFAULT_MAX_MELT.into(),
|
||||||
|
},
|
||||||
|
cln: None,
|
||||||
|
lnbits: None,
|
||||||
|
lnd: Some(lnd_config),
|
||||||
|
fake_wallet: None,
|
||||||
|
grpc_processor: None,
|
||||||
|
database: cdk_mintd::config::Database::default(),
|
||||||
|
mint_management_rpc: None,
|
||||||
|
auth: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -6,11 +8,45 @@ use cashu::amount::SplitTarget;
|
|||||||
use cashu::nut23::Amountless;
|
use cashu::nut23::Amountless;
|
||||||
use cashu::{Amount, CurrencyUnit, MintRequest, PreMintSecrets, ProofsMethods};
|
use cashu::{Amount, CurrencyUnit, MintRequest, PreMintSecrets, ProofsMethods};
|
||||||
use cdk::wallet::{HttpClient, MintConnector, Wallet};
|
use cdk::wallet::{HttpClient, MintConnector, Wallet};
|
||||||
use cdk_integration_tests::init_regtest::get_cln_dir;
|
use cdk_integration_tests::init_regtest::{get_cln_dir, get_temp_dir};
|
||||||
use cdk_integration_tests::{get_mint_url_from_env, wait_for_mint_to_be_paid};
|
use cdk_integration_tests::{get_mint_url_from_env, wait_for_mint_to_be_paid};
|
||||||
use cdk_sqlite::wallet::memory;
|
use cdk_sqlite::wallet::memory;
|
||||||
use ln_regtest_rs::ln_client::ClnClient;
|
use ln_regtest_rs::ln_client::ClnClient;
|
||||||
|
|
||||||
|
// Helper function to get temp directory from environment or fallback
|
||||||
|
fn get_test_temp_dir() -> PathBuf {
|
||||||
|
match env::var("CDK_ITESTS_DIR") {
|
||||||
|
Ok(dir) => PathBuf::from(dir),
|
||||||
|
Err(_) => get_temp_dir(), // fallback to default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create CLN client with retries
|
||||||
|
async fn create_cln_client_with_retry(cln_dir: PathBuf) -> Result<ClnClient> {
|
||||||
|
let mut retries = 0;
|
||||||
|
let max_retries = 10;
|
||||||
|
loop {
|
||||||
|
match ClnClient::new(cln_dir.clone(), None).await {
|
||||||
|
Ok(client) => return Ok(client),
|
||||||
|
Err(e) => {
|
||||||
|
retries += 1;
|
||||||
|
if retries >= max_retries {
|
||||||
|
bail!(
|
||||||
|
"Could not connect to CLN client after {} retries: {}",
|
||||||
|
max_retries,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"Failed to connect to CLN (attempt {}/{}): {}. Retrying in 7 seconds...",
|
||||||
|
retries, max_retries, e
|
||||||
|
);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(7)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests basic BOLT12 minting functionality:
|
/// Tests basic BOLT12 minting functionality:
|
||||||
/// - Creates a wallet
|
/// - Creates a wallet
|
||||||
/// - Gets a BOLT12 quote for a specific amount (100 sats)
|
/// - Gets a BOLT12 quote for a specific amount (100 sats)
|
||||||
@@ -36,8 +72,11 @@ async fn test_regtest_bolt12_mint() {
|
|||||||
|
|
||||||
assert_eq!(mint_quote.amount, Some(mint_amount));
|
assert_eq!(mint_quote.amount, Some(mint_amount));
|
||||||
|
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let work_dir = get_test_temp_dir();
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
|
let cln_one_dir = get_cln_dir(&work_dir, "one");
|
||||||
|
let cln_client = create_cln_client_with_retry(cln_one_dir.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
cln_client
|
cln_client
|
||||||
.pay_bolt12_offer(None, mint_quote.request)
|
.pay_bolt12_offer(None, mint_quote.request)
|
||||||
.await
|
.await
|
||||||
@@ -68,8 +107,9 @@ async fn test_regtest_bolt12_mint_multiple() -> Result<()> {
|
|||||||
|
|
||||||
let mint_quote = wallet.mint_bolt12_quote(None, None).await?;
|
let mint_quote = wallet.mint_bolt12_quote(None, None).await?;
|
||||||
|
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let work_dir = get_test_temp_dir();
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
|
let cln_one_dir = get_cln_dir(&work_dir, "one");
|
||||||
|
let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
|
||||||
cln_client
|
cln_client
|
||||||
.pay_bolt12_offer(Some(10000), mint_quote.request.clone())
|
.pay_bolt12_offer(Some(10000), mint_quote.request.clone())
|
||||||
.await
|
.await
|
||||||
@@ -131,8 +171,9 @@ async fn test_regtest_bolt12_multiple_wallets() -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Create a BOLT12 offer that both wallets will use
|
// Create a BOLT12 offer that both wallets will use
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let work_dir = get_test_temp_dir();
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
|
let cln_one_dir = get_cln_dir(&work_dir, "one");
|
||||||
|
let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
|
||||||
// First wallet payment
|
// First wallet payment
|
||||||
let quote_one = wallet_one
|
let quote_one = wallet_one
|
||||||
.mint_bolt12_quote(Some(10_000.into()), None)
|
.mint_bolt12_quote(Some(10_000.into()), None)
|
||||||
@@ -222,8 +263,9 @@ async fn test_regtest_bolt12_melt() -> Result<()> {
|
|||||||
|
|
||||||
assert_eq!(mint_quote.amount, Some(mint_amount));
|
assert_eq!(mint_quote.amount, Some(mint_amount));
|
||||||
// Pay the quote
|
// Pay the quote
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let work_dir = get_test_temp_dir();
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
|
let cln_one_dir = get_cln_dir(&work_dir, "one");
|
||||||
|
let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
|
||||||
cln_client
|
cln_client
|
||||||
.pay_bolt12_offer(None, mint_quote.request.clone())
|
.pay_bolt12_offer(None, mint_quote.request.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -281,8 +323,9 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
|
|||||||
|
|
||||||
let pay_amount_msats = 10_000;
|
let pay_amount_msats = 10_000;
|
||||||
|
|
||||||
let cln_one_dir = get_cln_dir("one");
|
let work_dir = get_test_temp_dir();
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
|
let cln_one_dir = get_cln_dir(&work_dir, "one");
|
||||||
|
let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
|
||||||
cln_client
|
cln_client
|
||||||
.pay_bolt12_offer(Some(pay_amount_msats), mint_quote.request.clone())
|
.pay_bolt12_offer(Some(pay_amount_msats), mint_quote.request.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
//! Fake Wallet Integration Tests
|
||||||
|
//!
|
||||||
|
//! This file contains tests for the fake wallet backend functionality.
|
||||||
|
//! The fake wallet simulates Lightning Network behavior for testing purposes,
|
||||||
|
//! allowing verification of mint behavior in various payment scenarios without
|
||||||
|
//! requiring a real Lightning node.
|
||||||
|
//!
|
||||||
|
//! Test Scenarios:
|
||||||
|
//! - Pending payment states and proof handling
|
||||||
|
//! - Payment failure cases and proof state management
|
||||||
|
//! - Change output verification in melt operations
|
||||||
|
//! - Witness signature validation
|
||||||
|
//! - Cross-unit transaction validation
|
||||||
|
//! - Overflow and balance validation
|
||||||
|
//! - Duplicate proof detection
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bip39::Mnemonic;
|
use bip39::Mnemonic;
|
||||||
@@ -408,40 +424,6 @@ async fn test_fake_melt_change_in_quote() {
|
|||||||
assert_eq!(melt_change, check);
|
assert_eq!(melt_change, check);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that the correct database type is used based on environment variables
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
||||||
async fn test_database_type() {
|
|
||||||
// Get the database type and work dir from environment
|
|
||||||
let db_type = std::env::var("CDK_MINTD_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");
|
|
||||||
}
|
|
||||||
_ => panic!("Unknown database type: {}", db_type),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests minting tokens with a valid witness signature
|
/// Tests minting tokens with a valid witness signature
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
async fn test_fake_mint_with_witness() {
|
async fn test_fake_mint_with_witness() {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
use core::panic;
|
use core::panic;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -32,6 +33,14 @@ use tokio::time::timeout;
|
|||||||
use tokio_tungstenite::connect_async;
|
use tokio_tungstenite::connect_async;
|
||||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||||
|
|
||||||
|
// Helper function to get temp directory from environment or fallback
|
||||||
|
fn get_test_temp_dir() -> PathBuf {
|
||||||
|
match env::var("CDK_ITESTS_DIR") {
|
||||||
|
Ok(dir) => PathBuf::from(dir),
|
||||||
|
Err(_) => panic!("Unknown test dir"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_notification<T: StreamExt<Item = Result<Message, E>> + Unpin, E: Debug>(
|
async fn get_notification<T: StreamExt<Item = Result<Message, E>> + Unpin, E: Debug>(
|
||||||
reader: &mut T,
|
reader: &mut T,
|
||||||
timeout_to_wait: Duration,
|
timeout_to_wait: Duration,
|
||||||
@@ -98,7 +107,9 @@ async fn test_happy_mint_melt_round_trip() {
|
|||||||
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
||||||
|
|
||||||
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
pay_if_regtest(&invoice).await.unwrap();
|
pay_if_regtest(&get_test_temp_dir(), &invoice)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
|
||||||
.await
|
.await
|
||||||
@@ -113,7 +124,9 @@ async fn test_happy_mint_melt_round_trip() {
|
|||||||
|
|
||||||
assert!(mint_amount == 100.into());
|
assert!(mint_amount == 100.into());
|
||||||
|
|
||||||
let invoice = create_invoice_for_env(Some(50)).await.unwrap();
|
let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(50))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let melt = wallet.melt_quote(invoice, None).await.unwrap();
|
let melt = wallet.melt_quote(invoice, None).await.unwrap();
|
||||||
|
|
||||||
@@ -217,7 +230,9 @@ async fn test_happy_mint() {
|
|||||||
assert_eq!(mint_quote.amount, Some(mint_amount));
|
assert_eq!(mint_quote.amount, Some(mint_amount));
|
||||||
|
|
||||||
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
pay_if_regtest(&invoice).await.unwrap();
|
pay_if_regtest(&get_test_temp_dir(), &invoice)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
||||||
.await
|
.await
|
||||||
@@ -262,7 +277,9 @@ async fn test_restore() {
|
|||||||
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
||||||
|
|
||||||
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
pay_if_regtest(&invoice).await.unwrap();
|
pay_if_regtest(&get_test_temp_dir(), &invoice)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
||||||
.await
|
.await
|
||||||
@@ -341,7 +358,7 @@ async fn test_fake_melt_change_in_quote() {
|
|||||||
|
|
||||||
let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
|
|
||||||
pay_if_regtest(&bolt11).await.unwrap();
|
pay_if_regtest(&get_test_temp_dir(), &bolt11).await.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
||||||
.await
|
.await
|
||||||
@@ -352,7 +369,9 @@ async fn test_fake_melt_change_in_quote() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invoice = create_invoice_for_env(Some(9)).await.unwrap();
|
let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(9))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let proofs = wallet.get_unspent_proofs().await.unwrap();
|
let proofs = wallet.get_unspent_proofs().await.unwrap();
|
||||||
|
|
||||||
@@ -408,7 +427,7 @@ async fn test_pay_invoice_twice() {
|
|||||||
|
|
||||||
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
||||||
|
|
||||||
pay_if_regtest(&mint_quote.request.parse().unwrap())
|
pay_if_regtest(&get_test_temp_dir(), &mint_quote.request.parse().unwrap())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -425,7 +444,9 @@ async fn test_pay_invoice_twice() {
|
|||||||
|
|
||||||
assert_eq!(mint_amount, 100.into());
|
assert_eq!(mint_amount, 100.into());
|
||||||
|
|
||||||
let invoice = create_invoice_for_env(Some(25)).await.unwrap();
|
let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(25))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let melt_quote = wallet.melt_quote(invoice.clone(), None).await.unwrap();
|
let melt_quote = wallet.melt_quote(invoice.clone(), None).await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
//! These tests verify the interaction between mint and wallet components, simulating real-world usage scenarios.
|
//! These tests verify the interaction between mint and wallet components, simulating real-world usage scenarios.
|
||||||
//! They test the complete flow of operations including wallet funding, token swapping, sending tokens between wallets,
|
//! They test the complete flow of operations including wallet funding, token swapping, sending tokens between wallets,
|
||||||
//! and other operations that require client-mint interaction.
|
//! and other operations that require client-mint interaction.
|
||||||
|
//!
|
||||||
|
//! Test Environment:
|
||||||
|
//! - Uses pure in-memory mint instances for fast execution
|
||||||
|
//! - Tests run concurrently with multi-threaded tokio runtime
|
||||||
|
//! - No external dependencies (Lightning nodes, databases) required
|
||||||
|
|
||||||
use std::assert_eq;
|
use std::assert_eq;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
//! Mint tests
|
//! Mint Tests
|
||||||
//!
|
//!
|
||||||
//! This file contains tests that focus on the mint's internal functionality without client interaction.
|
//! This file contains tests that focus on the mint's internal functionality without client interaction.
|
||||||
//! These tests verify the mint's behavior in isolation, such as keyset management, database operations,
|
//! These tests verify the mint's behavior in isolation, such as keyset management, database operations,
|
||||||
//! and other mint-specific functionality that doesn't require wallet clients.
|
//! and other mint-specific functionality that doesn't require wallet clients.
|
||||||
|
//!
|
||||||
|
//! Test Categories:
|
||||||
|
//! - Keyset rotation and management
|
||||||
|
//! - Database transaction handling
|
||||||
|
//! - Internal state transitions
|
||||||
|
//! - Fee calculation and enforcement
|
||||||
|
//! - Proof validation and state management
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -1,4 +1,20 @@
|
|||||||
use std::str::FromStr;
|
//! Regtest Integration Tests
|
||||||
|
//!
|
||||||
|
//! This file contains tests that run against actual Lightning Network nodes in regtest mode.
|
||||||
|
//! These tests require a local development environment with LND nodes configured for regtest.
|
||||||
|
//!
|
||||||
|
//! Test Environment Setup:
|
||||||
|
//! - Uses actual LND nodes connected to a regtest Bitcoin network
|
||||||
|
//! - Tests real Lightning payment flows including invoice creation and payment
|
||||||
|
//! - Verifies mint behavior with actual Lightning Network interactions
|
||||||
|
//!
|
||||||
|
//! Running Tests:
|
||||||
|
//! - Requires CDK_TEST_REGTEST=1 environment variable to be set
|
||||||
|
//! - Requires properly configured LND nodes with TLS certificates and macaroons
|
||||||
|
//! - Uses real Bitcoin transactions in regtest mode
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -10,32 +26,46 @@ use cdk::nuts::{
|
|||||||
NotificationPayload, PreMintSecrets,
|
NotificationPayload, PreMintSecrets,
|
||||||
};
|
};
|
||||||
use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
|
use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
|
||||||
use cdk_integration_tests::init_regtest::{
|
use cdk_integration_tests::init_regtest::{get_lnd_dir, LND_RPC_ADDR};
|
||||||
get_cln_dir, get_lnd_cert_file_path, get_lnd_dir, get_lnd_macaroon_path, get_mint_port,
|
|
||||||
LND_RPC_ADDR, LND_TWO_RPC_ADDR,
|
|
||||||
};
|
|
||||||
use cdk_integration_tests::{
|
use cdk_integration_tests::{
|
||||||
get_mint_url_from_env, get_second_mint_url_from_env, wait_for_mint_to_be_paid,
|
get_mint_url_from_env, get_second_mint_url_from_env, wait_for_mint_to_be_paid,
|
||||||
};
|
};
|
||||||
use cdk_sqlite::wallet::{self, memory};
|
use cdk_sqlite::wallet::{self, memory};
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use lightning_invoice::Bolt11Invoice;
|
use ln_regtest_rs::ln_client::{LightningClient, LndClient};
|
||||||
use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
|
|
||||||
use ln_regtest_rs::InvoiceStatus;
|
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
// This is the ln wallet we use to send/receive ln payements as the wallet
|
// This is the ln wallet we use to send/receive ln payements as the wallet
|
||||||
async fn init_lnd_client() -> LndClient {
|
async fn init_lnd_client() -> LndClient {
|
||||||
let lnd_dir = get_lnd_dir("one");
|
// Try to get the temp directory from environment variable first (from .env file)
|
||||||
|
let temp_dir = match env::var("CDK_ITESTS_DIR") {
|
||||||
|
Ok(dir) => {
|
||||||
|
let path = PathBuf::from(dir);
|
||||||
|
println!("Using temp directory from CDK_ITESTS_DIR: {:?}", path);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
panic!("Unknown temp dir");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The LND mint uses the second LND node (LND_TWO_RPC_ADDR = localhost:10010)
|
||||||
|
let lnd_dir = get_lnd_dir(&temp_dir, "one");
|
||||||
let cert_file = lnd_dir.join("tls.cert");
|
let cert_file = lnd_dir.join("tls.cert");
|
||||||
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
||||||
|
|
||||||
|
println!("Looking for LND cert file: {:?}", cert_file);
|
||||||
|
println!("Looking for LND macaroon file: {:?}", macaroon_file);
|
||||||
|
println!("Connecting to LND at: https://{}", LND_RPC_ADDR);
|
||||||
|
|
||||||
|
// Connect to LND
|
||||||
LndClient::new(
|
LndClient::new(
|
||||||
format!("https://{}", LND_RPC_ADDR),
|
format!("https://{}", LND_RPC_ADDR),
|
||||||
cert_file,
|
cert_file.clone(),
|
||||||
macaroon_file,
|
macaroon_file.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.expect("Could not connect to lnd rpc")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
@@ -98,41 +128,41 @@ async fn test_internal_payment() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let check_paid = match get_mint_port("0") {
|
// let check_paid = match get_mint_port("0") {
|
||||||
8085 => {
|
// 8085 => {
|
||||||
let cln_one_dir = get_cln_dir("one");
|
// let cln_one_dir = get_cln_dir(&get_temp_dir(), "one");
|
||||||
let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
|
// let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
|
||||||
|
|
||||||
let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
// let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
cln_client
|
// cln_client
|
||||||
.check_incoming_payment_status(&payment_hash.payment_hash().to_string())
|
// .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
|
||||||
.await
|
// .await
|
||||||
.expect("Could not check invoice")
|
// .expect("Could not check invoice")
|
||||||
}
|
// }
|
||||||
8087 => {
|
// 8087 => {
|
||||||
let lnd_two_dir = get_lnd_dir("two");
|
// let lnd_two_dir = get_lnd_dir(&get_temp_dir(), "two");
|
||||||
let lnd_client = LndClient::new(
|
// let lnd_client = LndClient::new(
|
||||||
format!("https://{}", LND_TWO_RPC_ADDR),
|
// format!("https://{}", LND_TWO_RPC_ADDR),
|
||||||
get_lnd_cert_file_path(&lnd_two_dir),
|
// get_lnd_cert_file_path(&lnd_two_dir),
|
||||||
get_lnd_macaroon_path(&lnd_two_dir),
|
// get_lnd_macaroon_path(&lnd_two_dir),
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
// let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
lnd_client
|
// lnd_client
|
||||||
.check_incoming_payment_status(&payment_hash.payment_hash().to_string())
|
// .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
|
||||||
.await
|
// .await
|
||||||
.expect("Could not check invoice")
|
// .expect("Could not check invoice")
|
||||||
}
|
// }
|
||||||
_ => panic!("Unknown mint port"),
|
// _ => panic!("Unknown mint port"),
|
||||||
};
|
// };
|
||||||
|
|
||||||
match check_paid {
|
// match check_paid {
|
||||||
InvoiceStatus::Unpaid => (),
|
// InvoiceStatus::Unpaid => (),
|
||||||
_ => {
|
// _ => {
|
||||||
panic!("Invoice has incorrect status: {:?}", check_paid);
|
// panic!("Invoice has incorrect status: {:?}", check_paid);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
let wallet_2_balance = wallet_2.total_balance().await.unwrap();
|
let wallet_2_balance = wallet_2.total_balance().await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use cashu::{Bolt11Invoice, ProofsMethods};
|
|||||||
use cdk::amount::{Amount, SplitTarget};
|
use cdk::amount::{Amount, SplitTarget};
|
||||||
use cdk::nuts::CurrencyUnit;
|
use cdk::nuts::CurrencyUnit;
|
||||||
use cdk::wallet::{ReceiveOptions, SendKind, SendOptions, Wallet};
|
use cdk::wallet::{ReceiveOptions, SendKind, SendOptions, Wallet};
|
||||||
|
use cdk_integration_tests::init_regtest::get_temp_dir;
|
||||||
use cdk_integration_tests::{
|
use cdk_integration_tests::{
|
||||||
create_invoice_for_env, get_mint_url_from_env, pay_if_regtest, wait_for_mint_to_be_paid,
|
create_invoice_for_env, get_mint_url_from_env, pay_if_regtest, wait_for_mint_to_be_paid,
|
||||||
};
|
};
|
||||||
@@ -26,7 +27,7 @@ async fn test_swap() {
|
|||||||
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
||||||
|
|
||||||
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
pay_if_regtest(&invoice).await.unwrap();
|
pay_if_regtest(&get_temp_dir(), &invoice).await.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
|
||||||
.await
|
.await
|
||||||
@@ -93,7 +94,7 @@ async fn test_fake_melt_change_in_quote() {
|
|||||||
|
|
||||||
let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
|
||||||
|
|
||||||
pay_if_regtest(&bolt11).await.unwrap();
|
pay_if_regtest(&get_temp_dir(), &bolt11).await.unwrap();
|
||||||
|
|
||||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
|
||||||
.await
|
.await
|
||||||
@@ -106,7 +107,9 @@ async fn test_fake_melt_change_in_quote() {
|
|||||||
|
|
||||||
let invoice_amount = 9;
|
let invoice_amount = 9;
|
||||||
|
|
||||||
let invoice = create_invoice_for_env(Some(invoice_amount)).await.unwrap();
|
let invoice = create_invoice_for_env(&get_temp_dir(), Some(invoice_amount))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
|
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ anyhow.workspace = true
|
|||||||
cdk = { workspace = true, features = [
|
cdk = { workspace = true, features = [
|
||||||
"mint",
|
"mint",
|
||||||
] }
|
] }
|
||||||
cdk-common = { workspace = true }
|
cdk-common.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
tonic = { workspace = true, features = ["transport"] }
|
tonic = { workspace = true, features = ["transport"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|||||||
@@ -7,22 +7,55 @@ use cdk_mint_rpc::GetInfoRequest;
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
use tracing::Level;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
/// Common CLI arguments for CDK binaries
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct CommonArgs {
|
||||||
|
/// Enable logging (default is false)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub enable_logging: bool,
|
||||||
|
|
||||||
|
/// Logging level when enabled (default is debug)
|
||||||
|
#[arg(long, default_value = "debug")]
|
||||||
|
pub log_level: tracing::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize logging based on CLI arguments
|
||||||
|
pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
|
||||||
|
if enable_logging {
|
||||||
|
let default_filter = log_level.to_string();
|
||||||
|
|
||||||
|
// Common filters to reduce noise
|
||||||
|
let sqlx_filter = "sqlx=warn";
|
||||||
|
let hyper_filter = "hyper=warn";
|
||||||
|
let h2_filter = "h2=warn";
|
||||||
|
let rustls_filter = "rustls=warn";
|
||||||
|
let reqwest_filter = "reqwest=warn";
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::new(format!(
|
||||||
|
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Ok if successful, Err if already initialized
|
||||||
|
let _ = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(env_filter)
|
||||||
|
.try_init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli";
|
const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli";
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
/// Address of RPC server
|
/// Address of RPC server
|
||||||
#[arg(short, long, default_value = "https://127.0.0.1:8086")]
|
#[arg(short, long, default_value = "https://127.0.0.1:8086")]
|
||||||
addr: String,
|
addr: String,
|
||||||
|
|
||||||
/// Logging level
|
|
||||||
#[arg(short, long, default_value = "debug")]
|
|
||||||
log_level: Level,
|
|
||||||
|
|
||||||
/// Path to working dir
|
/// Path to working dir
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
work_dir: Option<PathBuf>,
|
work_dir: Option<PathBuf>,
|
||||||
@@ -70,14 +103,9 @@ enum Commands {
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let args: Cli = Cli::parse();
|
let args: Cli = Cli::parse();
|
||||||
let default_filter = args.log_level;
|
|
||||||
|
|
||||||
let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
|
// Initialize logging based on CLI arguments
|
||||||
|
init_logging(args.common.enable_logging, args.common.log_level);
|
||||||
let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter}"));
|
|
||||||
|
|
||||||
// Parse input
|
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
|||||||
@@ -24,4 +24,12 @@ pub struct CLIArgs {
|
|||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
#[arg(short, long, help = "Recover Greenlight from seed", required = false)]
|
#[arg(short, long, help = "Recover Greenlight from seed", required = false)]
|
||||||
pub recover: Option<String>,
|
pub recover: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Enable logging output",
|
||||||
|
required = false,
|
||||||
|
action = clap::ArgAction::SetTrue,
|
||||||
|
default_value = "true"
|
||||||
|
)]
|
||||||
|
pub enable_logging: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,66 @@
|
|||||||
//! Cdk mintd lib
|
//! Cdk mintd lib
|
||||||
|
|
||||||
#[cfg(feature = "cln")]
|
// std
|
||||||
use std::path::PathBuf;
|
#[cfg(feature = "auth")]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// external crates
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use axum::Router;
|
||||||
|
use bip39::Mnemonic;
|
||||||
|
// internal crate modules
|
||||||
|
use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
|
||||||
|
use cdk::cdk_payment;
|
||||||
|
use cdk::cdk_payment::MintPayment;
|
||||||
|
use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "cln",
|
||||||
|
feature = "lnbits",
|
||||||
|
feature = "lnd",
|
||||||
|
feature = "fakewallet",
|
||||||
|
feature = "grpc-processor"
|
||||||
|
))]
|
||||||
|
use cdk::nuts::nut17::SupportedMethods;
|
||||||
|
use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "cln",
|
||||||
|
feature = "lnbits",
|
||||||
|
feature = "lnd",
|
||||||
|
feature = "fakewallet"
|
||||||
|
))]
|
||||||
|
use cdk::nuts::CurrencyUnit;
|
||||||
|
#[cfg(feature = "auth")]
|
||||||
|
use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
|
||||||
|
use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
|
||||||
|
use cdk::types::QuoteTTL;
|
||||||
|
use cdk_axum::cache::HttpCache;
|
||||||
|
#[cfg(feature = "auth")]
|
||||||
|
use cdk_sqlite::mint::MintSqliteAuthDatabase;
|
||||||
|
use cdk_sqlite::MintSqliteDatabase;
|
||||||
|
use cli::CLIArgs;
|
||||||
|
use config::{DatabaseEngine, LnBackend};
|
||||||
|
use env_vars::ENV_WORK_DIR;
|
||||||
|
use setup::LnBackendSetup;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use tower_http::compression::CompressionLayer;
|
||||||
|
use tower_http::decompression::RequestDecompressionLayer;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
#[cfg(feature = "swagger")]
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod env_vars;
|
pub mod env_vars;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
|
const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[cfg(feature = "cln")]
|
#[cfg(feature = "cln")]
|
||||||
fn expand_path(path: &str) -> Option<PathBuf> {
|
fn expand_path(path: &str) -> Option<PathBuf> {
|
||||||
if path.starts_with('~') {
|
if path.starts_with('~') {
|
||||||
@@ -23,3 +76,751 @@ fn expand_path(path: &str) -> Option<PathBuf> {
|
|||||||
Some(PathBuf::from(path))
|
Some(PathBuf::from(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs the initial setup for the application, including configuring tracing,
|
||||||
|
/// parsing CLI arguments, setting up the working directory, loading settings,
|
||||||
|
/// and initializing the database connection.
|
||||||
|
async fn initial_setup(
|
||||||
|
work_dir: &Path,
|
||||||
|
settings: &config::Settings,
|
||||||
|
db_password: Option<String>,
|
||||||
|
) -> Result<(
|
||||||
|
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
||||||
|
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
||||||
|
)> {
|
||||||
|
let (localstore, keystore) = setup_database(settings, work_dir, db_password).await?;
|
||||||
|
Ok((localstore, keystore))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up and initializes a tracing subscriber with custom log filtering.
|
||||||
|
pub fn setup_tracing() {
|
||||||
|
let default_filter = "debug";
|
||||||
|
let hyper_filter = "hyper=warn";
|
||||||
|
let h2_filter = "h2=warn";
|
||||||
|
let tower_http = "tower_http=warn";
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::new(format!(
|
||||||
|
"{default_filter},{hyper_filter},{h2_filter},{tower_http}"
|
||||||
|
));
|
||||||
|
|
||||||
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
|
||||||
|
pub async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
|
||||||
|
let work_dir = if let Some(work_dir) = &args.work_dir {
|
||||||
|
tracing::info!("Using work dir from cmd arg");
|
||||||
|
work_dir.clone()
|
||||||
|
} else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
|
||||||
|
tracing::info!("Using work dir from env var");
|
||||||
|
env_work_dir.into()
|
||||||
|
} else {
|
||||||
|
work_dir()?
|
||||||
|
};
|
||||||
|
tracing::info!("Using work dir: {}", work_dir.display());
|
||||||
|
Ok(work_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the application settings based on a configuration file and environment variables.
|
||||||
|
pub fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
|
||||||
|
// get config file name from args
|
||||||
|
let config_file_arg = match config_path {
|
||||||
|
Some(c) => c,
|
||||||
|
None => work_dir.join("config.toml"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut settings = if config_file_arg.exists() {
|
||||||
|
config::Settings::new(Some(config_file_arg))
|
||||||
|
} else {
|
||||||
|
tracing::info!("Config file does not exist. Attempting to read env vars");
|
||||||
|
config::Settings::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// This check for any settings defined in ENV VARs
|
||||||
|
// ENV VARS will take **priority** over those in the config
|
||||||
|
settings.from_env()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_database(
|
||||||
|
settings: &config::Settings,
|
||||||
|
work_dir: &Path,
|
||||||
|
db_password: Option<String>,
|
||||||
|
) -> Result<(
|
||||||
|
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
||||||
|
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
||||||
|
)> {
|
||||||
|
match settings.database.engine {
|
||||||
|
DatabaseEngine::Sqlite => {
|
||||||
|
let db = setup_sqlite_database(work_dir, db_password).await?;
|
||||||
|
let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
|
||||||
|
let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
|
||||||
|
Ok((localstore, keystore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_sqlite_database(
|
||||||
|
work_dir: &Path,
|
||||||
|
_password: Option<String>,
|
||||||
|
) -> Result<Arc<MintSqliteDatabase>> {
|
||||||
|
let sql_db_path = work_dir.join("cdk-mintd.sqlite");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sqlcipher"))]
|
||||||
|
let db = MintSqliteDatabase::new(&sql_db_path).await?;
|
||||||
|
#[cfg(feature = "sqlcipher")]
|
||||||
|
let db = {
|
||||||
|
// Get password from command line arguments for sqlcipher
|
||||||
|
MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Arc::new(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a `MintBuilder` instance with provided settings and initializes
|
||||||
|
* routers for Lightning Network backends.
|
||||||
|
*/
|
||||||
|
async fn configure_mint_builder(
|
||||||
|
settings: &config::Settings,
|
||||||
|
mint_builder: MintBuilder,
|
||||||
|
) -> Result<(MintBuilder, Vec<Router>)> {
|
||||||
|
let mut ln_routers = vec![];
|
||||||
|
|
||||||
|
// Configure basic mint information
|
||||||
|
let mint_builder = configure_basic_info(settings, mint_builder);
|
||||||
|
|
||||||
|
// Configure lightning backend
|
||||||
|
let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
|
||||||
|
|
||||||
|
// Configure caching
|
||||||
|
let mint_builder = configure_cache(settings, mint_builder);
|
||||||
|
|
||||||
|
Ok((mint_builder, ln_routers))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures basic mint information (name, contact info, descriptions, etc.)
|
||||||
|
fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
||||||
|
// Add contact information
|
||||||
|
let mut contacts = Vec::new();
|
||||||
|
if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
|
||||||
|
contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(email) = &settings.mint_info.contact_email {
|
||||||
|
contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add version information
|
||||||
|
let mint_version = MintVersion::new(
|
||||||
|
"cdk-mintd".to_string(),
|
||||||
|
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configure mint builder with basic info
|
||||||
|
let mut builder = mint_builder
|
||||||
|
.with_name(settings.mint_info.name.clone())
|
||||||
|
.with_version(mint_version)
|
||||||
|
.with_description(settings.mint_info.description.clone());
|
||||||
|
|
||||||
|
// Add optional information
|
||||||
|
if let Some(long_description) = &settings.mint_info.description_long {
|
||||||
|
builder = builder.with_long_description(long_description.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
for contact in contacts {
|
||||||
|
builder = builder.with_contact_info(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pubkey) = settings.mint_info.pubkey {
|
||||||
|
builder = builder.with_pubkey(pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(icon_url) = &settings.mint_info.icon_url {
|
||||||
|
builder = builder.with_icon_url(icon_url.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(motd) = &settings.mint_info.motd {
|
||||||
|
builder = builder.with_motd(motd.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tos_url) = &settings.mint_info.tos_url {
|
||||||
|
builder = builder.with_tos_url(tos_url.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
/// Configures Lightning Network backend based on the specified backend type
|
||||||
|
async fn configure_lightning_backend(
|
||||||
|
settings: &config::Settings,
|
||||||
|
mut mint_builder: MintBuilder,
|
||||||
|
ln_routers: &mut Vec<Router>,
|
||||||
|
) -> Result<MintBuilder> {
|
||||||
|
let mint_melt_limits = MintMeltLimits {
|
||||||
|
mint_min: settings.ln.min_mint,
|
||||||
|
mint_max: settings.ln.max_mint,
|
||||||
|
melt_min: settings.ln.min_melt,
|
||||||
|
melt_max: settings.ln.max_melt,
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
|
||||||
|
|
||||||
|
match settings.ln.ln_backend {
|
||||||
|
#[cfg(feature = "cln")]
|
||||||
|
LnBackend::Cln => {
|
||||||
|
let cln_settings = settings
|
||||||
|
.cln
|
||||||
|
.clone()
|
||||||
|
.expect("Config checked at load that cln is some");
|
||||||
|
let cln = cln_settings
|
||||||
|
.setup(ln_routers, settings, CurrencyUnit::Msat)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
mint_builder = configure_backend_for_unit(
|
||||||
|
settings,
|
||||||
|
mint_builder,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::new(cln),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "lnbits")]
|
||||||
|
LnBackend::LNbits => {
|
||||||
|
let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
|
||||||
|
let lnbits = lnbits_settings
|
||||||
|
.setup(ln_routers, settings, CurrencyUnit::Sat)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
mint_builder = configure_backend_for_unit(
|
||||||
|
settings,
|
||||||
|
mint_builder,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::new(lnbits),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "lnd")]
|
||||||
|
LnBackend::Lnd => {
|
||||||
|
let lnd_settings = settings.clone().lnd.expect("Checked at config load");
|
||||||
|
let lnd = lnd_settings
|
||||||
|
.setup(ln_routers, settings, CurrencyUnit::Msat)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
mint_builder = configure_backend_for_unit(
|
||||||
|
settings,
|
||||||
|
mint_builder,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::new(lnd),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "fakewallet")]
|
||||||
|
LnBackend::FakeWallet => {
|
||||||
|
let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
|
||||||
|
tracing::info!("Using fake wallet: {:?}", fake_wallet);
|
||||||
|
|
||||||
|
for unit in fake_wallet.clone().supported_units {
|
||||||
|
let fake = fake_wallet
|
||||||
|
.setup(ln_routers, settings, CurrencyUnit::Sat)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
mint_builder = configure_backend_for_unit(
|
||||||
|
settings,
|
||||||
|
mint_builder,
|
||||||
|
unit.clone(),
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::new(fake),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "grpc-processor")]
|
||||||
|
LnBackend::GrpcProcessor => {
|
||||||
|
let grpc_processor = settings
|
||||||
|
.clone()
|
||||||
|
.grpc_processor
|
||||||
|
.expect("grpc processor config defined");
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Attempting to start with gRPC payment processor at {}:{}.",
|
||||||
|
grpc_processor.addr,
|
||||||
|
grpc_processor.port
|
||||||
|
);
|
||||||
|
|
||||||
|
for unit in grpc_processor.clone().supported_units {
|
||||||
|
tracing::debug!("Adding unit: {:?}", unit);
|
||||||
|
let processor = grpc_processor
|
||||||
|
.setup(ln_routers, settings, unit.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
mint_builder = configure_backend_for_unit(
|
||||||
|
settings,
|
||||||
|
mint_builder,
|
||||||
|
unit.clone(),
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::new(processor),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LnBackend::None => {
|
||||||
|
tracing::error!(
|
||||||
|
"Payment backend was not set or feature disabled. {:?}",
|
||||||
|
settings.ln.ln_backend
|
||||||
|
);
|
||||||
|
bail!("Lightning backend must be configured");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(mint_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to configure a mint builder with a lightning backend for a specific currency unit
|
||||||
|
async fn configure_backend_for_unit(
|
||||||
|
settings: &config::Settings,
|
||||||
|
mut mint_builder: MintBuilder,
|
||||||
|
unit: cdk::nuts::CurrencyUnit,
|
||||||
|
mint_melt_limits: MintMeltLimits,
|
||||||
|
backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
|
||||||
|
) -> Result<MintBuilder> {
|
||||||
|
let payment_settings = backend.get_settings().await?;
|
||||||
|
|
||||||
|
if let Some(bolt12) = payment_settings.get("bolt12") {
|
||||||
|
if bolt12.as_bool().unwrap_or_default() {
|
||||||
|
mint_builder
|
||||||
|
.add_payment_processor(
|
||||||
|
unit.clone(),
|
||||||
|
PaymentMethod::Bolt12,
|
||||||
|
mint_melt_limits,
|
||||||
|
Arc::clone(&backend),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_builder
|
||||||
|
.add_payment_processor(
|
||||||
|
unit.clone(),
|
||||||
|
PaymentMethod::Bolt11,
|
||||||
|
mint_melt_limits,
|
||||||
|
backend,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||||
|
mint_builder.set_unit_fee(&unit, input_fee)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nut17_supported = SupportedMethods::default_bolt11(unit);
|
||||||
|
mint_builder = mint_builder.with_supported_websockets(nut17_supported);
|
||||||
|
|
||||||
|
Ok(mint_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures cache settings
|
||||||
|
fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
||||||
|
let cached_endpoints = vec![
|
||||||
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
|
||||||
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
|
||||||
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
|
||||||
|
];
|
||||||
|
|
||||||
|
let cache: HttpCache = settings.info.http_cache.clone().into();
|
||||||
|
mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "auth")]
|
||||||
|
async fn setup_authentication(
|
||||||
|
settings: &config::Settings,
|
||||||
|
work_dir: &Path,
|
||||||
|
mut mint_builder: MintBuilder,
|
||||||
|
_password: Option<String>,
|
||||||
|
) -> Result<MintBuilder> {
|
||||||
|
if let Some(auth_settings) = settings.auth.clone() {
|
||||||
|
tracing::info!("Auth settings are defined. {:?}", auth_settings);
|
||||||
|
let auth_localstore: Arc<
|
||||||
|
dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
|
||||||
|
> = match settings.database.engine {
|
||||||
|
DatabaseEngine::Sqlite => {
|
||||||
|
let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
|
||||||
|
#[cfg(not(feature = "sqlcipher"))]
|
||||||
|
let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
|
||||||
|
#[cfg(feature = "sqlcipher")]
|
||||||
|
let sqlite_db = {
|
||||||
|
// Get password from command line arguments for sqlcipher
|
||||||
|
MintSqliteAuthDatabase::new((sql_db_path, _password.unwrap())).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
Arc::new(sqlite_db)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mint_blind_auth_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
|
||||||
|
|
||||||
|
let mut protected_endpoints = HashMap::new();
|
||||||
|
|
||||||
|
protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
|
||||||
|
|
||||||
|
let mut blind_auth_endpoints = vec![];
|
||||||
|
let mut unprotected_endpoints = vec![];
|
||||||
|
|
||||||
|
{
|
||||||
|
let mint_quote_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
|
||||||
|
let mint_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
|
||||||
|
if auth_settings.enabled_mint {
|
||||||
|
protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
|
||||||
|
|
||||||
|
protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
|
||||||
|
|
||||||
|
blind_auth_endpoints.push(mint_quote_protected_endpoint);
|
||||||
|
blind_auth_endpoints.push(mint_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(mint_protected_endpoint);
|
||||||
|
unprotected_endpoints.push(mint_quote_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let melt_quote_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
|
||||||
|
let melt_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
|
||||||
|
|
||||||
|
if auth_settings.enabled_melt {
|
||||||
|
protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
|
||||||
|
protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
|
||||||
|
|
||||||
|
blind_auth_endpoints.push(melt_quote_protected_endpoint);
|
||||||
|
blind_auth_endpoints.push(melt_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(melt_quote_protected_endpoint);
|
||||||
|
unprotected_endpoints.push(melt_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
|
||||||
|
|
||||||
|
if auth_settings.enabled_swap {
|
||||||
|
protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
|
||||||
|
blind_auth_endpoints.push(swap_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(swap_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let check_mint_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
|
||||||
|
|
||||||
|
if auth_settings.enabled_check_mint_quote {
|
||||||
|
protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
|
||||||
|
blind_auth_endpoints.push(check_mint_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(check_mint_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let check_melt_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
|
||||||
|
|
||||||
|
if auth_settings.enabled_check_melt_quote {
|
||||||
|
protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
|
||||||
|
blind_auth_endpoints.push(check_melt_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(check_melt_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let restore_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
|
||||||
|
|
||||||
|
if auth_settings.enabled_restore {
|
||||||
|
protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
|
||||||
|
blind_auth_endpoints.push(restore_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(restore_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let state_protected_endpoint =
|
||||||
|
ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
|
||||||
|
|
||||||
|
if auth_settings.enabled_check_proof_state {
|
||||||
|
protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
|
||||||
|
blind_auth_endpoints.push(state_protected_endpoint);
|
||||||
|
} else {
|
||||||
|
unprotected_endpoints.push(state_protected_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_builder = mint_builder.with_auth(
|
||||||
|
auth_localstore.clone(),
|
||||||
|
auth_settings.openid_discovery,
|
||||||
|
auth_settings.openid_client_id,
|
||||||
|
vec![mint_blind_auth_endpoint],
|
||||||
|
);
|
||||||
|
mint_builder =
|
||||||
|
mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
|
||||||
|
|
||||||
|
let mut tx = auth_localstore.begin_transaction().await?;
|
||||||
|
|
||||||
|
tx.remove_protected_endpoints(unprotected_endpoints).await?;
|
||||||
|
tx.add_protected_endpoints(protected_endpoints).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
}
|
||||||
|
Ok(mint_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build mints with the configured the signing method (remote signatory or local seed)
|
||||||
|
async fn build_mint(
|
||||||
|
settings: &config::Settings,
|
||||||
|
keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
||||||
|
mint_builder: MintBuilder,
|
||||||
|
) -> Result<Mint> {
|
||||||
|
if let Some(signatory_url) = settings.info.signatory_url.clone() {
|
||||||
|
tracing::info!(
|
||||||
|
"Connecting to remote signatory to {} with certs {:?}",
|
||||||
|
signatory_url,
|
||||||
|
settings.info.signatory_certs.clone()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(mint_builder
|
||||||
|
.build_with_signatory(Arc::new(
|
||||||
|
cdk_signatory::SignatoryRpcClient::new(
|
||||||
|
signatory_url,
|
||||||
|
settings.info.signatory_certs.clone(),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
.await?)
|
||||||
|
} else if let Some(mnemonic) = settings
|
||||||
|
.info
|
||||||
|
.mnemonic
|
||||||
|
.clone()
|
||||||
|
.map(|s| Mnemonic::from_str(&s))
|
||||||
|
.transpose()?
|
||||||
|
{
|
||||||
|
Ok(mint_builder
|
||||||
|
.build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
|
||||||
|
.await?)
|
||||||
|
} else {
|
||||||
|
bail!("No seed nor remote signatory set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_services_with_shutdown(
|
||||||
|
mint: Arc<cdk::mint::Mint>,
|
||||||
|
settings: &config::Settings,
|
||||||
|
ln_routers: Vec<Router>,
|
||||||
|
work_dir: &Path,
|
||||||
|
mint_builder_info: cdk::nuts::MintInfo,
|
||||||
|
shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
|
||||||
|
) -> Result<()> {
|
||||||
|
let listen_addr = settings.info.listen_host.clone();
|
||||||
|
let listen_port = settings.info.listen_port;
|
||||||
|
let cache: HttpCache = settings.info.http_cache.clone().into();
|
||||||
|
|
||||||
|
#[cfg(feature = "management-rpc")]
|
||||||
|
let mut rpc_enabled = false;
|
||||||
|
#[cfg(not(feature = "management-rpc"))]
|
||||||
|
let rpc_enabled = false;
|
||||||
|
|
||||||
|
#[cfg(feature = "management-rpc")]
|
||||||
|
let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
|
||||||
|
|
||||||
|
#[cfg(feature = "management-rpc")]
|
||||||
|
{
|
||||||
|
if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
|
||||||
|
if rpc_settings.enabled {
|
||||||
|
let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
|
||||||
|
let port = rpc_settings.port.unwrap_or(8086);
|
||||||
|
let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
|
||||||
|
|
||||||
|
let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
|
||||||
|
|
||||||
|
if !tls_dir.exists() {
|
||||||
|
tracing::error!("TLS directory does not exist: {}", tls_dir.display());
|
||||||
|
bail!("Cannot start RPC server: TLS directory does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_rpc.start(Some(tls_dir)).await?;
|
||||||
|
|
||||||
|
rpc_server = Some(mint_rpc);
|
||||||
|
|
||||||
|
rpc_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rpc_enabled {
|
||||||
|
if mint.mint_info().await.is_err() {
|
||||||
|
tracing::info!("Mint info not set on mint, setting.");
|
||||||
|
mint.set_mint_info(mint_builder_info).await?;
|
||||||
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
||||||
|
} else {
|
||||||
|
if mint.localstore().get_quote_ttl().await.is_err() {
|
||||||
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
||||||
|
}
|
||||||
|
// Add version information
|
||||||
|
let mint_version = MintVersion::new(
|
||||||
|
"cdk-mintd".to_string(),
|
||||||
|
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
||||||
|
);
|
||||||
|
let mut stored_mint_info = mint.mint_info().await?;
|
||||||
|
stored_mint_info.version = Some(mint_version);
|
||||||
|
mint.set_mint_info(stored_mint_info).await?;
|
||||||
|
|
||||||
|
tracing::info!("Mint info already set, not using config file settings.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!("RPC not enabled, using mint info from config.");
|
||||||
|
mint.set_mint_info(mint_builder_info).await?;
|
||||||
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mint_info = mint.mint_info().await?;
|
||||||
|
let nut04_methods = mint_info.nuts.nut04.supported_methods();
|
||||||
|
let nut05_methods = mint_info.nuts.nut05.supported_methods();
|
||||||
|
|
||||||
|
let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
|
||||||
|
|| nut05_methods.contains(&&PaymentMethod::Bolt12);
|
||||||
|
|
||||||
|
let v1_service =
|
||||||
|
cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut mint_service = Router::new()
|
||||||
|
.merge(v1_service)
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(RequestDecompressionLayer::new())
|
||||||
|
.layer(CompressionLayer::new()),
|
||||||
|
)
|
||||||
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
||||||
|
#[cfg(feature = "swagger")]
|
||||||
|
{
|
||||||
|
if settings.info.enable_swagger_ui.unwrap_or(false) {
|
||||||
|
mint_service = mint_service.merge(
|
||||||
|
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
|
||||||
|
.url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for router in ln_routers {
|
||||||
|
mint_service = mint_service.merge(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
mint.start().await?;
|
||||||
|
|
||||||
|
let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(socket_addr).await?;
|
||||||
|
|
||||||
|
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
||||||
|
|
||||||
|
// Wait for axum server to complete with custom shutdown signal
|
||||||
|
let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal);
|
||||||
|
|
||||||
|
match axum_result.await {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::info!("Axum server stopped with okay status");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Axum server stopped with error");
|
||||||
|
tracing::error!("{}", err);
|
||||||
|
bail!("Axum exited with error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mint.stop().await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "management-rpc")]
|
||||||
|
{
|
||||||
|
if let Some(rpc_server) = rpc_server {
|
||||||
|
rpc_server.stop().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown_signal() {
|
||||||
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install CTRL+C handler");
|
||||||
|
tracing::info!("Shutdown signal received");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn work_dir() -> Result<PathBuf> {
|
||||||
|
let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
|
||||||
|
let dir = home_dir.join(".cdk-mintd");
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
|
Ok(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main entry point for the application when used as a library.
|
||||||
|
///
|
||||||
|
/// This asynchronous function performs the following steps:
|
||||||
|
/// 1. Executes the initial setup, including loading configurations and initializing the database.
|
||||||
|
/// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
|
||||||
|
/// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
|
||||||
|
/// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
|
||||||
|
/// 5. Checks and resolves the status of any pending mint and melt quotes.
|
||||||
|
pub async fn run_mintd(
|
||||||
|
work_dir: &Path,
|
||||||
|
settings: &config::Settings,
|
||||||
|
db_password: Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
run_mintd_with_shutdown(work_dir, settings, shutdown_signal(), db_password).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run mintd with a custom shutdown signal
|
||||||
|
pub async fn run_mintd_with_shutdown(
|
||||||
|
work_dir: &Path,
|
||||||
|
settings: &config::Settings,
|
||||||
|
shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
|
||||||
|
db_password: Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (localstore, keystore) = initial_setup(work_dir, settings, db_password.clone()).await?;
|
||||||
|
|
||||||
|
let mint_builder = MintBuilder::new(localstore);
|
||||||
|
|
||||||
|
let (mint_builder, ln_routers) = configure_mint_builder(settings, mint_builder).await?;
|
||||||
|
#[cfg(feature = "auth")]
|
||||||
|
let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
|
||||||
|
|
||||||
|
let mint = build_mint(settings, keystore, mint_builder).await?;
|
||||||
|
|
||||||
|
tracing::debug!("Mint built from builder.");
|
||||||
|
|
||||||
|
let mint = Arc::new(mint);
|
||||||
|
|
||||||
|
// Checks the status of all pending melt quotes
|
||||||
|
// Pending melt quotes where the payment has gone through inputs are burnt
|
||||||
|
// Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
||||||
|
mint.check_pending_melt_quotes().await?;
|
||||||
|
|
||||||
|
start_services_with_shutdown(
|
||||||
|
mint.clone(),
|
||||||
|
settings,
|
||||||
|
ln_routers,
|
||||||
|
work_dir,
|
||||||
|
mint.mint_info().await?,
|
||||||
|
shutdown_signal,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,63 +2,6 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![warn(rustdoc::bare_urls)]
|
#![warn(rustdoc::bare_urls)]
|
||||||
|
|
||||||
// std
|
|
||||||
#[cfg(feature = "auth")]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
// external crates
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
|
||||||
use axum::Router;
|
|
||||||
use bip39::Mnemonic;
|
|
||||||
// internal crate modules
|
|
||||||
use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
|
|
||||||
use cdk::cdk_payment;
|
|
||||||
use cdk::cdk_payment::MintPayment;
|
|
||||||
use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "cln",
|
|
||||||
feature = "lnbits",
|
|
||||||
feature = "lnd",
|
|
||||||
feature = "fakewallet",
|
|
||||||
feature = "grpc-processor"
|
|
||||||
))]
|
|
||||||
use cdk::nuts::nut17::SupportedMethods;
|
|
||||||
use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "cln",
|
|
||||||
feature = "lnbits",
|
|
||||||
feature = "lnd",
|
|
||||||
feature = "fakewallet"
|
|
||||||
))]
|
|
||||||
use cdk::nuts::CurrencyUnit;
|
|
||||||
#[cfg(feature = "auth")]
|
|
||||||
use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
|
|
||||||
use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
|
|
||||||
use cdk::types::QuoteTTL;
|
|
||||||
use cdk_axum::cache::HttpCache;
|
|
||||||
use cdk_mintd::cli::CLIArgs;
|
|
||||||
use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
|
|
||||||
use cdk_mintd::env_vars::ENV_WORK_DIR;
|
|
||||||
use cdk_mintd::setup::LnBackendSetup;
|
|
||||||
#[cfg(feature = "auth")]
|
|
||||||
use cdk_sqlite::mint::MintSqliteAuthDatabase;
|
|
||||||
use cdk_sqlite::MintSqliteDatabase;
|
|
||||||
use clap::Parser;
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
use tower_http::compression::CompressionLayer;
|
|
||||||
use tower_http::decompression::RequestDecompressionLayer;
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
#[cfg(feature = "swagger")]
|
|
||||||
use utoipa::OpenApi;
|
|
||||||
|
|
||||||
const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
// Ensure at least one lightning backend is enabled at compile time
|
// Ensure at least one lightning backend is enabled at compile time
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(
|
||||||
feature = "cln",
|
feature = "cln",
|
||||||
@@ -71,737 +14,29 @@ compile_error!(
|
|||||||
"At least one lightning backend feature must be enabled: cln, lnbits, lnd, fakewallet, or grpc-processor"
|
"At least one lightning backend feature must be enabled: cln, lnbits, lnd, fakewallet, or grpc-processor"
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The main entry point for the application.
|
use anyhow::Result;
|
||||||
///
|
use cdk_mintd::cli::CLIArgs;
|
||||||
/// This asynchronous function performs the following steps:
|
use cdk_mintd::{get_work_directory, load_settings, setup_tracing};
|
||||||
/// 1. Executes the initial setup, including loading configurations and initializing the database.
|
use clap::Parser;
|
||||||
/// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
|
use tokio::main;
|
||||||
/// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
|
|
||||||
/// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
|
#[main]
|
||||||
/// 5. Checks and resolves the status of any pending mint and melt quotes.
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let (work_dir, settings, localstore, keystore) = initial_setup().await?;
|
|
||||||
|
|
||||||
let mint_builder = MintBuilder::new(localstore);
|
|
||||||
|
|
||||||
let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?;
|
|
||||||
#[cfg(feature = "auth")]
|
|
||||||
let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?;
|
|
||||||
|
|
||||||
let mint = build_mint(&settings, keystore, mint_builder).await?;
|
|
||||||
|
|
||||||
tracing::debug!("Mint built from builder.");
|
|
||||||
|
|
||||||
let mint = Arc::new(mint);
|
|
||||||
|
|
||||||
// Checks the status of all pending melt quotes
|
|
||||||
// Pending melt quotes where the payment has gone through inputs are burnt
|
|
||||||
// Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
|
||||||
mint.check_pending_melt_quotes().await?;
|
|
||||||
|
|
||||||
start_services(
|
|
||||||
mint.clone(),
|
|
||||||
&settings,
|
|
||||||
ln_routers,
|
|
||||||
&work_dir,
|
|
||||||
mint.mint_info().await?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs the initial setup for the application, including configuring tracing,
|
|
||||||
/// parsing CLI arguments, setting up the working directory, loading settings,
|
|
||||||
/// and initializing the database connection.
|
|
||||||
async fn initial_setup() -> Result<(
|
|
||||||
PathBuf,
|
|
||||||
config::Settings,
|
|
||||||
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
|
||||||
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
|
||||||
)> {
|
|
||||||
setup_tracing();
|
|
||||||
let args = CLIArgs::parse();
|
let args = CLIArgs::parse();
|
||||||
|
|
||||||
|
if args.enable_logging {
|
||||||
|
setup_tracing();
|
||||||
|
}
|
||||||
|
|
||||||
let work_dir = get_work_directory(&args).await?;
|
let work_dir = get_work_directory(&args).await?;
|
||||||
|
|
||||||
let settings = load_settings(&work_dir, args.config)?;
|
let settings = load_settings(&work_dir, args.config)?;
|
||||||
let (localstore, keystore) = setup_database(&settings, &work_dir).await?;
|
|
||||||
Ok((work_dir, settings, localstore, keystore))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up and initializes a tracing subscriber with custom log filtering.
|
|
||||||
fn setup_tracing() {
|
|
||||||
let default_filter = "debug";
|
|
||||||
let sqlx_filter = "sqlx=warn";
|
|
||||||
let hyper_filter = "hyper=warn";
|
|
||||||
let h2_filter = "h2=warn";
|
|
||||||
let tower_http = "tower_http=warn";
|
|
||||||
|
|
||||||
let env_filter = EnvFilter::new(format!(
|
|
||||||
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{tower_http}"
|
|
||||||
));
|
|
||||||
|
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
|
|
||||||
async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
|
|
||||||
let work_dir = if let Some(work_dir) = &args.work_dir {
|
|
||||||
tracing::info!("Using work dir from cmd arg");
|
|
||||||
work_dir.clone()
|
|
||||||
} else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
|
|
||||||
tracing::info!("Using work dir from env var");
|
|
||||||
env_work_dir.into()
|
|
||||||
} else {
|
|
||||||
work_dir()?
|
|
||||||
};
|
|
||||||
tracing::info!("Using work dir: {}", work_dir.display());
|
|
||||||
Ok(work_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads the application settings based on a configuration file and environment variables.
|
|
||||||
fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
|
|
||||||
// get config file name from args
|
|
||||||
let config_file_arg = match config_path {
|
|
||||||
Some(c) => c,
|
|
||||||
None => work_dir.join("config.toml"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut settings = if config_file_arg.exists() {
|
|
||||||
config::Settings::new(Some(config_file_arg))
|
|
||||||
} else {
|
|
||||||
tracing::info!("Config file does not exist. Attempting to read env vars");
|
|
||||||
config::Settings::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// This check for any settings defined in ENV VARs
|
|
||||||
// ENV VARS will take **priority** over those in the config
|
|
||||||
settings.from_env()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn setup_database(
|
|
||||||
settings: &config::Settings,
|
|
||||||
work_dir: &Path,
|
|
||||||
) -> Result<(
|
|
||||||
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
|
||||||
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
|
||||||
)> {
|
|
||||||
match settings.database.engine {
|
|
||||||
DatabaseEngine::Sqlite => {
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
#[cfg(feature = "sqlcipher")]
|
||||||
let password = CLIArgs::parse().password;
|
let password = Some(CLIArgs::parse().password);
|
||||||
|
|
||||||
#[cfg(not(feature = "sqlcipher"))]
|
#[cfg(not(feature = "sqlcipher"))]
|
||||||
let password = String::new();
|
let password = None;
|
||||||
let db = setup_sqlite_database(work_dir, Some(password)).await?;
|
|
||||||
let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
|
cdk_mintd::run_mintd(&work_dir, &settings, password).await
|
||||||
let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
|
|
||||||
Ok((localstore, keystore))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn setup_sqlite_database(
|
|
||||||
work_dir: &Path,
|
|
||||||
_password: Option<String>,
|
|
||||||
) -> Result<Arc<MintSqliteDatabase>> {
|
|
||||||
let sql_db_path = work_dir.join("cdk-mintd.sqlite");
|
|
||||||
#[cfg(not(feature = "sqlcipher"))]
|
|
||||||
let db = MintSqliteDatabase::new(&sql_db_path).await?;
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
|
||||||
let db = {
|
|
||||||
// Get password from command line arguments for sqlcipher
|
|
||||||
MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
|
|
||||||
};
|
|
||||||
Ok(Arc::new(db))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures a `MintBuilder` instance with provided settings and initializes
|
|
||||||
* routers for Lightning Network backends.
|
|
||||||
*/
|
|
||||||
async fn configure_mint_builder(
|
|
||||||
settings: &config::Settings,
|
|
||||||
mint_builder: MintBuilder,
|
|
||||||
) -> Result<(MintBuilder, Vec<Router>)> {
|
|
||||||
let mut ln_routers = vec![];
|
|
||||||
|
|
||||||
// Configure basic mint information
|
|
||||||
let mint_builder = configure_basic_info(settings, mint_builder);
|
|
||||||
|
|
||||||
// Configure lightning backend
|
|
||||||
let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
|
|
||||||
|
|
||||||
// Configure caching
|
|
||||||
let mint_builder = configure_cache(settings, mint_builder);
|
|
||||||
|
|
||||||
Ok((mint_builder, ln_routers))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures basic mint information (name, contact info, descriptions, etc.)
|
|
||||||
fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
|
||||||
// Add contact information
|
|
||||||
let mut contacts = Vec::new();
|
|
||||||
if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
|
|
||||||
contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
|
|
||||||
}
|
|
||||||
if let Some(email) = &settings.mint_info.contact_email {
|
|
||||||
contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add version information
|
|
||||||
let mint_version = MintVersion::new(
|
|
||||||
"cdk-mintd".to_string(),
|
|
||||||
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Configure mint builder with basic info
|
|
||||||
let mut builder = mint_builder
|
|
||||||
.with_name(settings.mint_info.name.clone())
|
|
||||||
.with_version(mint_version)
|
|
||||||
.with_description(settings.mint_info.description.clone());
|
|
||||||
|
|
||||||
// Add optional information
|
|
||||||
if let Some(long_description) = &settings.mint_info.description_long {
|
|
||||||
builder = builder.with_long_description(long_description.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for contact in contacts {
|
|
||||||
builder = builder.with_contact_info(contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pubkey) = settings.mint_info.pubkey {
|
|
||||||
builder = builder.with_pubkey(pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(icon_url) = &settings.mint_info.icon_url {
|
|
||||||
builder = builder.with_icon_url(icon_url.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(motd) = &settings.mint_info.motd {
|
|
||||||
builder = builder.with_motd(motd.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(tos_url) = &settings.mint_info.tos_url {
|
|
||||||
builder = builder.with_tos_url(tos_url.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
/// Configures Lightning Network backend based on the specified backend type
|
|
||||||
async fn configure_lightning_backend(
|
|
||||||
settings: &config::Settings,
|
|
||||||
mut mint_builder: MintBuilder,
|
|
||||||
ln_routers: &mut Vec<Router>,
|
|
||||||
) -> Result<MintBuilder> {
|
|
||||||
let mint_melt_limits = MintMeltLimits {
|
|
||||||
mint_min: settings.ln.min_mint,
|
|
||||||
mint_max: settings.ln.max_mint,
|
|
||||||
melt_min: settings.ln.min_melt,
|
|
||||||
melt_max: settings.ln.max_melt,
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
|
|
||||||
|
|
||||||
match settings.ln.ln_backend {
|
|
||||||
#[cfg(feature = "cln")]
|
|
||||||
LnBackend::Cln => {
|
|
||||||
let cln_settings = settings
|
|
||||||
.cln
|
|
||||||
.clone()
|
|
||||||
.expect("Config checked at load that cln is some");
|
|
||||||
let cln = cln_settings
|
|
||||||
.setup(ln_routers, settings, CurrencyUnit::Msat)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mint_builder = configure_backend_for_unit(
|
|
||||||
settings,
|
|
||||||
mint_builder,
|
|
||||||
CurrencyUnit::Sat,
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::new(cln),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
#[cfg(feature = "lnbits")]
|
|
||||||
LnBackend::LNbits => {
|
|
||||||
let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
|
|
||||||
let lnbits = lnbits_settings
|
|
||||||
.setup(ln_routers, settings, CurrencyUnit::Sat)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mint_builder = configure_backend_for_unit(
|
|
||||||
settings,
|
|
||||||
mint_builder,
|
|
||||||
CurrencyUnit::Sat,
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::new(lnbits),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
#[cfg(feature = "lnd")]
|
|
||||||
LnBackend::Lnd => {
|
|
||||||
let lnd_settings = settings.clone().lnd.expect("Checked at config load");
|
|
||||||
let lnd = lnd_settings
|
|
||||||
.setup(ln_routers, settings, CurrencyUnit::Msat)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mint_builder = configure_backend_for_unit(
|
|
||||||
settings,
|
|
||||||
mint_builder,
|
|
||||||
CurrencyUnit::Sat,
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::new(lnd),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
#[cfg(feature = "fakewallet")]
|
|
||||||
LnBackend::FakeWallet => {
|
|
||||||
let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
|
|
||||||
tracing::info!("Using fake wallet: {:?}", fake_wallet);
|
|
||||||
|
|
||||||
for unit in fake_wallet.clone().supported_units {
|
|
||||||
let fake = fake_wallet
|
|
||||||
.setup(ln_routers, settings, CurrencyUnit::Sat)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mint_builder = configure_backend_for_unit(
|
|
||||||
settings,
|
|
||||||
mint_builder,
|
|
||||||
unit.clone(),
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::new(fake),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "grpc-processor")]
|
|
||||||
LnBackend::GrpcProcessor => {
|
|
||||||
let grpc_processor = settings
|
|
||||||
.clone()
|
|
||||||
.grpc_processor
|
|
||||||
.expect("grpc processor config defined");
|
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
"Attempting to start with gRPC payment processor at {}:{}.",
|
|
||||||
grpc_processor.addr,
|
|
||||||
grpc_processor.port
|
|
||||||
);
|
|
||||||
|
|
||||||
for unit in grpc_processor.clone().supported_units {
|
|
||||||
tracing::debug!("Adding unit: {:?}", unit);
|
|
||||||
let processor = grpc_processor
|
|
||||||
.setup(ln_routers, settings, unit.clone())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mint_builder = configure_backend_for_unit(
|
|
||||||
settings,
|
|
||||||
mint_builder,
|
|
||||||
unit.clone(),
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::new(processor),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LnBackend::None => {
|
|
||||||
tracing::error!(
|
|
||||||
"Payment backend was not set or feature disabled. {:?}",
|
|
||||||
settings.ln.ln_backend
|
|
||||||
);
|
|
||||||
bail!("Lightning backend must be configured");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(mint_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to configure a mint builder with a lightning backend for a specific currency unit
|
|
||||||
async fn configure_backend_for_unit(
|
|
||||||
settings: &config::Settings,
|
|
||||||
mut mint_builder: MintBuilder,
|
|
||||||
unit: cdk::nuts::CurrencyUnit,
|
|
||||||
mint_melt_limits: MintMeltLimits,
|
|
||||||
backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
|
|
||||||
) -> Result<MintBuilder> {
|
|
||||||
let payment_settings = backend.get_settings().await?;
|
|
||||||
|
|
||||||
if let Some(bolt12) = payment_settings.get("bolt12") {
|
|
||||||
if bolt12.as_bool().unwrap_or_default() {
|
|
||||||
mint_builder
|
|
||||||
.add_payment_processor(
|
|
||||||
unit.clone(),
|
|
||||||
PaymentMethod::Bolt12,
|
|
||||||
mint_melt_limits,
|
|
||||||
Arc::clone(&backend),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mint_builder
|
|
||||||
.add_payment_processor(
|
|
||||||
unit.clone(),
|
|
||||||
PaymentMethod::Bolt11,
|
|
||||||
mint_melt_limits,
|
|
||||||
backend,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
||||||
mint_builder.set_unit_fee(&unit, input_fee)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let nut17_supported = SupportedMethods::default_bolt11(unit);
|
|
||||||
mint_builder = mint_builder.with_supported_websockets(nut17_supported);
|
|
||||||
|
|
||||||
Ok(mint_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures cache settings
|
|
||||||
fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
|
||||||
let cached_endpoints = vec![
|
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
|
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
|
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
|
|
||||||
];
|
|
||||||
|
|
||||||
let cache: HttpCache = settings.info.http_cache.clone().into();
|
|
||||||
mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "auth")]
|
|
||||||
async fn setup_authentication(
|
|
||||||
settings: &config::Settings,
|
|
||||||
work_dir: &Path,
|
|
||||||
mut mint_builder: MintBuilder,
|
|
||||||
) -> Result<MintBuilder> {
|
|
||||||
if let Some(auth_settings) = settings.auth.clone() {
|
|
||||||
tracing::info!("Auth settings are defined. {:?}", auth_settings);
|
|
||||||
let auth_localstore: Arc<
|
|
||||||
dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
|
|
||||||
> = match settings.database.engine {
|
|
||||||
DatabaseEngine::Sqlite => {
|
|
||||||
let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
|
||||||
let password = CLIArgs::parse().password;
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
|
||||||
let sqlite_db = MintSqliteAuthDatabase::new((sql_db_path, password)).await?;
|
|
||||||
#[cfg(not(feature = "sqlcipher"))]
|
|
||||||
let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
|
|
||||||
Arc::new(sqlite_db)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mint_blind_auth_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
|
|
||||||
|
|
||||||
let mut protected_endpoints = HashMap::new();
|
|
||||||
|
|
||||||
protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
|
|
||||||
|
|
||||||
let mut blind_auth_endpoints = vec![];
|
|
||||||
let mut unprotected_endpoints = vec![];
|
|
||||||
|
|
||||||
{
|
|
||||||
let mint_quote_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
|
|
||||||
let mint_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
|
|
||||||
if auth_settings.enabled_mint {
|
|
||||||
protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
|
|
||||||
|
|
||||||
protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
|
|
||||||
|
|
||||||
blind_auth_endpoints.push(mint_quote_protected_endpoint);
|
|
||||||
blind_auth_endpoints.push(mint_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(mint_protected_endpoint);
|
|
||||||
unprotected_endpoints.push(mint_quote_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let melt_quote_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
|
|
||||||
let melt_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
|
|
||||||
|
|
||||||
if auth_settings.enabled_melt {
|
|
||||||
protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
|
|
||||||
protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
|
|
||||||
|
|
||||||
blind_auth_endpoints.push(melt_quote_protected_endpoint);
|
|
||||||
blind_auth_endpoints.push(melt_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(melt_quote_protected_endpoint);
|
|
||||||
unprotected_endpoints.push(melt_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
|
|
||||||
|
|
||||||
if auth_settings.enabled_swap {
|
|
||||||
protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
|
|
||||||
blind_auth_endpoints.push(swap_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(swap_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let check_mint_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
|
|
||||||
|
|
||||||
if auth_settings.enabled_check_mint_quote {
|
|
||||||
protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
|
|
||||||
blind_auth_endpoints.push(check_mint_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(check_mint_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let check_melt_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
|
|
||||||
|
|
||||||
if auth_settings.enabled_check_melt_quote {
|
|
||||||
protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
|
|
||||||
blind_auth_endpoints.push(check_melt_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(check_melt_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let restore_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
|
|
||||||
|
|
||||||
if auth_settings.enabled_restore {
|
|
||||||
protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
|
|
||||||
blind_auth_endpoints.push(restore_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(restore_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let state_protected_endpoint =
|
|
||||||
ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
|
|
||||||
|
|
||||||
if auth_settings.enabled_check_proof_state {
|
|
||||||
protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
|
|
||||||
blind_auth_endpoints.push(state_protected_endpoint);
|
|
||||||
} else {
|
|
||||||
unprotected_endpoints.push(state_protected_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mint_builder = mint_builder.with_auth(
|
|
||||||
auth_localstore.clone(),
|
|
||||||
auth_settings.openid_discovery,
|
|
||||||
auth_settings.openid_client_id,
|
|
||||||
vec![mint_blind_auth_endpoint],
|
|
||||||
);
|
|
||||||
mint_builder =
|
|
||||||
mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
|
|
||||||
|
|
||||||
let mut tx = auth_localstore.begin_transaction().await?;
|
|
||||||
|
|
||||||
tx.remove_protected_endpoints(unprotected_endpoints).await?;
|
|
||||||
tx.add_protected_endpoints(protected_endpoints).await?;
|
|
||||||
tx.commit().await?;
|
|
||||||
}
|
|
||||||
Ok(mint_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build mints with the configured the signing method (remote signatory or local seed)
|
|
||||||
async fn build_mint(
|
|
||||||
settings: &config::Settings,
|
|
||||||
keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
|
||||||
mint_builder: MintBuilder,
|
|
||||||
) -> Result<Mint> {
|
|
||||||
if let Some(signatory_url) = settings.info.signatory_url.clone() {
|
|
||||||
tracing::info!(
|
|
||||||
"Connecting to remote signatory to {} with certs {:?}",
|
|
||||||
signatory_url,
|
|
||||||
settings.info.signatory_certs.clone()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(mint_builder
|
|
||||||
.build_with_signatory(Arc::new(
|
|
||||||
cdk_signatory::SignatoryRpcClient::new(
|
|
||||||
signatory_url,
|
|
||||||
settings.info.signatory_certs.clone(),
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
.await?)
|
|
||||||
} else if let Some(mnemonic) = settings
|
|
||||||
.info
|
|
||||||
.mnemonic
|
|
||||||
.clone()
|
|
||||||
.map(|s| Mnemonic::from_str(&s))
|
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
Ok(mint_builder
|
|
||||||
.build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
|
|
||||||
.await?)
|
|
||||||
} else {
|
|
||||||
bail!("No seed nor remote signatory set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn start_services(
|
|
||||||
mint: Arc<cdk::mint::Mint>,
|
|
||||||
settings: &config::Settings,
|
|
||||||
ln_routers: Vec<Router>,
|
|
||||||
_work_dir: &Path,
|
|
||||||
mint_builder_info: cdk::nuts::MintInfo,
|
|
||||||
) -> Result<()> {
|
|
||||||
let listen_addr = settings.info.listen_host.clone();
|
|
||||||
let listen_port = settings.info.listen_port;
|
|
||||||
let cache: HttpCache = settings.info.http_cache.clone().into();
|
|
||||||
|
|
||||||
#[cfg(feature = "management-rpc")]
|
|
||||||
let mut rpc_enabled = false;
|
|
||||||
#[cfg(not(feature = "management-rpc"))]
|
|
||||||
let rpc_enabled = false;
|
|
||||||
|
|
||||||
#[cfg(feature = "management-rpc")]
|
|
||||||
let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
|
|
||||||
|
|
||||||
#[cfg(feature = "management-rpc")]
|
|
||||||
{
|
|
||||||
if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
|
|
||||||
if rpc_settings.enabled {
|
|
||||||
let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
|
|
||||||
let port = rpc_settings.port.unwrap_or(8086);
|
|
||||||
let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
|
|
||||||
|
|
||||||
let tls_dir = rpc_settings.tls_dir_path.unwrap_or(_work_dir.join("tls"));
|
|
||||||
|
|
||||||
if !tls_dir.exists() {
|
|
||||||
tracing::error!("TLS directory does not exist: {}", tls_dir.display());
|
|
||||||
bail!("Cannot start RPC server: TLS directory does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
mint_rpc.start(Some(tls_dir)).await?;
|
|
||||||
|
|
||||||
rpc_server = Some(mint_rpc);
|
|
||||||
|
|
||||||
rpc_enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rpc_enabled {
|
|
||||||
if mint.mint_info().await.is_err() {
|
|
||||||
tracing::info!("Mint info not set on mint, setting.");
|
|
||||||
mint.set_mint_info(mint_builder_info).await?;
|
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
||||||
} else {
|
|
||||||
if mint.localstore().get_quote_ttl().await.is_err() {
|
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
||||||
}
|
|
||||||
// Add version information
|
|
||||||
let mint_version = MintVersion::new(
|
|
||||||
"cdk-mintd".to_string(),
|
|
||||||
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
|
||||||
);
|
|
||||||
let mut stored_mint_info = mint.mint_info().await?;
|
|
||||||
stored_mint_info.version = Some(mint_version);
|
|
||||||
mint.set_mint_info(stored_mint_info).await?;
|
|
||||||
|
|
||||||
tracing::info!("Mint info already set, not using config file settings.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("RPC not enabled, using mint info from config.");
|
|
||||||
mint.set_mint_info(mint_builder_info).await?;
|
|
||||||
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mint_info = mint.mint_info().await?;
|
|
||||||
let nut04_methods = mint_info.nuts.nut04.supported_methods();
|
|
||||||
let nut05_methods = mint_info.nuts.nut05.supported_methods();
|
|
||||||
|
|
||||||
let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
|
|
||||||
|| nut05_methods.contains(&&PaymentMethod::Bolt12);
|
|
||||||
|
|
||||||
let v1_service =
|
|
||||||
cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut mint_service = Router::new()
|
|
||||||
.merge(v1_service)
|
|
||||||
.layer(
|
|
||||||
ServiceBuilder::new()
|
|
||||||
.layer(RequestDecompressionLayer::new())
|
|
||||||
.layer(CompressionLayer::new()),
|
|
||||||
)
|
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
|
|
||||||
#[cfg(feature = "swagger")]
|
|
||||||
{
|
|
||||||
if settings.info.enable_swagger_ui.unwrap_or(false) {
|
|
||||||
mint_service = mint_service.merge(
|
|
||||||
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
|
|
||||||
.url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for router in ln_routers {
|
|
||||||
mint_service = mint_service.merge(router);
|
|
||||||
}
|
|
||||||
|
|
||||||
mint.start().await?;
|
|
||||||
|
|
||||||
let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(socket_addr).await?;
|
|
||||||
|
|
||||||
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
|
||||||
|
|
||||||
// Wait for axum server to complete
|
|
||||||
let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
|
|
||||||
|
|
||||||
match axum_result.await {
|
|
||||||
Ok(_) => {
|
|
||||||
tracing::info!("Axum server stopped with okay status");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!("Axum server stopped with error");
|
|
||||||
tracing::error!("{}", err);
|
|
||||||
bail!("Axum exited with error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mint.stop().await?;
|
|
||||||
|
|
||||||
#[cfg(feature = "management-rpc")]
|
|
||||||
{
|
|
||||||
if let Some(rpc_server) = rpc_server {
|
|
||||||
rpc_server.stop().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
|
||||||
tokio::signal::ctrl_c()
|
|
||||||
.await
|
|
||||||
.expect("failed to install CTRL+C handler");
|
|
||||||
tracing::info!("Shutdown signal received");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn work_dir() -> Result<PathBuf> {
|
|
||||||
let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
|
|
||||||
let dir = home_dir.join(".cdk-mintd");
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&dir)?;
|
|
||||||
|
|
||||||
Ok(dir)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ cdk-common = { workspace = true, features = ["mint"] }
|
|||||||
cdk-cln = { workspace = true, optional = true }
|
cdk-cln = { workspace = true, optional = true }
|
||||||
cdk-lnd = { workspace = true, optional = true }
|
cdk-lnd = { workspace = true, optional = true }
|
||||||
cdk-fake-wallet = { workspace = true, optional = true }
|
cdk-fake-wallet = { workspace = true, optional = true }
|
||||||
|
clap = { workspace = true, features = ["derive"] }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|||||||
@@ -14,11 +14,47 @@ use cdk_common::payment::{self, MintPayment};
|
|||||||
use cdk_common::Amount;
|
use cdk_common::Amount;
|
||||||
#[cfg(feature = "fake")]
|
#[cfg(feature = "fake")]
|
||||||
use cdk_fake_wallet::FakeWallet;
|
use cdk_fake_wallet::FakeWallet;
|
||||||
|
use clap::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
|
#[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
/// Common CLI arguments for CDK binaries
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct CommonArgs {
|
||||||
|
/// Enable logging (default is false)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub enable_logging: bool,
|
||||||
|
|
||||||
|
/// Logging level when enabled (default is debug)
|
||||||
|
#[arg(long, default_value = "debug")]
|
||||||
|
pub log_level: tracing::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize logging based on CLI arguments
|
||||||
|
pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
|
||||||
|
if enable_logging {
|
||||||
|
let default_filter = log_level.to_string();
|
||||||
|
|
||||||
|
// Common filters to reduce noise
|
||||||
|
let sqlx_filter = "sqlx=warn";
|
||||||
|
let hyper_filter = "hyper=warn";
|
||||||
|
let h2_filter = "h2=warn";
|
||||||
|
let rustls_filter = "rustls=warn";
|
||||||
|
let reqwest_filter = "reqwest=warn";
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::new(format!(
|
||||||
|
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Ok if successful, Err if already initialized
|
||||||
|
let _ = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(env_filter)
|
||||||
|
.try_init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const ENV_LN_BACKEND: &str = "CDK_PAYMENT_PROCESSOR_LN_BACKEND";
|
pub const ENV_LN_BACKEND: &str = "CDK_PAYMENT_PROCESSOR_LN_BACKEND";
|
||||||
pub const ENV_LISTEN_HOST: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_HOST";
|
pub const ENV_LISTEN_HOST: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_HOST";
|
||||||
pub const ENV_LISTEN_PORT: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_PORT";
|
pub const ENV_LISTEN_PORT: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_PORT";
|
||||||
@@ -36,20 +72,20 @@ pub const ENV_LND_ADDRESS: &str = "CDK_PAYMENT_PROCESSOR_LND_ADDRESS";
|
|||||||
pub const ENV_LND_CERT_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_CERT_FILE";
|
pub const ENV_LND_CERT_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_CERT_FILE";
|
||||||
pub const ENV_LND_MACAROON_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_MACAROON_FILE";
|
pub const ENV_LND_MACAROON_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_MACAROON_FILE";
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "payment-processor")]
|
||||||
|
#[command(about = "CDK Payment Processor", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let default_filter = "debug";
|
let args = Args::parse();
|
||||||
|
|
||||||
let sqlx_filter = "sqlx=warn";
|
// Initialize logging based on CLI arguments
|
||||||
let hyper_filter = "hyper=warn";
|
init_logging(args.common.enable_logging, args.common.log_level);
|
||||||
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();
|
|
||||||
|
|
||||||
#[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
|
#[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,9 +17,43 @@ use cdk_signatory::{db_signatory, start_grpc_server};
|
|||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
use cdk_sqlite::MintSqliteDatabase;
|
use cdk_sqlite::MintSqliteDatabase;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tracing::Level;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
/// Common CLI arguments for CDK binaries
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct CommonArgs {
|
||||||
|
/// Enable logging (default is false)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub enable_logging: bool,
|
||||||
|
|
||||||
|
/// Logging level when enabled (default is debug)
|
||||||
|
#[arg(long, default_value = "debug")]
|
||||||
|
pub log_level: tracing::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize logging based on CLI arguments
|
||||||
|
pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
|
||||||
|
if enable_logging {
|
||||||
|
let default_filter = log_level.to_string();
|
||||||
|
|
||||||
|
// Common filters to reduce noise
|
||||||
|
let sqlx_filter = "sqlx=warn";
|
||||||
|
let hyper_filter = "hyper=warn";
|
||||||
|
let h2_filter = "h2=warn";
|
||||||
|
let rustls_filter = "rustls=warn";
|
||||||
|
let reqwest_filter = "reqwest=warn";
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::new(format!(
|
||||||
|
"{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Ok if successful, Err if already initialized
|
||||||
|
let _ = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(env_filter)
|
||||||
|
.try_init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
|
const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
|
||||||
const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
|
const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
|
||||||
|
|
||||||
@@ -30,6 +64,9 @@ const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
|
|||||||
#[command(version = "0.1.0")]
|
#[command(version = "0.1.0")]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
#[command(flatten)]
|
||||||
|
common: CommonArgs,
|
||||||
|
|
||||||
/// Database engine to use (sqlite/redb)
|
/// Database engine to use (sqlite/redb)
|
||||||
#[arg(short, long, default_value = "sqlite")]
|
#[arg(short, long, default_value = "sqlite")]
|
||||||
engine: String,
|
engine: String,
|
||||||
@@ -39,9 +76,6 @@ struct Cli {
|
|||||||
/// Path to working dir
|
/// Path to working dir
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
work_dir: Option<PathBuf>,
|
work_dir: Option<PathBuf>,
|
||||||
/// Logging level
|
|
||||||
#[arg(short, long, default_value = "debug")]
|
|
||||||
log_level: Level,
|
|
||||||
#[arg(long, default_value = "127.0.0.1")]
|
#[arg(long, default_value = "127.0.0.1")]
|
||||||
listen_addr: String,
|
listen_addr: String,
|
||||||
#[arg(long, default_value = "15060")]
|
#[arg(long, default_value = "15060")]
|
||||||
@@ -56,7 +90,10 @@ struct Cli {
|
|||||||
/// Main function for the signatory standalone binary
|
/// Main function for the signatory standalone binary
|
||||||
pub async fn cli_main() -> Result<()> {
|
pub async fn cli_main() -> Result<()> {
|
||||||
let args: Cli = Cli::parse();
|
let args: Cli = Cli::parse();
|
||||||
let default_filter = args.log_level;
|
|
||||||
|
// Initialize logging based on CLI arguments
|
||||||
|
init_logging(args.common.enable_logging, args.common.log_level);
|
||||||
|
|
||||||
let supported_units = args
|
let supported_units = args
|
||||||
.units
|
.units
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -74,13 +111,6 @@ pub async fn cli_main() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||||
|
|
||||||
let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
|
|
||||||
|
|
||||||
let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter}"));
|
|
||||||
|
|
||||||
// Parse input
|
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
|
||||||
|
|
||||||
let work_dir = match &args.work_dir {
|
let work_dir = match &args.work_dir {
|
||||||
Some(work_dir) => work_dir.clone(),
|
Some(work_dir) => work_dir.clone(),
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Function to perform cleanup
|
# Function to perform cleanup
|
||||||
cleanup() {
|
cleanup() {
|
||||||
echo "Cleaning up..."
|
echo "Cleaning up..."
|
||||||
|
|
||||||
echo "Killing the cdk mintd"
|
if [ -n "$FAKE_AUTH_MINT_PID" ]; then
|
||||||
kill -2 $cdk_mintd_pid
|
echo "Killing the fake auth mint process"
|
||||||
wait $cdk_mintd_pid
|
kill -2 $FAKE_AUTH_MINT_PID 2>/dev/null || true
|
||||||
|
wait $FAKE_AUTH_MINT_PID 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Mint binary terminated"
|
echo "Mint binary terminated"
|
||||||
|
|
||||||
# Remove the temporary directory
|
# Remove the temporary directory
|
||||||
|
if [ -n "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
|
||||||
rm -rf "$CDK_ITESTS_DIR"
|
rm -rf "$CDK_ITESTS_DIR"
|
||||||
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unset all environment variables
|
||||||
unset CDK_ITESTS_DIR
|
unset CDK_ITESTS_DIR
|
||||||
unset CDK_ITESTS_MINT_ADDR
|
unset CDK_ITESTS_MINT_ADDR
|
||||||
unset CDK_ITESTS_MINT_PORT
|
unset CDK_ITESTS_MINT_PORT
|
||||||
|
unset FAKE_AUTH_MINT_PID
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up trap to call cleanup on script exit
|
# Set up trap to call cleanup on script exit
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
# Create a temporary directory
|
# Create a temporary directory
|
||||||
export CDK_ITESTS_DIR=$(mktemp -d)
|
export CDK_ITESTS_DIR=$(mktemp -d)
|
||||||
export CDK_ITESTS_MINT_ADDR="127.0.0.1";
|
export CDK_ITESTS_MINT_ADDR="127.0.0.1"
|
||||||
export CDK_ITESTS_MINT_PORT=8087;
|
export CDK_ITESTS_MINT_PORT=8087
|
||||||
|
|
||||||
# Check if the temporary directory was created successfully
|
# Check if the temporary directory was created successfully
|
||||||
if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
||||||
@@ -34,72 +40,41 @@ if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Temp directory created: $CDK_ITESTS_DIR"
|
echo "Temp directory created: $CDK_ITESTS_DIR"
|
||||||
export MINT_DATABASE="$1";
|
|
||||||
export OPENID_DISCOVERY="$2";
|
|
||||||
|
|
||||||
|
# Check if a database type was provided as first argument, default to sqlite
|
||||||
|
export MINT_DATABASE="${1:-sqlite}"
|
||||||
|
|
||||||
|
# Check if OPENID_DISCOVERY was provided as second argument, default to a test value
|
||||||
|
export OPENID_DISCOVERY="${2:-http://127.0.0.1:8080/realms/cdk-test-realm/.well-known/openid-configuration}"
|
||||||
|
|
||||||
|
# Build the project
|
||||||
cargo build -p cdk-integration-tests
|
cargo build -p cdk-integration-tests
|
||||||
|
|
||||||
export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT";
|
|
||||||
export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR";
|
|
||||||
export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR;
|
|
||||||
export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT;
|
|
||||||
export CDK_MINTD_LN_BACKEND="fakewallet";
|
|
||||||
export CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS="sat";
|
|
||||||
export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal";
|
|
||||||
export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0";
|
|
||||||
export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1";
|
|
||||||
export CDK_MINTD_DATABASE=$MINT_DATABASE;
|
|
||||||
|
|
||||||
# Auth configuration
|
# Auth configuration
|
||||||
export CDK_TEST_OIDC_USER="cdk-test";
|
export CDK_TEST_OIDC_USER="cdk-test"
|
||||||
export CDK_TEST_OIDC_PASSWORD="cdkpassword";
|
export CDK_TEST_OIDC_PASSWORD="cdkpassword"
|
||||||
|
|
||||||
export CDK_MINTD_AUTH_OPENID_DISCOVERY=$OPENID_DISCOVERY;
|
# Start the fake auth mint in the background
|
||||||
export CDK_MINTD_AUTH_OPENID_CLIENT_ID="cashu-client";
|
echo "Starting fake auth mint with discovery URL: $OPENID_DISCOVERY"
|
||||||
export CDK_MINTD_AUTH_MINT_MAX_BAT="50";
|
echo "Using temp directory: $CDK_ITESTS_DIR"
|
||||||
export CDK_MINTD_AUTH_ENABLED_MINT="true";
|
cargo run -p cdk-integration-tests --bin start_fake_auth_mint -- --enable-logging "$MINT_DATABASE" "$CDK_ITESTS_DIR" "$OPENID_DISCOVERY" "$CDK_ITESTS_MINT_PORT" &
|
||||||
export CDK_MINTD_AUTH_ENABLED_MELT="true";
|
|
||||||
export CDK_MINTD_AUTH_ENABLED_SWAP="true";
|
|
||||||
export CDK_MINTD_AUTH_ENABLED_CHECK_MINT_QUOTE="true";
|
|
||||||
export CDK_MINTD_AUTH_ENABLED_CHECK_MELT_QUOTE="true";
|
|
||||||
export CDK_MINTD_AUTH_ENABLED_RESTORE="true";
|
|
||||||
export CDK_MINTD_AUTH_ENABLED_CHECK_PROOF_STATE="true";
|
|
||||||
|
|
||||||
echo "Starting auth mintd";
|
# Store the PID of the mint process
|
||||||
cargo run --bin cdk-mintd --features redb &
|
FAKE_AUTH_MINT_PID=$!
|
||||||
cdk_mintd_pid=$!
|
|
||||||
|
|
||||||
URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT/v1/info"
|
# Wait a moment for the mint to start
|
||||||
TIMEOUT=100
|
sleep 5
|
||||||
START_TIME=$(date +%s)
|
|
||||||
# Loop until the endpoint returns a 200 OK status or timeout is reached
|
|
||||||
while true; do
|
|
||||||
# Get the current time
|
|
||||||
CURRENT_TIME=$(date +%s)
|
|
||||||
|
|
||||||
# Calculate the elapsed time
|
# Check if the mint is running
|
||||||
ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
|
if ! kill -0 $FAKE_AUTH_MINT_PID 2>/dev/null; then
|
||||||
|
echo "Failed to start fake auth mint"
|
||||||
# Check if the elapsed time exceeds the timeout
|
|
||||||
if [ $ELAPSED_TIME -ge $TIMEOUT ]; then
|
|
||||||
echo "Timeout of $TIMEOUT seconds reached. Exiting..."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make a request to the endpoint and capture the HTTP status code
|
echo "Fake auth mint started with PID: $FAKE_AUTH_MINT_PID"
|
||||||
HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}" $URL)
|
|
||||||
|
|
||||||
# Check if the HTTP status is 200 OK
|
|
||||||
if [ "$HTTP_STATUS" -eq 200 ]; then
|
|
||||||
echo "Received 200 OK from $URL"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
echo "Waiting for 200 OK response, current status: $HTTP_STATUS"
|
|
||||||
sleep 2 # Wait for 2 seconds before retrying
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Run cargo test
|
# Run cargo test
|
||||||
|
echo "Running fake auth integration tests..."
|
||||||
cargo test -p cdk-integration-tests --test fake_auth
|
cargo test -p cdk-integration-tests --test fake_auth
|
||||||
|
|
||||||
# Capture the exit status of cargo test
|
# Capture the exit status of cargo test
|
||||||
|
|||||||
@@ -1,46 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Script to run fake mint tests with proper handling of race conditions
|
||||||
|
# This script ensures the .env file is properly created and available
|
||||||
|
# before running tests
|
||||||
|
|
||||||
# Function to perform cleanup
|
# Function to perform cleanup
|
||||||
cleanup() {
|
cleanup() {
|
||||||
echo "Cleaning up..."
|
echo "Cleaning up..."
|
||||||
|
|
||||||
echo "Killing the cdk mintd"
|
if [ -n "$FAKE_MINT_PID" ]; then
|
||||||
kill -2 $CDK_MINTD_PID
|
echo "Killing the fake mint process"
|
||||||
wait $CDK_MINTD_PID
|
kill -2 $FAKE_MINT_PID 2>/dev/null || true
|
||||||
kill -9 $CDK_SIGNATORY_PID
|
wait $FAKE_MINT_PID 2>/dev/null || true
|
||||||
wait $CDK_SIGNATORY_PID
|
fi
|
||||||
|
|
||||||
|
if [ -n "$CDK_SIGNATORY_PID" ]; then
|
||||||
|
echo "Killing the signatory process"
|
||||||
|
kill -9 $CDK_SIGNATORY_PID 2>/dev/null || true
|
||||||
|
wait $CDK_SIGNATORY_PID 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Mint binary terminated"
|
echo "Mint binary terminated"
|
||||||
|
|
||||||
# Remove the temporary directory
|
# Remove the temporary directory
|
||||||
|
if [ -n "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
|
||||||
rm -rf "$CDK_ITESTS_DIR"
|
rm -rf "$CDK_ITESTS_DIR"
|
||||||
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
# Unset all environment variables
|
# Unset all environment variables
|
||||||
unset CDK_ITESTS_DIR
|
unset CDK_ITESTS_DIR
|
||||||
unset CDK_ITESTS_MINT_ADDR
|
|
||||||
unset CDK_ITESTS_MINT_PORT
|
|
||||||
unset CDK_MINTD_DATABASE
|
|
||||||
unset CDK_TEST_MINT_URL
|
unset CDK_TEST_MINT_URL
|
||||||
unset CDK_MINTD_URL
|
unset FAKE_MINT_PID
|
||||||
unset CDK_MINTD_WORK_DIR
|
unset CDK_SIGNATORY_PID
|
||||||
unset CDK_MINTD_LISTEN_HOST
|
|
||||||
unset CDK_MINTD_LISTEN_PORT
|
|
||||||
unset CDK_MINTD_LN_BACKEND
|
|
||||||
unset CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS
|
|
||||||
unset CDK_MINTD_MNEMONIC
|
|
||||||
unset CDK_MINTD_FAKE_WALLET_FEE_PERCENT
|
|
||||||
unset CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN
|
|
||||||
unset CDK_MINTD_PID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up trap to call cleanup on script exit
|
# Set up trap to call cleanup on script exit
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
# Create a temporary directory
|
# Create a temporary directory
|
||||||
export CDK_ITESTS_DIR=$(mktemp -d)
|
export CDK_ITESTS_DIR=$(mktemp -d)
|
||||||
export CDK_ITESTS_MINT_ADDR="127.0.0.1"
|
|
||||||
export CDK_ITESTS_MINT_PORT=8086
|
|
||||||
|
|
||||||
# Check if the temporary directory was created successfully
|
# Check if the temporary directory was created successfully
|
||||||
if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
||||||
@@ -49,36 +48,74 @@ if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Temp directory created: $CDK_ITESTS_DIR"
|
echo "Temp directory created: $CDK_ITESTS_DIR"
|
||||||
export CDK_MINTD_DATABASE="$1"
|
|
||||||
|
# Check if a database type was provided as first argument, default to sqlite
|
||||||
|
export CDK_MINTD_DATABASE="${1:-sqlite}"
|
||||||
|
|
||||||
cargo build -p cdk-integration-tests
|
cargo build -p cdk-integration-tests
|
||||||
|
|
||||||
|
# Start the fake mint binary with the new Rust-based approach
|
||||||
export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT"
|
echo "Starting fake mint using Rust binary..."
|
||||||
export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR"
|
|
||||||
export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
|
|
||||||
export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT
|
|
||||||
export CDK_MINTD_LN_BACKEND="fakewallet"
|
|
||||||
export CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS="sat,usd"
|
|
||||||
export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
|
|
||||||
export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0"
|
|
||||||
export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
|
|
||||||
|
|
||||||
if [ "$2" = "external_signatory" ]; then
|
if [ "$2" = "external_signatory" ]; then
|
||||||
export CDK_MINTD_SIGNATORY_URL="https://127.0.0.1:15060"
|
echo "Starting with external signatory support"
|
||||||
export CDK_MINTD_SIGNATORY_CERTS="$CDK_ITESTS_DIR"
|
|
||||||
bash -x `dirname $0`/../crates/cdk-signatory/generate_certs.sh $CDK_ITESTS_DIR
|
bash -x `dirname $0`/../crates/cdk-signatory/generate_certs.sh $CDK_ITESTS_DIR
|
||||||
|
cargo build --bin signatory
|
||||||
cargo run --bin signatory -- -w $CDK_ITESTS_DIR -u "sat" -u "usd" &
|
cargo run --bin signatory -- -w $CDK_ITESTS_DIR -u "sat" -u "usd" &
|
||||||
export CDK_SIGNATORY_PID=$!
|
export CDK_SIGNATORY_PID=$!
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
|
cargo run --bin start_fake_mint -- --enable-logging --external-signatory "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" &
|
||||||
|
else
|
||||||
|
cargo run --bin start_fake_mint -- --enable-logging "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" &
|
||||||
|
fi
|
||||||
|
export FAKE_MINT_PID=$!
|
||||||
|
|
||||||
|
# Give the mint a moment to start
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Look for the .env file in the temp directory
|
||||||
|
ENV_FILE_PATH="$CDK_ITESTS_DIR/.env"
|
||||||
|
|
||||||
|
# Wait for the .env file to be created (with longer timeout)
|
||||||
|
max_wait=200
|
||||||
|
wait_count=0
|
||||||
|
while [ $wait_count -lt $max_wait ]; do
|
||||||
|
if [ -f "$ENV_FILE_PATH" ]; then
|
||||||
|
echo ".env file found at: $ENV_FILE_PATH"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for .env file to be created... ($wait_count/$max_wait)"
|
||||||
|
wait_count=$((wait_count + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if we found the .env file
|
||||||
|
if [ ! -f "$ENV_FILE_PATH" ]; then
|
||||||
|
echo "ERROR: Could not find .env file at $ENV_FILE_PATH after $max_wait seconds"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting fake mintd"
|
# Source the environment variables from the .env file
|
||||||
cargo run --bin cdk-mintd &
|
echo "Sourcing environment variables from $ENV_FILE_PATH"
|
||||||
export CDK_MINTD_PID=$!
|
source "$ENV_FILE_PATH"
|
||||||
|
|
||||||
URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT/v1/info"
|
echo "Sourced environment variables:"
|
||||||
TIMEOUT=100
|
echo "CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL"
|
||||||
|
echo "CDK_ITESTS_DIR=$CDK_ITESTS_DIR"
|
||||||
|
|
||||||
|
# Validate that we sourced the variables
|
||||||
|
if [ -z "$CDK_TEST_MINT_URL" ] || [ -z "$CDK_ITESTS_DIR" ]; then
|
||||||
|
echo "ERROR: Failed to source environment variables from the .env file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export all variables so they're available to the tests
|
||||||
|
export CDK_TEST_MINT_URL
|
||||||
|
|
||||||
|
URL="$CDK_TEST_MINT_URL/v1/info"
|
||||||
|
|
||||||
|
TIMEOUT=120
|
||||||
START_TIME=$(date +%s)
|
START_TIME=$(date +%s)
|
||||||
# Loop until the endpoint returns a 200 OK status or timeout is reached
|
# Loop until the endpoint returns a 200 OK status or timeout is reached
|
||||||
while true; do
|
while true; do
|
||||||
@@ -107,10 +144,8 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT"
|
|
||||||
|
|
||||||
# Run first test
|
# Run first test
|
||||||
|
echo "Running fake_wallet test"
|
||||||
cargo test -p cdk-integration-tests --test fake_wallet
|
cargo test -p cdk-integration-tests --test fake_wallet
|
||||||
status1=$?
|
status1=$?
|
||||||
|
|
||||||
@@ -121,6 +156,7 @@ if [ $status1 -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Run second test only if the first one succeeded
|
# Run second test only if the first one succeeded
|
||||||
|
echo "Running happy_path_mint_wallet test"
|
||||||
cargo test -p cdk-integration-tests --test happy_path_mint_wallet
|
cargo test -p cdk-integration-tests --test happy_path_mint_wallet
|
||||||
status2=$?
|
status2=$?
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ cargo build -p cdk-integration-tests --bin start_regtest
|
|||||||
cargo build --bin cdk-mintd
|
cargo build --bin cdk-mintd
|
||||||
|
|
||||||
echo "Starting regtest network (Bitcoin + Lightning nodes)..."
|
echo "Starting regtest network (Bitcoin + Lightning nodes)..."
|
||||||
cargo run --bin start_regtest &
|
cargo run --bin start_regtest -- --enable-logging "$CDK_ITESTS_DIR" &
|
||||||
export CDK_REGTEST_PID=$!
|
export CDK_REGTEST_PID=$!
|
||||||
|
|
||||||
# Create named pipe for progress tracking
|
# Create named pipe for progress tracking
|
||||||
|
|||||||
148
misc/itests.sh
148
misc/itests.sh
@@ -4,25 +4,29 @@
|
|||||||
cleanup() {
|
cleanup() {
|
||||||
echo "Cleaning up..."
|
echo "Cleaning up..."
|
||||||
|
|
||||||
echo "Killing the cdk mintd"
|
echo "Killing the cdk regtest and mints"
|
||||||
kill -2 $CDK_MINTD_PID
|
if [ ! -z "$CDK_REGTEST_PID" ]; then
|
||||||
wait $CDK_MINTD_PID
|
# First try graceful shutdown with SIGTERM
|
||||||
|
kill -15 $CDK_REGTEST_PID 2>/dev/null
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check if process is still running, if so force kill with SIGKILL
|
||||||
|
if ps -p $CDK_REGTEST_PID > /dev/null 2>&1; then
|
||||||
|
echo "Process still running, force killing..."
|
||||||
|
kill -9 $CDK_REGTEST_PID 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Killing the cdk lnd mintd"
|
# Wait for process to terminate
|
||||||
kill -2 $CDK_MINTD_LND_PID
|
wait $CDK_REGTEST_PID 2>/dev/null || true
|
||||||
wait $CDK_MINTD_LND_PID
|
fi
|
||||||
|
|
||||||
echo "Killing the cdk regtest"
|
|
||||||
kill -2 $CDK_REGTEST_PID
|
|
||||||
wait $CDK_REGTEST_PID
|
|
||||||
|
|
||||||
|
|
||||||
echo "Mint binary terminated"
|
echo "Mint binary terminated"
|
||||||
|
|
||||||
# Remove the temporary directory
|
# Remove the temporary directory
|
||||||
|
if [ ! -z "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
|
||||||
rm -rf "$CDK_ITESTS_DIR"
|
rm -rf "$CDK_ITESTS_DIR"
|
||||||
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
echo "Temp directory removed: $CDK_ITESTS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
# Unset all environment variables
|
# Unset all environment variables
|
||||||
unset CDK_ITESTS_DIR
|
unset CDK_ITESTS_DIR
|
||||||
@@ -32,18 +36,6 @@ cleanup() {
|
|||||||
unset CDK_MINTD_DATABASE
|
unset CDK_MINTD_DATABASE
|
||||||
unset CDK_TEST_MINT_URL
|
unset CDK_TEST_MINT_URL
|
||||||
unset CDK_TEST_MINT_URL_2
|
unset CDK_TEST_MINT_URL_2
|
||||||
unset CDK_MINTD_URL
|
|
||||||
unset CDK_MINTD_WORK_DIR
|
|
||||||
unset CDK_MINTD_LISTEN_HOST
|
|
||||||
unset CDK_MINTD_LISTEN_PORT
|
|
||||||
unset CDK_MINTD_LN_BACKEND
|
|
||||||
unset CDK_MINTD_MNEMONIC
|
|
||||||
unset CDK_MINTD_CLN_RPC_PATH
|
|
||||||
unset CDK_MINTD_LND_ADDRESS
|
|
||||||
unset CDK_MINTD_LND_CERT_FILE
|
|
||||||
unset CDK_MINTD_LND_MACAROON_FILE
|
|
||||||
unset CDK_MINTD_PID
|
|
||||||
unset CDK_MINTD_LND_PID
|
|
||||||
unset CDK_REGTEST_PID
|
unset CDK_REGTEST_PID
|
||||||
unset RUST_BACKTRACE
|
unset RUST_BACKTRACE
|
||||||
unset CDK_TEST_REGTEST
|
unset CDK_TEST_REGTEST
|
||||||
@@ -70,52 +62,58 @@ echo "Temp directory created: $CDK_ITESTS_DIR"
|
|||||||
export CDK_MINTD_DATABASE="$1"
|
export CDK_MINTD_DATABASE="$1"
|
||||||
|
|
||||||
cargo build -p cdk-integration-tests
|
cargo build -p cdk-integration-tests
|
||||||
|
cargo build --bin start_regtest_mints
|
||||||
|
|
||||||
cargo run --bin start_regtest &
|
echo "Starting regtest and mints"
|
||||||
|
# Run the binary in background
|
||||||
|
cargo r --bin start_regtest_mints -- --enable-logging "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" "$CDK_ITESTS_MINT_ADDR" "$CDK_ITESTS_MINT_PORT_0" "$CDK_ITESTS_MINT_PORT_1" &
|
||||||
export CDK_REGTEST_PID=$!
|
export CDK_REGTEST_PID=$!
|
||||||
mkfifo "$CDK_ITESTS_DIR/progress_pipe"
|
|
||||||
rm -f "$CDK_ITESTS_DIR/signal_received" # Ensure clean state
|
# Give it a moment to start - reduced from 5 to 2 seconds since we have better waiting mechanisms now
|
||||||
# Start reading from pipe in background
|
sleep 2
|
||||||
(while read line; do
|
|
||||||
case "$line" in
|
# Look for the .env file in the current directory
|
||||||
"checkpoint1")
|
ENV_FILE_PATH="$CDK_ITESTS_DIR/.env"
|
||||||
echo "Reached first checkpoint"
|
|
||||||
touch "$CDK_ITESTS_DIR/signal_received"
|
# Wait for the .env file to be created in the current directory
|
||||||
exit 0
|
max_wait=120
|
||||||
;;
|
wait_count=0
|
||||||
esac
|
while [ $wait_count -lt $max_wait ]; do
|
||||||
done < "$CDK_ITESTS_DIR/progress_pipe") &
|
if [ -f "$ENV_FILE_PATH" ]; then
|
||||||
# Wait for up to 120 seconds
|
echo ".env file found at: $ENV_FILE_PATH"
|
||||||
for ((i=0; i<120; i++)); do
|
|
||||||
if [ -f "$CDK_ITESTS_DIR/signal_received" ]; then
|
|
||||||
echo "break signal received"
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
wait_count=$((wait_count + 1))
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
echo "Regtest set up continuing"
|
|
||||||
|
|
||||||
echo "Starting regtest mint"
|
# Check if we found the .env file
|
||||||
# cargo run --bin regtest_mint &
|
if [ ! -f "$ENV_FILE_PATH" ]; then
|
||||||
|
echo "ERROR: Could not find .env file at $ENV_FILE_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
export CDK_MINTD_CLN_RPC_PATH="$CDK_ITESTS_DIR/cln/one/regtest/lightning-rpc"
|
# Source the environment variables from the .env file
|
||||||
export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
|
echo "Sourcing environment variables from $ENV_FILE_PATH"
|
||||||
export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR"
|
source "$ENV_FILE_PATH"
|
||||||
export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
|
|
||||||
export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT_0
|
|
||||||
export CDK_MINTD_LN_BACKEND="cln"
|
|
||||||
export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
|
|
||||||
export RUST_BACKTRACE=1
|
|
||||||
|
|
||||||
echo "Starting cln mintd"
|
echo "Sourced environment variables:"
|
||||||
cargo run --bin cdk-mintd &
|
echo "CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL"
|
||||||
export CDK_MINTD_PID=$!
|
echo "CDK_TEST_MINT_URL_2=$CDK_TEST_MINT_URL_2"
|
||||||
|
echo "CDK_ITESTS_DIR=$CDK_ITESTS_DIR"
|
||||||
|
|
||||||
|
# Validate that we sourced the variables
|
||||||
|
if [ -z "$CDK_TEST_MINT_URL" ] || [ -z "$CDK_TEST_MINT_URL_2" ] || [ -z "$CDK_ITESTS_DIR" ]; then
|
||||||
|
echo "ERROR: Failed to source environment variables from the .env file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo $CDK_ITESTS_DIR
|
# Export all variables so they're available to the tests
|
||||||
|
export CDK_TEST_MINT_URL
|
||||||
|
export CDK_TEST_MINT_URL_2
|
||||||
|
|
||||||
|
URL="$CDK_TEST_MINT_URL/v1/info"
|
||||||
|
|
||||||
URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0/v1/info"
|
|
||||||
|
|
||||||
TIMEOUT=100
|
TIMEOUT=100
|
||||||
START_TIME=$(date +%s)
|
START_TIME=$(date +%s)
|
||||||
@@ -146,24 +144,8 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
URL="$CDK_TEST_MINT_URL_2/v1/info"
|
||||||
|
|
||||||
export CDK_MINTD_LND_ADDRESS="https://localhost:10010"
|
|
||||||
export CDK_MINTD_LND_CERT_FILE="$CDK_ITESTS_DIR/lnd/two/tls.cert"
|
|
||||||
export CDK_MINTD_LND_MACAROON_FILE="$CDK_ITESTS_DIR/lnd/two/data/chain/bitcoin/regtest/admin.macaroon"
|
|
||||||
|
|
||||||
export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
|
|
||||||
mkdir -p "$CDK_ITESTS_DIR/lnd_mint"
|
|
||||||
export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR/lnd_mint"
|
|
||||||
export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
|
|
||||||
export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT_1
|
|
||||||
export CDK_MINTD_LN_BACKEND="lnd"
|
|
||||||
export CDK_MINTD_MNEMONIC="cattle gold bind busy sound reduce tone addict baby spend february strategy"
|
|
||||||
|
|
||||||
echo "Starting lnd mintd"
|
|
||||||
cargo run --bin cdk-mintd &
|
|
||||||
export CDK_MINTD_LND_PID=$!
|
|
||||||
|
|
||||||
URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1/v1/info"
|
|
||||||
|
|
||||||
TIMEOUT=100
|
TIMEOUT=100
|
||||||
START_TIME=$(date +%s)
|
START_TIME=$(date +%s)
|
||||||
@@ -194,13 +176,6 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
|
|
||||||
export CDK_TEST_MINT_URL_2="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
|
|
||||||
|
|
||||||
# Run tests and exit immediately on failure
|
|
||||||
|
|
||||||
# Run cargo test
|
# Run cargo test
|
||||||
echo "Running regtest test with CLN mint"
|
echo "Running regtest test with CLN mint"
|
||||||
cargo test -p cdk-integration-tests --test regtest
|
cargo test -p cdk-integration-tests --test regtest
|
||||||
@@ -216,7 +191,7 @@ if [ $? -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# # Run cargo test with the http_subscription feature
|
# Run cargo test with the http_subscription feature
|
||||||
echo "Running regtest test with http_subscription feature"
|
echo "Running regtest test with http_subscription feature"
|
||||||
cargo test -p cdk-integration-tests --test regtest --features http_subscription
|
cargo test -p cdk-integration-tests --test regtest --features http_subscription
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@@ -233,12 +208,13 @@ fi
|
|||||||
|
|
||||||
# Switch Mints: Run tests with LND mint
|
# Switch Mints: Run tests with LND mint
|
||||||
echo "Switching to LND mint for tests"
|
echo "Switching to LND mint for tests"
|
||||||
export CDK_ITESTS_MINT_PORT_0=8087
|
|
||||||
export CDK_ITESTS_MINT_PORT_1=8085
|
|
||||||
export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
|
|
||||||
export CDK_TEST_MINT_URL_2="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
|
|
||||||
|
|
||||||
echo "Running regtest test with LND mint"
|
echo "Running regtest test with LND mint"
|
||||||
|
CDK_TEST_MINT_URL_SWITCHED=$CDK_TEST_MINT_URL_2
|
||||||
|
CDK_TEST_MINT_URL_2_SWITCHED=$CDK_TEST_MINT_URL
|
||||||
|
export CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL_SWITCHED
|
||||||
|
export CDK_TEST_MINT_URL_2=$CDK_TEST_MINT_URL_2_SWITCHED
|
||||||
|
|
||||||
cargo test -p cdk-integration-tests --test regtest
|
cargo test -p cdk-integration-tests --test regtest
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "regtest test with LND mint failed, exiting"
|
echo "regtest test with LND mint failed, exiting"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ cargo build -p cdk-integration-tests
|
|||||||
export CDK_TEST_REGTEST=0
|
export CDK_TEST_REGTEST=0
|
||||||
if [ "$LN_BACKEND" != "FAKEWALLET" ]; then
|
if [ "$LN_BACKEND" != "FAKEWALLET" ]; then
|
||||||
export CDK_TEST_REGTEST=1
|
export CDK_TEST_REGTEST=1
|
||||||
cargo run --bin start_regtest &
|
cargo run --bin start_regtest "$CDK_ITESTS_DIR" &
|
||||||
CDK_REGTEST_PID=$!
|
CDK_REGTEST_PID=$!
|
||||||
mkfifo "$CDK_ITESTS_DIR/progress_pipe"
|
mkfifo "$CDK_ITESTS_DIR/progress_pipe"
|
||||||
rm -f "$CDK_ITESTS_DIR/signal_received" # Ensure clean state
|
rm -f "$CDK_ITESTS_DIR/signal_received" # Ensure clean state
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
|
|||||||
export CDK_MINTD_INPUT_FEE_PPK="100"
|
export CDK_MINTD_INPUT_FEE_PPK="100"
|
||||||
|
|
||||||
|
|
||||||
|
export CDK_ITESTS_DIR="$CDK_ITESTS"
|
||||||
|
|
||||||
|
|
||||||
echo "Starting fake mintd"
|
echo "Starting fake mintd"
|
||||||
cargo run --bin cdk-mintd &
|
cargo run --bin cdk-mintd &
|
||||||
CDK_MINTD_PID=$!
|
CDK_MINTD_PID=$!
|
||||||
|
|||||||
Reference in New Issue
Block a user