123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- //! Mint tests
- use std::collections::HashMap;
- use std::sync::Arc;
- use std::time::Duration;
- use anyhow::{bail, Result};
- use bip39::Mnemonic;
- use cdk::amount::{Amount, SplitTarget};
- use cdk::cdk_database::mint_memory::MintMemoryDatabase;
- use cdk::dhke::construct_proofs;
- use cdk::mint::MintQuote;
- use cdk::nuts::nut00::ProofsMethods;
- use cdk::nuts::nut17::Params;
- use cdk::nuts::{
- CurrencyUnit, Id, MintBolt11Request, MintInfo, NotificationPayload, Nuts, PreMintSecrets,
- ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest,
- };
- use cdk::types::QuoteTTL;
- use cdk::util::unix_time;
- use cdk::Mint;
- use tokio::sync::OnceCell;
- use tokio::time::sleep;
- pub const MINT_URL: &str = "http://127.0.0.1:8088";
- static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
- async fn new_mint(fee: u64) -> Mint {
- let mut supported_units = HashMap::new();
- supported_units.insert(CurrencyUnit::Sat, (fee, 32));
- let nuts = Nuts::new()
- .nut07(true)
- .nut08(true)
- .nut09(true)
- .nut10(true)
- .nut11(true)
- .nut12(true)
- .nut14(true);
- let mint_info = MintInfo::new().nuts(nuts);
- let mnemonic = Mnemonic::generate(12).unwrap();
- let quote_ttl = QuoteTTL::new(10000, 10000);
- Mint::new(
- MINT_URL,
- &mnemonic.to_seed_normalized(""),
- mint_info,
- quote_ttl,
- Arc::new(MintMemoryDatabase::default()),
- HashMap::new(),
- supported_units,
- HashMap::new(),
- )
- .await
- .unwrap()
- }
- async fn initialize() -> &'static Mint {
- INSTANCE.get_or_init(|| new_mint(0)).await
- }
- async fn mint_proofs(
- mint: &Mint,
- amount: Amount,
- split_target: &SplitTarget,
- keys: cdk::nuts::Keys,
- ) -> Result<Proofs> {
- let request_lookup = uuid::Uuid::new_v4().to_string();
- let quote = MintQuote::new(
- mint.mint_url.clone(),
- "".to_string(),
- CurrencyUnit::Sat,
- amount,
- unix_time() + 36000,
- request_lookup.to_string(),
- );
- mint.localstore.add_mint_quote(quote.clone()).await?;
- mint.pay_mint_quote_for_request_id(&request_lookup).await?;
- let keyset_id = Id::from(&keys);
- let premint = PreMintSecrets::random(keyset_id, amount, split_target)?;
- let mint_request = MintBolt11Request {
- quote: quote.id,
- outputs: premint.blinded_messages(),
- };
- let after_mint = mint.process_mint_request(mint_request).await?;
- let proofs = construct_proofs(
- after_mint.signatures,
- premint.rs(),
- premint.secrets(),
- &keys,
- )?;
- Ok(proofs)
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_mint_double_spend() -> Result<()> {
- let mint = initialize().await;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
- let preswap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
- let swap = mint.process_swap_request(swap_request).await;
- assert!(swap.is_ok());
- let preswap_two = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
- let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
- match mint.process_swap_request(swap_two_request).await {
- Ok(_) => bail!("Proofs double spent"),
- Err(err) => match err {
- cdk::Error::TokenAlreadySpent => (),
- _ => bail!("Wrong error returned"),
- },
- }
- Ok(())
- }
- /// This attempts to swap for more outputs then inputs.
- /// This will work if the mint does not check for outputs amounts overflowing
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_attempt_to_swap_by_overflowing() -> Result<()> {
- let mint = initialize().await;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
- let amount = 2_u64.pow(63);
- let pre_mint_amount =
- PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
- let pre_mint_amount_two =
- PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
- let mut pre_mint = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
- pre_mint.combine(pre_mint_amount);
- pre_mint.combine(pre_mint_amount_two);
- let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap occurred with overflow"),
- Err(err) => match err {
- cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned in swap overflow")
- }
- },
- }
- Ok(())
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- pub async fn test_p2pk_swap() -> Result<()> {
- let mint = initialize().await;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
- let secret = SecretKey::generate();
- let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
- let pre_swap = PreMintSecrets::with_conditions(
- keyset_id,
- 100.into(),
- &SplitTarget::default(),
- &spending_conditions,
- )?;
- let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
- let keys = mint.pubkeys().await?.keysets.first().cloned().unwrap().keys;
- let post_swap = mint.process_swap_request(swap_request).await?;
- let mut proofs = construct_proofs(
- post_swap.signatures,
- pre_swap.rs(),
- pre_swap.secrets(),
- &keys,
- )?;
- let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
- let public_keys_to_listen: Vec<_> = swap_request
- .inputs
- .ys()
- .expect("key")
- .into_iter()
- .enumerate()
- .filter_map(|(key, pk)| {
- if key % 2 == 0 {
- // Only expect messages from every other key
- Some(pk.to_string())
- } else {
- None
- }
- })
- .collect();
- let mut listener = mint
- .pubsub_manager
- .try_subscribe(Params {
- kind: cdk::nuts::nut17::Kind::ProofState,
- filters: public_keys_to_listen.clone(),
- id: "test".into(),
- })
- .await
- .expect("valid subscription");
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Proofs spent without sig"),
- Err(err) => match err {
- cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned")
- }
- },
- }
- for proof in &mut proofs {
- proof.sign_p2pk(secret.clone())?;
- }
- let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
- let attempt_swap = mint.process_swap_request(swap_request).await;
- assert!(attempt_swap.is_ok());
- sleep(Duration::from_millis(10)).await;
- let mut msgs = HashMap::new();
- while let Ok((sub_id, msg)) = listener.try_recv() {
- assert_eq!(sub_id, "test".into());
- match msg {
- NotificationPayload::ProofState(ProofState { y, state, .. }) => {
- let pk = y.to_string();
- msgs.get_mut(&pk)
- .map(|x: &mut Vec<State>| {
- x.push(state);
- })
- .unwrap_or_else(|| {
- msgs.insert(pk, vec![state]);
- });
- }
- _ => bail!("Wrong message received"),
- }
- }
- for keys in public_keys_to_listen {
- let statuses = msgs.remove(&keys).expect("some events");
- assert_eq!(statuses, vec![State::Pending, State::Pending, State::Spent]);
- }
- assert!(listener.try_recv().is_err(), "no other event is happening");
- assert!(msgs.is_empty(), "Only expected key events are received");
- Ok(())
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_swap_unbalanced() -> Result<()> {
- let mint = initialize().await;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
- let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => bail!("Wrong error returned"),
- },
- }
- let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => bail!("Wrong error returned"),
- },
- }
- Ok(())
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_swap_overpay_underpay_fee() -> Result<()> {
- let mint = new_mint(1).await;
- mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, HashMap::new())
- .await?;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let proofs = mint_proofs(&mint, 1000.into(), &SplitTarget::default(), keys).await?;
- let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
- // Attempt to swap overpaying fee
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned")
- }
- },
- }
- let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
- // Attempt to swap underpaying fee
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned")
- }
- },
- }
- Ok(())
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_mint_enforce_fee() -> Result<()> {
- let mint = new_mint(1).await;
- let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
- let keyset_id = Id::from(&keys);
- let mut proofs = mint_proofs(&mint, 1010.into(), &SplitTarget::Value(1.into()), keys).await?;
- let five_proofs: Vec<_> = proofs.drain(..5).collect();
- let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
- // Attempt to swap underpaying fee
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned")
- }
- },
- }
- let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
- let _ = mint.process_swap_request(swap_request).await?;
- let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
- let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
- // Attempt to swap underpaying fee
- match mint.process_swap_request(swap_request).await {
- Ok(_) => bail!("Swap was allowed unbalanced"),
- Err(err) => match err {
- cdk::Error::TransactionUnbalanced(_, _, _) => (),
- _ => {
- println!("{:?}", err);
- bail!("Wrong error returned")
- }
- },
- }
- let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default())?;
- let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
- let _ = mint.process_swap_request(swap_request).await?;
- Ok(())
- }
|