init_pure_tests.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. use std::collections::{HashMap, HashSet};
  2. use std::fmt::{Debug, Formatter};
  3. use std::path::PathBuf;
  4. use std::str::FromStr;
  5. use std::sync::Arc;
  6. use std::{env, fs};
  7. use anyhow::{anyhow, bail, Result};
  8. use async_trait::async_trait;
  9. use bip39::Mnemonic;
  10. use cdk::amount::SplitTarget;
  11. use cdk::cdk_database::{self, WalletDatabase};
  12. use cdk::mint::{MintBuilder, MintMeltLimits};
  13. use cdk::nuts::nut00::ProofsMethods;
  14. use cdk::nuts::{
  15. CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse,
  16. MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
  17. MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, PaymentMethod,
  18. RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
  19. };
  20. use cdk::types::{FeeReserve, QuoteTTL};
  21. use cdk::util::unix_time;
  22. use cdk::wallet::{AuthWallet, MintConnector, Wallet, WalletBuilder};
  23. use cdk::{Amount, Error, Mint};
  24. use cdk_fake_wallet::FakeWallet;
  25. use tokio::sync::{Notify, RwLock};
  26. use tracing_subscriber::EnvFilter;
  27. use uuid::Uuid;
  28. use crate::wait_for_mint_to_be_paid;
  29. pub struct DirectMintConnection {
  30. pub mint: Mint,
  31. auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
  32. }
  33. impl DirectMintConnection {
  34. pub fn new(mint: Mint) -> Self {
  35. Self {
  36. mint,
  37. auth_wallet: Arc::new(RwLock::new(None)),
  38. }
  39. }
  40. }
  41. impl Debug for DirectMintConnection {
  42. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  43. write!(f, "DirectMintConnection",)
  44. }
  45. }
  46. /// Implements the generic [MintConnector] (i.e. use the interface that expects to communicate
  47. /// to a generic mint, where we don't know that quote ID's are [Uuid]s) for [DirectMintConnection],
  48. /// where we know we're dealing with a mint that uses [Uuid]s for quotes.
  49. /// Convert the requests and responses between the [String] and [Uuid] variants as necessary.
  50. #[async_trait]
  51. impl MintConnector for DirectMintConnection {
  52. async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
  53. Ok(self.mint.pubkeys().keysets)
  54. }
  55. async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
  56. self.mint.keyset(&keyset_id).ok_or(Error::UnknownKeySet)
  57. }
  58. async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
  59. Ok(self.mint.keysets())
  60. }
  61. async fn post_mint_quote(
  62. &self,
  63. request: MintQuoteBolt11Request,
  64. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  65. self.mint
  66. .get_mint_bolt11_quote(request)
  67. .await
  68. .map(Into::into)
  69. }
  70. async fn get_mint_quote_status(
  71. &self,
  72. quote_id: &str,
  73. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  74. let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
  75. self.mint
  76. .check_mint_quote(&quote_id_uuid)
  77. .await
  78. .map(Into::into)
  79. }
  80. async fn post_mint(
  81. &self,
  82. request: MintBolt11Request<String>,
  83. ) -> Result<MintBolt11Response, Error> {
  84. let request_uuid = request.try_into().unwrap();
  85. self.mint.process_mint_request(request_uuid).await
  86. }
  87. async fn post_melt_quote(
  88. &self,
  89. request: MeltQuoteBolt11Request,
  90. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  91. self.mint
  92. .get_melt_bolt11_quote(&request)
  93. .await
  94. .map(Into::into)
  95. }
  96. async fn get_melt_quote_status(
  97. &self,
  98. quote_id: &str,
  99. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  100. let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
  101. self.mint
  102. .check_melt_quote(&quote_id_uuid)
  103. .await
  104. .map(Into::into)
  105. }
  106. async fn post_melt(
  107. &self,
  108. request: MeltBolt11Request<String>,
  109. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  110. let request_uuid = request.try_into().unwrap();
  111. self.mint.melt_bolt11(&request_uuid).await.map(Into::into)
  112. }
  113. async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
  114. self.mint.process_swap_request(swap_request).await
  115. }
  116. async fn get_mint_info(&self) -> Result<MintInfo, Error> {
  117. Ok(self.mint.mint_info().await?.clone().time(unix_time()))
  118. }
  119. async fn post_check_state(
  120. &self,
  121. request: CheckStateRequest,
  122. ) -> Result<CheckStateResponse, Error> {
  123. self.mint.check_state(&request).await
  124. }
  125. async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
  126. self.mint.restore(request).await
  127. }
  128. /// Get the auth wallet for the client
  129. async fn get_auth_wallet(&self) -> Option<AuthWallet> {
  130. self.auth_wallet.read().await.clone()
  131. }
  132. /// Set auth wallet on client
  133. async fn set_auth_wallet(&self, wallet: Option<AuthWallet>) {
  134. let mut auth_wallet = self.auth_wallet.write().await;
  135. *auth_wallet = wallet;
  136. }
  137. }
  138. pub fn setup_tracing() {
  139. let default_filter = "debug";
  140. let sqlx_filter = "sqlx=warn";
  141. let hyper_filter = "hyper=warn";
  142. let env_filter = EnvFilter::new(format!(
  143. "{},{},{}",
  144. default_filter, sqlx_filter, hyper_filter
  145. ));
  146. // Ok if successful, Err if already initialized
  147. // Allows us to setup tracing at the start of several parallel tests
  148. let _ = tracing_subscriber::fmt()
  149. .with_env_filter(env_filter)
  150. .try_init();
  151. }
  152. pub async fn create_and_start_test_mint() -> Result<Mint> {
  153. // Read environment variable to determine database type
  154. let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
  155. let mut mint_builder = match db_type.to_lowercase().as_str() {
  156. "sqlite" => {
  157. // Create a temporary directory for SQLite database
  158. let temp_dir = create_temp_dir("cdk-test-sqlite-mint")?;
  159. let path = temp_dir.join("mint.db").to_str().unwrap().to_string();
  160. let database = Arc::new(
  161. cdk_sqlite::MintSqliteDatabase::new(&path)
  162. .await
  163. .expect("Could not create sqlite db"),
  164. );
  165. MintBuilder::new()
  166. .with_localstore(database.clone())
  167. .with_keystore(database)
  168. }
  169. "redb" => {
  170. // Create a temporary directory for ReDB database
  171. let temp_dir = create_temp_dir("cdk-test-redb-mint")?;
  172. let path = temp_dir.join("mint.redb");
  173. let database = Arc::new(
  174. cdk_redb::MintRedbDatabase::new(&path)
  175. .expect("Could not create redb mint database"),
  176. );
  177. MintBuilder::new()
  178. .with_localstore(database.clone())
  179. .with_keystore(database)
  180. }
  181. "memory" => MintBuilder::new()
  182. .with_localstore(Arc::new(cdk_sqlite::mint::memory::empty().await?))
  183. .with_keystore(Arc::new(cdk_sqlite::mint::memory::empty().await?)),
  184. _ => {
  185. bail!("Db type not set")
  186. }
  187. };
  188. let fee_reserve = FeeReserve {
  189. min_fee_reserve: 1.into(),
  190. percent_fee_reserve: 1.0,
  191. };
  192. let ln_fake_backend = FakeWallet::new(
  193. fee_reserve.clone(),
  194. HashMap::default(),
  195. HashSet::default(),
  196. 0,
  197. );
  198. mint_builder = mint_builder
  199. .add_ln_backend(
  200. CurrencyUnit::Sat,
  201. PaymentMethod::Bolt11,
  202. MintMeltLimits::new(1, 10_000),
  203. Arc::new(ln_fake_backend),
  204. )
  205. .await?;
  206. let mnemonic = Mnemonic::generate(12)?;
  207. mint_builder = mint_builder
  208. .with_name("pure test mint".to_string())
  209. .with_description("pure test mint".to_string())
  210. .with_urls(vec!["https://aaa".to_string()])
  211. .with_seed(mnemonic.to_seed_normalized("").to_vec());
  212. let localstore = mint_builder
  213. .localstore
  214. .as_ref()
  215. .map(|x| x.clone())
  216. .expect("localstore");
  217. localstore
  218. .set_mint_info(mint_builder.mint_info.clone())
  219. .await?;
  220. let quote_ttl = QuoteTTL::new(10000, 10000);
  221. localstore.set_quote_ttl(quote_ttl).await?;
  222. let mint = mint_builder.build().await?;
  223. let mint_clone = mint.clone();
  224. let shutdown = Arc::new(Notify::new());
  225. tokio::spawn({
  226. let shutdown = Arc::clone(&shutdown);
  227. async move { mint_clone.wait_for_paid_invoices(shutdown).await }
  228. });
  229. Ok(mint)
  230. }
  231. pub async fn create_test_wallet_for_mint(mint: Mint) -> Result<Wallet> {
  232. let connector = DirectMintConnection::new(mint.clone());
  233. let mint_info = mint.mint_info().await?;
  234. let mint_url = mint_info
  235. .urls
  236. .as_ref()
  237. .ok_or(anyhow!("Test mint URLs list is unset"))?
  238. .first()
  239. .ok_or(anyhow!("Test mint has empty URLs list"))?;
  240. let seed = Mnemonic::generate(12)?.to_seed_normalized("");
  241. let unit = CurrencyUnit::Sat;
  242. // Read environment variable to determine database type
  243. let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
  244. let localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync> =
  245. match db_type.to_lowercase().as_str() {
  246. "sqlite" => {
  247. // Create a temporary directory for SQLite database
  248. let temp_dir = create_temp_dir("cdk-test-sqlite-wallet")?;
  249. let path = temp_dir.join("wallet.db").to_str().unwrap().to_string();
  250. let database = cdk_sqlite::WalletSqliteDatabase::new(&path)
  251. .await
  252. .expect("Could not create sqlite db");
  253. Arc::new(database)
  254. }
  255. "redb" => {
  256. // Create a temporary directory for ReDB database
  257. let temp_dir = create_temp_dir("cdk-test-redb-wallet")?;
  258. let path = temp_dir.join("wallet.redb");
  259. let database = cdk_redb::WalletRedbDatabase::new(&path)
  260. .expect("Could not create redb mint database");
  261. Arc::new(database)
  262. }
  263. "memory" => {
  264. let database = cdk_sqlite::wallet::memory::empty().await?;
  265. Arc::new(database)
  266. }
  267. _ => {
  268. bail!("Db type not set")
  269. }
  270. };
  271. let wallet = WalletBuilder::new()
  272. .mint_url(mint_url.parse().unwrap())
  273. .unit(unit)
  274. .localstore(localstore)
  275. .seed(&seed)
  276. .client(connector)
  277. .build()?;
  278. Ok(wallet)
  279. }
  280. /// Creates a mint quote for the given amount and checks its state in a loop. Returns when
  281. /// amount is minted.
  282. /// Creates a temporary directory with a unique name based on the prefix
  283. fn create_temp_dir(prefix: &str) -> Result<PathBuf> {
  284. let temp_dir = env::temp_dir();
  285. let unique_dir = temp_dir.join(format!("{}-{}", prefix, Uuid::new_v4()));
  286. fs::create_dir_all(&unique_dir)?;
  287. Ok(unique_dir)
  288. }
  289. pub async fn fund_wallet(
  290. wallet: Wallet,
  291. amount: u64,
  292. split_target: Option<SplitTarget>,
  293. ) -> Result<Amount> {
  294. let desired_amount = Amount::from(amount);
  295. let quote = wallet.mint_quote(desired_amount, None).await?;
  296. wait_for_mint_to_be_paid(&wallet, &quote.id, 60).await?;
  297. Ok(wallet
  298. .mint(&quote.id, split_target.unwrap_or_default(), None)
  299. .await?
  300. .total_amount()?)
  301. }