init_pure_tests.rs 8.5 KB

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