mod.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. //! Cashu Mint
  2. use std::collections::HashMap;
  3. use std::sync::Arc;
  4. use arc_swap::ArcSwap;
  5. use bitcoin::bip32::{DerivationPath, Xpriv};
  6. use bitcoin::secp256k1;
  7. use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
  8. #[cfg(feature = "auth")]
  9. use cdk_common::database::MintAuthDatabase;
  10. use cdk_common::database::{self, MintDatabase};
  11. use cdk_common::nuts::{
  12. self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind, MintKeySet, Proof,
  13. };
  14. use cdk_common::secret;
  15. use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
  16. use futures::StreamExt;
  17. #[cfg(feature = "auth")]
  18. use nut21::ProtectedEndpoint;
  19. use subscription::PubSubManager;
  20. use tokio::sync::Notify;
  21. use tokio::task::JoinSet;
  22. use tracing::instrument;
  23. use uuid::Uuid;
  24. use crate::cdk_payment::{self, MintPayment};
  25. use crate::error::Error;
  26. use crate::fees::calculate_fee;
  27. use crate::nuts::*;
  28. use crate::util::unix_time;
  29. use crate::Amount;
  30. #[cfg(feature = "auth")]
  31. use crate::OidcClient;
  32. #[cfg(feature = "auth")]
  33. pub(crate) mod auth;
  34. mod builder;
  35. mod check_spendable;
  36. mod issue;
  37. mod keysets;
  38. mod ln;
  39. mod melt;
  40. mod start_up_check;
  41. pub mod subscription;
  42. mod swap;
  43. mod verification;
  44. pub use builder::{MintBuilder, MintMeltLimits};
  45. pub use cdk_common::mint::{MeltQuote, MintKeySetInfo, MintQuote};
  46. pub use verification::Verification;
  47. /// Cashu Mint
  48. #[derive(Clone)]
  49. pub struct Mint {
  50. /// Signatory backend.
  51. ///
  52. /// It is mainly implemented in the cdk-signatory crate, and it can be embedded in the mint or
  53. /// it can be a gRPC client to a remote signatory server.
  54. pub signatory: Arc<dyn Signatory + Send + Sync>,
  55. /// Mint Storage backend
  56. pub localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
  57. /// Auth Storage backend (only available with auth feature)
  58. #[cfg(feature = "auth")]
  59. pub auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>,
  60. /// Ln backends for mint
  61. pub ln:
  62. HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
  63. /// Subscription manager
  64. pub pubsub_manager: Arc<PubSubManager>,
  65. #[cfg(feature = "auth")]
  66. oidc_client: Option<OidcClient>,
  67. /// In-memory keyset
  68. keysets: Arc<ArcSwap<Vec<SignatoryKeySet>>>,
  69. }
  70. impl Mint {
  71. /// Get the payment processor for the given unit and payment method
  72. pub fn get_payment_processor(
  73. &self,
  74. unit: CurrencyUnit,
  75. payment_method: PaymentMethod,
  76. ) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
  77. let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
  78. self.ln.get(&key).cloned().ok_or_else(|| {
  79. tracing::info!(
  80. "No payment processor set for pair {}, {}",
  81. unit,
  82. payment_method
  83. );
  84. Error::UnsupportedUnit
  85. })
  86. }
  87. /// Create new [`Mint`] without authentication
  88. pub async fn new(
  89. signatory: Arc<dyn Signatory + Send + Sync>,
  90. localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
  91. ln: HashMap<
  92. PaymentProcessorKey,
  93. Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
  94. >,
  95. ) -> Result<Self, Error> {
  96. Self::new_internal(
  97. signatory,
  98. localstore,
  99. #[cfg(feature = "auth")]
  100. None,
  101. ln,
  102. #[cfg(feature = "auth")]
  103. None,
  104. )
  105. .await
  106. }
  107. /// Create new [`Mint`] with authentication support
  108. #[cfg(feature = "auth")]
  109. pub async fn new_with_auth(
  110. signatory: Arc<dyn Signatory + Send + Sync>,
  111. localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
  112. auth_localstore: Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>,
  113. ln: HashMap<
  114. PaymentProcessorKey,
  115. Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
  116. >,
  117. open_id_discovery: String,
  118. ) -> Result<Self, Error> {
  119. Self::new_internal(
  120. signatory,
  121. localstore,
  122. Some(auth_localstore),
  123. ln,
  124. Some(open_id_discovery),
  125. )
  126. .await
  127. }
  128. /// Internal function to create a new [`Mint`] with shared logic
  129. #[inline]
  130. async fn new_internal(
  131. signatory: Arc<dyn Signatory + Send + Sync>,
  132. localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
  133. #[cfg(feature = "auth")] auth_localstore: Option<
  134. Arc<dyn database::MintAuthDatabase<Err = database::Error> + Send + Sync>,
  135. >,
  136. ln: HashMap<
  137. PaymentProcessorKey,
  138. Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
  139. >,
  140. #[cfg(feature = "auth")] open_id_discovery: Option<String>,
  141. ) -> Result<Self, Error> {
  142. #[cfg(feature = "auth")]
  143. let oidc_client =
  144. open_id_discovery.map(|openid_discovery| OidcClient::new(openid_discovery.clone()));
  145. let keysets = signatory.keysets().await?;
  146. Ok(Self {
  147. signatory,
  148. pubsub_manager: Arc::new(localstore.clone().into()),
  149. localstore,
  150. #[cfg(feature = "auth")]
  151. oidc_client,
  152. ln,
  153. #[cfg(feature = "auth")]
  154. auth_localstore,
  155. keysets: Arc::new(ArcSwap::new(keysets.keysets.into())),
  156. })
  157. }
  158. /// Get mint info
  159. #[instrument(skip_all)]
  160. pub async fn mint_info(&self) -> Result<MintInfo, Error> {
  161. let mint_info = self.localstore.get_mint_info().await?;
  162. #[cfg(feature = "auth")]
  163. let mint_info = if let Some(auth_db) = self.auth_localstore.as_ref() {
  164. let mut mint_info = mint_info;
  165. let auth_endpoints = auth_db.get_auth_for_endpoints().await?;
  166. let mut clear_auth_endpoints: Vec<ProtectedEndpoint> = vec![];
  167. let mut blind_auth_endpoints: Vec<ProtectedEndpoint> = vec![];
  168. for (endpoint, auth) in auth_endpoints {
  169. match auth {
  170. Some(AuthRequired::Clear) => {
  171. clear_auth_endpoints.push(endpoint);
  172. }
  173. Some(AuthRequired::Blind) => {
  174. blind_auth_endpoints.push(endpoint);
  175. }
  176. None => (),
  177. }
  178. }
  179. mint_info.nuts.nut21 = mint_info.nuts.nut21.map(|mut a| {
  180. a.protected_endpoints = clear_auth_endpoints;
  181. a
  182. });
  183. mint_info.nuts.nut22 = mint_info.nuts.nut22.map(|mut a| {
  184. a.protected_endpoints = blind_auth_endpoints;
  185. a
  186. });
  187. mint_info
  188. } else {
  189. mint_info
  190. };
  191. Ok(mint_info)
  192. }
  193. /// Set mint info
  194. #[instrument(skip_all)]
  195. pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
  196. Ok(self.localstore.set_mint_info(mint_info).await?)
  197. }
  198. /// Get quote ttl
  199. #[instrument(skip_all)]
  200. pub async fn quote_ttl(&self) -> Result<QuoteTTL, Error> {
  201. Ok(self.localstore.get_quote_ttl().await?)
  202. }
  203. /// Set quote ttl
  204. #[instrument(skip_all)]
  205. pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
  206. Ok(self.localstore.set_quote_ttl(quote_ttl).await?)
  207. }
  208. /// Wait for any invoice to be paid
  209. /// For each backend starts a task that waits for any invoice to be paid
  210. /// Once invoice is paid mint quote status is updated
  211. #[instrument(skip_all)]
  212. pub async fn wait_for_paid_invoices(&self, shutdown: Arc<Notify>) -> Result<(), Error> {
  213. let mint_arc = Arc::new(self.clone());
  214. let mut join_set = JoinSet::new();
  215. for (key, ln) in self.ln.iter() {
  216. if !ln.is_wait_invoice_active() {
  217. tracing::info!("Wait payment for {:?} inactive starting.", key);
  218. let mint = Arc::clone(&mint_arc);
  219. let ln = Arc::clone(ln);
  220. let shutdown = Arc::clone(&shutdown);
  221. let key = key.clone();
  222. join_set.spawn(async move {
  223. loop {
  224. tracing::info!("Restarting wait for: {:?}", key);
  225. tokio::select! {
  226. _ = shutdown.notified() => {
  227. tracing::info!("Shutdown signal received, stopping task for {:?}", key);
  228. ln.cancel_wait_invoice();
  229. break;
  230. }
  231. result = ln.wait_any_incoming_payment() => {
  232. match result {
  233. Ok(mut stream) => {
  234. while let Some(request_lookup_id) = stream.next().await {
  235. if let Err(err) = mint.pay_mint_quote_for_request_id(&request_lookup_id).await {
  236. tracing::warn!("{:?}", err);
  237. }
  238. }
  239. }
  240. Err(err) => {
  241. tracing::warn!("Could not get incoming payment stream for {:?}: {}",key, err);
  242. tokio::time::sleep(std::time::Duration::from_secs(5)).await;
  243. }
  244. }
  245. }
  246. }
  247. }
  248. });
  249. }
  250. }
  251. // Spawn a task to manage the JoinSet
  252. while let Some(result) = join_set.join_next().await {
  253. match result {
  254. Ok(_) => tracing::info!("A task completed successfully."),
  255. Err(err) => tracing::warn!("A task failed: {:?}", err),
  256. }
  257. }
  258. Ok(())
  259. }
  260. /// Fee required for proof set
  261. #[instrument(skip_all)]
  262. pub async fn get_proofs_fee(&self, proofs: &Proofs) -> Result<Amount, Error> {
  263. let mut proofs_per_keyset = HashMap::new();
  264. let mut fee_per_keyset = HashMap::new();
  265. for proof in proofs {
  266. if let std::collections::hash_map::Entry::Vacant(e) =
  267. fee_per_keyset.entry(proof.keyset_id)
  268. {
  269. let mint_keyset_info = self
  270. .get_keyset_info(&proof.keyset_id)
  271. .ok_or(Error::UnknownKeySet)?;
  272. e.insert(mint_keyset_info.input_fee_ppk);
  273. }
  274. proofs_per_keyset
  275. .entry(proof.keyset_id)
  276. .and_modify(|count| *count += 1)
  277. .or_insert(1);
  278. }
  279. let fee = calculate_fee(&proofs_per_keyset, &fee_per_keyset)?;
  280. Ok(fee)
  281. }
  282. /// Get active keysets
  283. pub fn get_active_keysets(&self) -> HashMap<CurrencyUnit, Id> {
  284. self.keysets
  285. .load()
  286. .iter()
  287. .filter_map(|keyset| {
  288. if keyset.active {
  289. Some((keyset.unit.clone(), keyset.id))
  290. } else {
  291. None
  292. }
  293. })
  294. .collect()
  295. }
  296. /// Get keyset info
  297. pub fn get_keyset_info(&self, id: &Id) -> Option<MintKeySetInfo> {
  298. self.keysets
  299. .load()
  300. .iter()
  301. .filter_map(|keyset| {
  302. if keyset.id == *id {
  303. Some(keyset.into())
  304. } else {
  305. None
  306. }
  307. })
  308. .next()
  309. }
  310. /// Blind Sign
  311. #[instrument(skip_all)]
  312. pub async fn blind_sign(
  313. &self,
  314. blinded_message: &BlindedMessage,
  315. ) -> Result<BlindSignature, Error> {
  316. self.signatory
  317. .blind_sign(vec![blinded_message.to_owned()])
  318. .await?
  319. .pop()
  320. .ok_or(Error::Internal)
  321. }
  322. /// Verify [`Proof`] meets conditions and is signed
  323. #[instrument(skip_all)]
  324. pub async fn verify_proofs(&self, proofs: &[Proof]) -> Result<(), Error> {
  325. proofs
  326. .iter()
  327. .map(|proof| {
  328. // Check if secret is a nut10 secret with conditions
  329. if let Ok(secret) =
  330. <&secret::Secret as TryInto<nuts::nut10::Secret>>::try_into(&proof.secret)
  331. {
  332. // Checks and verifies known secret kinds.
  333. // If it is an unknown secret kind it will be treated as a normal secret.
  334. // Spending conditions will **not** be check. It is up to the wallet to ensure
  335. // only supported secret kinds are used as there is no way for the mint to
  336. // enforce only signing supported secrets as they are blinded at
  337. // that point.
  338. match secret.kind {
  339. Kind::P2PK => {
  340. proof.verify_p2pk()?;
  341. }
  342. Kind::HTLC => {
  343. proof.verify_htlc()?;
  344. }
  345. }
  346. }
  347. Ok(())
  348. })
  349. .collect::<Result<Vec<()>, Error>>()?;
  350. self.signatory.verify_proofs(proofs.to_owned()).await
  351. }
  352. /// Verify melt request is valid
  353. /// Check to see if there is a corresponding mint quote for a melt.
  354. /// In this case the mint can settle the payment internally and no ln payment is
  355. /// needed
  356. #[instrument(skip_all)]
  357. pub async fn handle_internal_melt_mint(
  358. &self,
  359. melt_quote: &MeltQuote,
  360. melt_request: &MeltRequest<Uuid>,
  361. ) -> Result<Option<Amount>, Error> {
  362. let mint_quote = match self
  363. .localstore
  364. .get_mint_quote_by_request(&melt_quote.request)
  365. .await
  366. {
  367. Ok(Some(mint_quote)) => mint_quote,
  368. // Not an internal melt -> mint
  369. Ok(None) => return Ok(None),
  370. Err(err) => {
  371. tracing::debug!("Error attempting to get mint quote: {}", err);
  372. return Err(Error::Internal);
  373. }
  374. };
  375. // Mint quote has already been settled, proofs should not be burned or held.
  376. if mint_quote.state == MintQuoteState::Issued || mint_quote.state == MintQuoteState::Paid {
  377. return Err(Error::RequestAlreadyPaid);
  378. }
  379. let inputs_amount_quote_unit = melt_request.proofs_amount().map_err(|_| {
  380. tracing::error!("Proof inputs in melt quote overflowed");
  381. Error::AmountOverflow
  382. })?;
  383. let mut mint_quote = mint_quote;
  384. if mint_quote.amount > inputs_amount_quote_unit {
  385. tracing::debug!(
  386. "Not enough inuts provided: {} needed {}",
  387. inputs_amount_quote_unit,
  388. mint_quote.amount
  389. );
  390. return Err(Error::InsufficientFunds);
  391. }
  392. mint_quote.state = MintQuoteState::Paid;
  393. let amount = melt_quote.amount;
  394. self.update_mint_quote(mint_quote).await?;
  395. Ok(Some(amount))
  396. }
  397. /// Restore
  398. #[instrument(skip_all)]
  399. pub async fn restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
  400. let output_len = request.outputs.len();
  401. let mut outputs = Vec::with_capacity(output_len);
  402. let mut signatures = Vec::with_capacity(output_len);
  403. let blinded_message: Vec<PublicKey> =
  404. request.outputs.iter().map(|b| b.blinded_secret).collect();
  405. let blinded_signatures = self
  406. .localstore
  407. .get_blind_signatures(&blinded_message)
  408. .await?;
  409. assert_eq!(blinded_signatures.len(), output_len);
  410. for (blinded_message, blinded_signature) in
  411. request.outputs.into_iter().zip(blinded_signatures)
  412. {
  413. if let Some(blinded_signature) = blinded_signature {
  414. outputs.push(blinded_message);
  415. signatures.push(blinded_signature);
  416. }
  417. }
  418. Ok(RestoreResponse {
  419. outputs,
  420. signatures: signatures.clone(),
  421. promises: Some(signatures),
  422. })
  423. }
  424. /// Get the total amount issed by keyset
  425. #[instrument(skip_all)]
  426. pub async fn total_issued(&self) -> Result<HashMap<Id, Amount>, Error> {
  427. let keysets = self.keysets().keysets;
  428. let mut total_issued = HashMap::new();
  429. for keyset in keysets {
  430. let blinded = self
  431. .localstore
  432. .get_blind_signatures_for_keyset(&keyset.id)
  433. .await?;
  434. let total = Amount::try_sum(blinded.iter().map(|b| b.amount))?;
  435. total_issued.insert(keyset.id, total);
  436. }
  437. Ok(total_issued)
  438. }
  439. /// Total redeemed for keyset
  440. #[instrument(skip_all)]
  441. pub async fn total_redeemed(&self) -> Result<HashMap<Id, Amount>, Error> {
  442. let keysets = self.keysets().keysets;
  443. let mut total_redeemed = HashMap::new();
  444. for keyset in keysets {
  445. let (proofs, state) = self.localstore.get_proofs_by_keyset_id(&keyset.id).await?;
  446. let total_spent =
  447. Amount::try_sum(proofs.iter().zip(state).filter_map(|(p, s)| {
  448. match s == Some(State::Spent) {
  449. true => Some(p.amount),
  450. false => None,
  451. }
  452. }))?;
  453. total_redeemed.insert(keyset.id, total_spent);
  454. }
  455. Ok(total_redeemed)
  456. }
  457. }
  458. /// Generate new [`MintKeySetInfo`] from path
  459. #[instrument(skip_all)]
  460. fn create_new_keyset<C: secp256k1::Signing>(
  461. secp: &secp256k1::Secp256k1<C>,
  462. xpriv: Xpriv,
  463. derivation_path: DerivationPath,
  464. derivation_path_index: Option<u32>,
  465. unit: CurrencyUnit,
  466. max_order: u8,
  467. input_fee_ppk: u64,
  468. ) -> (MintKeySet, MintKeySetInfo) {
  469. let keyset = MintKeySet::generate(
  470. secp,
  471. xpriv
  472. .derive_priv(secp, &derivation_path)
  473. .expect("RNG busted"),
  474. unit,
  475. max_order,
  476. );
  477. let keyset_info = MintKeySetInfo {
  478. id: keyset.id,
  479. unit: keyset.unit.clone(),
  480. active: true,
  481. valid_from: unix_time(),
  482. valid_to: None,
  483. derivation_path,
  484. derivation_path_index,
  485. max_order,
  486. input_fee_ppk,
  487. };
  488. (keyset, keyset_info)
  489. }
  490. #[cfg(test)]
  491. mod tests {
  492. use cdk_common::common::PaymentProcessorKey;
  493. use cdk_sqlite::mint::memory::new_with_state;
  494. use uuid::Uuid;
  495. use super::*;
  496. #[derive(Default)]
  497. struct MintConfig<'a> {
  498. active_keysets: HashMap<CurrencyUnit, Id>,
  499. keysets: Vec<MintKeySetInfo>,
  500. mint_quotes: Vec<MintQuote>,
  501. melt_quotes: Vec<MeltQuote>,
  502. pending_proofs: Proofs,
  503. spent_proofs: Proofs,
  504. seed: &'a [u8],
  505. mint_info: MintInfo,
  506. supported_units: HashMap<CurrencyUnit, (u64, u8)>,
  507. melt_requests: Vec<(MeltRequest<Uuid>, PaymentProcessorKey)>,
  508. }
  509. async fn create_mint(config: MintConfig<'_>) -> Mint {
  510. let localstore = Arc::new(
  511. new_with_state(
  512. config.active_keysets,
  513. config.keysets,
  514. config.mint_quotes,
  515. config.melt_quotes,
  516. config.pending_proofs,
  517. config.spent_proofs,
  518. config.melt_requests,
  519. config.mint_info,
  520. )
  521. .await
  522. .unwrap(),
  523. );
  524. let signatory = Arc::new(
  525. cdk_signatory::db_signatory::DbSignatory::new(
  526. localstore.clone(),
  527. None,
  528. config.seed,
  529. config.supported_units,
  530. HashMap::new(),
  531. )
  532. .await
  533. .expect("Failed to create signatory"),
  534. );
  535. Mint::new(signatory, localstore, HashMap::new())
  536. .await
  537. .unwrap()
  538. }
  539. #[tokio::test]
  540. async fn mint_mod_new_mint() {
  541. let config = MintConfig::<'_> {
  542. ..Default::default()
  543. };
  544. let mint = create_mint(config).await;
  545. assert_eq!(
  546. mint.pubkeys(),
  547. KeysResponse {
  548. keysets: Vec::new()
  549. }
  550. );
  551. assert_eq!(
  552. mint.keysets(),
  553. KeysetResponse {
  554. keysets: Vec::new()
  555. }
  556. );
  557. assert_eq!(
  558. mint.total_issued().await.unwrap(),
  559. HashMap::<nut02::Id, Amount>::new()
  560. );
  561. assert_eq!(
  562. mint.total_redeemed().await.unwrap(),
  563. HashMap::<nut02::Id, Amount>::new()
  564. );
  565. }
  566. #[tokio::test]
  567. async fn mint_mod_rotate_keyset() {
  568. let config = MintConfig::<'_> {
  569. ..Default::default()
  570. };
  571. let mint = create_mint(config).await;
  572. let keysets = mint.keysets();
  573. assert!(keysets.keysets.is_empty());
  574. // generate the first keyset and set it to active
  575. mint.rotate_keyset(CurrencyUnit::default(), 1, 1)
  576. .await
  577. .expect("test");
  578. let keysets = mint.keysets();
  579. assert!(keysets.keysets.len().eq(&1));
  580. assert!(keysets.keysets[0].active);
  581. let first_keyset_id = keysets.keysets[0].id;
  582. // set the first keyset to inactive and generate a new keyset
  583. mint.rotate_keyset(CurrencyUnit::default(), 1, 1)
  584. .await
  585. .expect("test");
  586. let keysets = mint.keysets();
  587. assert_eq!(2, keysets.keysets.len());
  588. for keyset in &keysets.keysets {
  589. if keyset.id == first_keyset_id {
  590. assert!(!keyset.active);
  591. } else {
  592. assert!(keyset.active);
  593. }
  594. }
  595. }
  596. }