diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a018018d..0bd07f77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,6 +180,21 @@ jobs: run: nix develop -i -L .#stable --command cargo clippy ${{ matrix.build-args }} -- -D warnings - name: Test fake mint run: nix develop -i -L .#stable --command just fake-mint-itest ${{ matrix.database }} + + pure-itest: + name: "Integration fake wallet tests" + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v11 + - name: Nix Cache + uses: DeterminateSystems/magic-nix-cache-action@v6 + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + - name: Test fake mint + run: nix develop -i -L .#stable --command just test msrv-build: name: "MSRV build" diff --git a/CHANGELOG.md b/CHANGELOG.md index db6dae7c..7367a59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,8 @@ * `Wallet::receive_raw` which receives raw binary tokens ([lollerfirst]). ### Fixed -* Multimint unit check when wallet receiving token ([thesimplekid]) +* Multimint unit check when wallet receiving token ([thesimplekid]). +* Mint start up with most recent keyset after a rotation ([thesimplekid]). ### Removed diff --git a/crates/cdk-integration-tests/tests/mint.rs b/crates/cdk-integration-tests/tests/mint.rs index b06223df..265c031b 100644 --- a/crates/cdk-integration-tests/tests/mint.rs +++ b/crates/cdk-integration-tests/tests/mint.rs @@ -1,6 +1,6 @@ //! Mint tests -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::Duration; @@ -10,15 +10,16 @@ use cdk::amount::{Amount, SplitTarget}; use cdk::cdk_database::mint_memory::MintMemoryDatabase; use cdk::cdk_database::MintDatabase; use cdk::dhke::construct_proofs; -use cdk::mint::MintQuote; +use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits, MintQuote}; use cdk::nuts::nut00::ProofsMethods; use cdk::nuts::{ - CurrencyUnit, Id, MintBolt11Request, MintInfo, NotificationPayload, Nuts, PreMintSecrets, - ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest, + CurrencyUnit, Id, MintBolt11Request, MintInfo, NotificationPayload, Nuts, PaymentMethod, + PreMintSecrets, ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest, }; use cdk::subscription::{IndexableParams, Params}; use cdk::util::unix_time; use cdk::Mint; +use cdk_fake_wallet::FakeWallet; use tokio::sync::OnceCell; use tokio::time::sleep; @@ -335,7 +336,7 @@ async fn test_swap_unbalanced() -> Result<()> { async fn test_swap_overpay_underpay_fee() -> Result<()> { let mint = new_mint(1).await; - mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, HashMap::new()) + mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new()) .await?; let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys; @@ -437,3 +438,69 @@ async fn test_mint_enforce_fee() -> Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_correct_keyset() -> Result<()> { + let mnemonic = Mnemonic::generate(12)?; + let fee_reserve = FeeReserve { + min_fee_reserve: 1.into(), + percent_fee_reserve: 1.0, + }; + + let database = MintMemoryDatabase::default(); + + let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); + + let mut mint_builder = MintBuilder::new(); + let localstore = Arc::new(database); + mint_builder = mint_builder.with_localstore(localstore.clone()); + + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Sat, + PaymentMethod::Bolt11, + MintMeltLimits::new(1, 5_000), + Arc::new(fake_wallet), + ); + + mint_builder = mint_builder + .with_name("regtest mint".to_string()) + .with_description("regtest mint".to_string()) + .with_quote_ttl(10000, 1000) + .with_seed(mnemonic.to_seed_normalized("").to_vec()); + + let mint = mint_builder.build().await?; + + mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?; + mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?; + + let active = mint.localstore.get_active_keysets().await?; + + let active = active + .get(&CurrencyUnit::Sat) + .expect("There is a keyset for unit"); + + let keyset_info = mint + .localstore + .get_keyset_info(active) + .await? + .expect("There is keyset"); + + assert!(keyset_info.derivation_path_index == Some(2)); + + let mint = mint_builder.build().await?; + + let active = mint.localstore.get_active_keysets().await?; + + let active = active + .get(&CurrencyUnit::Sat) + .expect("There is a keyset for unit"); + + let keyset_info = mint + .localstore + .get_keyset_info(active) + .await? + .expect("There is keyset"); + + assert!(keyset_info.derivation_path_index == Some(2)); + Ok(()) +} diff --git a/crates/cdk/src/mint/keysets.rs b/crates/cdk/src/mint/keysets.rs index c099381c..7dd96bee 100644 --- a/crates/cdk/src/mint/keysets.rs +++ b/crates/cdk/src/mint/keysets.rs @@ -96,8 +96,8 @@ impl Mint { derivation_path_index: u32, max_order: u8, input_fee_ppk: u64, - custom_paths: HashMap, - ) -> Result<(), Error> { + custom_paths: &HashMap, + ) -> Result { let derivation_path = match custom_paths.get(&unit) { Some(path) => path.clone(), None => derivation_path_from_unit(unit.clone(), derivation_path_index) @@ -114,13 +114,52 @@ impl Mint { input_fee_ppk, ); let id = keyset_info.id; - self.localstore.add_keyset_info(keyset_info).await?; + self.localstore.add_keyset_info(keyset_info.clone()).await?; self.localstore.set_active_keyset(unit, id).await?; let mut keysets = self.keysets.write().await; keysets.insert(id, keyset); - Ok(()) + Ok(keyset_info) + } + + /// Rotate to next keyset for unit + #[instrument(skip(self))] + pub async fn rotate_next_keyset( + &self, + unit: CurrencyUnit, + max_order: u8, + input_fee_ppk: u64, + ) -> Result { + let current_keyset_id = self + .localstore + .get_active_keyset_id(&unit) + .await? + .ok_or(Error::UnsupportedUnit)?; + + let keyset_info = self + .localstore + .get_keyset_info(¤t_keyset_id) + .await? + .ok_or(Error::UnknownKeySet)?; + + tracing::debug!( + "Current active keyset {} path index {:?}", + keyset_info.id, + keyset_info.derivation_path_index + ); + + let keyset_info = self + .rotate_keyset( + unit, + keyset_info.derivation_path_index.unwrap_or(1) + 1, + max_order, + input_fee_ppk, + &self.custom_paths, + ) + .await?; + + Ok(keyset_info) } /// Ensure Keyset is loaded in mint diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 910c4da2..64417ba6 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -48,6 +48,7 @@ pub struct Mint { secp_ctx: Secp256k1, xpriv: Xpriv, keysets: Arc>>, + custom_paths: HashMap, } impl Mint { @@ -104,6 +105,7 @@ impl Mint { } else if &highest_index_keyset.input_fee_ppk == input_fee_ppk && &highest_index_keyset.max_order == max_order { + tracing::debug!("Current highest index keyset matches expect fee and max order. Setting active"); let id = highest_index_keyset.id; let keyset = MintKeySet::generate_from_xpriv( &secp_ctx, @@ -116,6 +118,7 @@ impl Mint { let mut keyset_info = highest_index_keyset; keyset_info.active = true; localstore.add_keyset_info(keyset_info).await?; + active_keyset_units.push(unit.clone()); localstore.set_active_keyset(unit, id).await?; continue; } else { @@ -182,6 +185,7 @@ impl Mint { localstore, ln, keysets, + custom_paths, }) } @@ -743,7 +747,7 @@ mod tests { assert!(keysets.keysets.is_empty()); // generate the first keyset and set it to active - mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1, HashMap::new()) + mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1, &HashMap::new()) .await?; let keysets = mint.keysets().await.unwrap(); @@ -752,7 +756,7 @@ mod tests { let first_keyset_id = keysets.keysets[0].id; // set the first keyset to inactive and generate a new keyset - mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1, HashMap::new()) + mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1, &HashMap::new()) .await?; let keysets = mint.keysets().await.unwrap(); @@ -784,7 +788,7 @@ mod tests { }; let mint = create_mint(config).await?; - mint.rotate_keyset(CurrencyUnit::default(), 0, 32, 1, HashMap::new()) + mint.rotate_keyset(CurrencyUnit::default(), 0, 32, 1, &HashMap::new()) .await?; let keys = mint.keysets.read().await.clone(); diff --git a/justfile b/justfile index 67053476..17b468ab 100644 --- a/justfile +++ b/justfile @@ -49,6 +49,7 @@ test: build # Run pure integration tests cargo test -p cdk-integration-tests --test integration_tests_pure + cargo test -p cdk-integration-tests --test mint # run `cargo clippy` on everything clippy *ARGS="--locked --offline --workspace --all-targets":