1use std::collections::{BTreeMap, BTreeSet};
2use std::time::SystemTime;
3
4use anyhow::{anyhow, bail};
5use bitcoin::hex::DisplayHex as _;
6use fedimint_api_client::api::ApiVersionSet;
7use fedimint_client_module::db::ClientModuleMigrationFn;
8use fedimint_client_module::module::recovery::RecoveryProgress;
9use fedimint_client_module::oplog::{JsonStringed, OperationLogEntry, OperationOutcome};
10use fedimint_client_module::sm::{ActiveStateMeta, InactiveStateMeta};
11use fedimint_core::config::{ClientConfig, ClientConfigV0, FederationId, GlobalClientConfig};
12use fedimint_core::core::{ModuleInstanceId, OperationId};
13use fedimint_core::db::{
14 Database, DatabaseTransaction, DatabaseVersion, DatabaseVersionKey,
15 IDatabaseTransactionOpsCore, IDatabaseTransactionOpsCoreTyped, MODULE_GLOBAL_PREFIX,
16 apply_migrations_dbtx, create_database_version_dbtx, get_current_database_version,
17};
18use fedimint_core::encoding::{Decodable, Encodable};
19use fedimint_core::module::registry::ModuleRegistry;
20use fedimint_core::module::{Amounts, SupportedApiVersionsSummary};
21use fedimint_core::{ChainId, PeerId, TransactionId, impl_db_lookup, impl_db_record};
22use fedimint_eventlog::{
23 DB_KEY_PREFIX_EVENT_LOG, DB_KEY_PREFIX_UNORDERED_EVENT_LOG, EventLogId, UnordedEventLogId,
24};
25use fedimint_logging::LOG_CLIENT_DB;
26use futures::StreamExt;
27use serde::{Deserialize, Serialize};
28use strum::IntoEnumIterator as _;
29use strum_macros::EnumIter;
30use tracing::{debug, info, trace, warn};
31
32use crate::backup::{ClientBackup, Metadata};
33use crate::sm::executor::{
34 ActiveStateKeyBytes, ActiveStateKeyPrefixBytes, ExecutorDbPrefixes, InactiveStateKeyBytes,
35 InactiveStateKeyPrefixBytes,
36};
37
38#[repr(u8)]
39#[derive(Clone, EnumIter, Debug)]
40pub enum DbKeyPrefix {
41 EncodedClientSecret = 0x28,
42 ClientSecret = 0x29, ClientPreRootSecretHash = 0x2a,
44 OperationLog = 0x2c,
45 ChronologicalOperationLog = 0x2d,
46 CommonApiVersionCache = 0x2e,
47 ClientConfig = 0x2f,
48 PendingClientConfig = 0x3b,
49 ClientInviteCode = 0x30, ClientInitState = 0x31,
51 ClientMetadata = 0x32,
52 ClientLastBackup = 0x33,
53 ClientMetaField = 0x34,
54 ClientMetaServiceInfo = 0x35,
55 ApiSecret = 0x36,
56 PeerLastApiVersionsSummaryCache = 0x37,
57 ApiUrlAnnouncement = 0x38,
58 EventLog = fedimint_eventlog::DB_KEY_PREFIX_EVENT_LOG,
59 UnorderedEventLog = fedimint_eventlog::DB_KEY_PREFIX_UNORDERED_EVENT_LOG,
60 EventLogTrimable = fedimint_eventlog::DB_KEY_PREFIX_EVENT_LOG_TRIMABLE,
61 ChainId = 0x3c,
62 ClientModuleRecovery = 0x40,
63 GuardianMetadata = 0x42,
64 TransactionFees = 0x43,
65
66 DatabaseVersion = fedimint_core::db::DbKeyPrefix::DatabaseVersion as u8,
67 ClientBackup = fedimint_core::db::DbKeyPrefix::ClientBackup as u8,
68
69 ActiveStates = ExecutorDbPrefixes::ActiveStates as u8,
70 InactiveStates = ExecutorDbPrefixes::InactiveStates as u8,
71
72 UserData = 0xb0,
82 ExternalReservedStart = 0xb1,
85 ExternalReservedEnd = 0xcf,
88 InternalReservedStart = 0xd0,
91 ModuleGlobalPrefix = 0xff,
93}
94
95#[repr(u8)]
96#[derive(Clone, EnumIter, Debug)]
97pub(crate) enum DbKeyPrefixInternalReserved {
98 DefaultApplicationEventLogPos = 0xd0,
100}
101
102pub(crate) async fn verify_client_db_integrity_dbtx(dbtx: &mut DatabaseTransaction<'_>) {
103 let prefixes: BTreeSet<u8> = DbKeyPrefix::iter().map(|prefix| prefix as u8).collect();
104
105 let mut records = dbtx.raw_find_by_prefix(&[]).await.expect("DB fail");
106 while let Some((k, v)) = records.next().await {
107 if DbKeyPrefix::UserData as u8 <= k[0] {
109 break;
110 }
111
112 assert!(
113 prefixes.contains(&k[0]),
114 "Unexpected client db record found: {}: {}",
115 k.as_hex(),
116 v.as_hex()
117 );
118 }
119}
120
121impl std::fmt::Display for DbKeyPrefix {
122 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123 write!(f, "{self:?}")
124 }
125}
126
127#[derive(Debug, Encodable, Decodable)]
128pub struct EncodedClientSecretKey;
129
130#[derive(Debug, Encodable, Decodable)]
131pub struct EncodedClientSecretKeyPrefix;
132
133impl_db_record!(
134 key = EncodedClientSecretKey,
135 value = Vec<u8>,
136 db_prefix = DbKeyPrefix::EncodedClientSecret,
137);
138impl_db_lookup!(
139 key = EncodedClientSecretKey,
140 query_prefix = EncodedClientSecretKeyPrefix
141);
142
143#[derive(Debug, Encodable, Decodable, Serialize)]
144pub struct OperationLogKey {
145 pub operation_id: OperationId,
146}
147
148impl_db_record!(
149 key = OperationLogKey,
150 value = OperationLogEntry,
151 db_prefix = DbKeyPrefix::OperationLog
152);
153
154#[derive(Debug, Encodable)]
155pub struct OperationLogKeyPrefix;
156
157impl_db_lookup!(key = OperationLogKey, query_prefix = OperationLogKeyPrefix);
158
159#[derive(Debug, Encodable, Decodable, Serialize)]
160pub struct OperationLogKeyV0 {
161 pub operation_id: OperationId,
162}
163
164#[derive(Debug, Encodable)]
165pub struct OperationLogKeyPrefixV0;
166
167impl_db_record!(
168 key = OperationLogKeyV0,
169 value = OperationLogEntryV0,
170 db_prefix = DbKeyPrefix::OperationLog
171);
172
173impl_db_lookup!(
174 key = OperationLogKeyV0,
175 query_prefix = OperationLogKeyPrefixV0
176);
177
178#[derive(Debug, Encodable, Decodable, Serialize)]
179pub struct ClientPreRootSecretHashKey;
180
181impl_db_record!(
182 key = ClientPreRootSecretHashKey,
183 value = [u8; 8],
184 db_prefix = DbKeyPrefix::ClientPreRootSecretHash
185);
186
187#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
189pub struct ChronologicalOperationLogKey {
190 pub creation_time: std::time::SystemTime,
191 pub operation_id: OperationId,
192}
193
194#[derive(Debug, Encodable)]
195pub struct ChronologicalOperationLogKeyPrefix;
196
197impl_db_record!(
198 key = ChronologicalOperationLogKey,
199 value = (),
200 db_prefix = DbKeyPrefix::ChronologicalOperationLog
201);
202
203impl_db_lookup!(
204 key = ChronologicalOperationLogKey,
205 query_prefix = ChronologicalOperationLogKeyPrefix
206);
207
208#[derive(Debug, Encodable, Decodable)]
209pub struct CachedApiVersionSetKey;
210
211#[derive(Debug, Encodable, Decodable)]
212pub struct CachedApiVersionSet(pub ApiVersionSet);
213
214impl_db_record!(
215 key = CachedApiVersionSetKey,
216 value = CachedApiVersionSet,
217 db_prefix = DbKeyPrefix::CommonApiVersionCache
218);
219
220#[derive(Debug, Encodable, Decodable)]
221pub struct PeerLastApiVersionsSummaryKey(pub PeerId);
222
223#[derive(Debug, Encodable, Decodable)]
224pub struct PeerLastApiVersionsSummary(pub SupportedApiVersionsSummary);
225
226impl_db_record!(
227 key = PeerLastApiVersionsSummaryKey,
228 value = PeerLastApiVersionsSummary,
229 db_prefix = DbKeyPrefix::PeerLastApiVersionsSummaryCache
230);
231
232#[derive(Debug, Encodable, Decodable, Serialize)]
233pub struct ClientConfigKey;
234
235impl_db_record!(
236 key = ClientConfigKey,
237 value = ClientConfig,
238 db_prefix = DbKeyPrefix::ClientConfig
239);
240
241#[derive(Debug, Encodable, Decodable, Serialize)]
242pub struct PendingClientConfigKey;
243
244impl_db_record!(
245 key = PendingClientConfigKey,
246 value = ClientConfig,
247 db_prefix = DbKeyPrefix::PendingClientConfig
248);
249
250#[derive(Debug, Encodable, Decodable, Serialize)]
251pub struct ClientConfigKeyV0 {
252 pub id: FederationId,
253}
254
255#[derive(Debug, Encodable)]
256pub struct ClientConfigKeyPrefixV0;
257
258impl_db_record!(
259 key = ClientConfigKeyV0,
260 value = ClientConfigV0,
261 db_prefix = DbKeyPrefix::ClientConfig
262);
263
264impl_db_lookup!(
265 key = ClientConfigKeyV0,
266 query_prefix = ClientConfigKeyPrefixV0
267);
268
269#[derive(Debug, Encodable, Decodable, Serialize)]
270pub struct ApiSecretKey;
271
272#[derive(Debug, Encodable)]
273pub struct ApiSecretKeyPrefix;
274
275impl_db_record!(
276 key = ApiSecretKey,
277 value = String,
278 db_prefix = DbKeyPrefix::ApiSecret
279);
280
281impl_db_lookup!(key = ApiSecretKey, query_prefix = ApiSecretKeyPrefix);
282
283#[derive(Debug, Encodable, Decodable, Serialize)]
285pub struct ChainIdKey;
286
287impl_db_record!(
288 key = ChainIdKey,
289 value = ChainId,
290 db_prefix = DbKeyPrefix::ChainId
291);
292
293#[derive(Debug, Encodable, Decodable)]
295pub struct TransactionFeesKey(pub TransactionId);
296
297impl_db_record!(
298 key = TransactionFeesKey,
299 value = Amounts,
300 db_prefix = DbKeyPrefix::TransactionFees,
301);
302
303#[derive(Debug, Encodable, Decodable, Serialize)]
305pub struct ClientMetadataKey;
306
307#[derive(Debug, Encodable)]
308pub struct ClientMetadataPrefix;
309
310impl_db_record!(
311 key = ClientMetadataKey,
312 value = Metadata,
313 db_prefix = DbKeyPrefix::ClientMetadata
314);
315
316impl_db_lookup!(key = ClientMetadataKey, query_prefix = ClientMetadataPrefix);
317
318#[derive(Debug, Encodable, Decodable, Serialize)]
320pub struct ClientInitStateKey;
321
322#[derive(Debug, Encodable)]
323pub struct ClientInitStatePrefix;
324
325#[derive(Debug, Encodable, Decodable)]
327pub enum InitMode {
328 Fresh,
330 Recover { snapshot: Option<ClientBackup> },
333}
334
335#[derive(Debug, Encodable, Decodable)]
341pub enum InitModeComplete {
342 Fresh,
343 Recover,
344}
345
346#[derive(Debug, Encodable, Decodable)]
348pub enum InitState {
349 Pending(InitMode),
352 Complete(InitModeComplete),
354}
355
356impl InitState {
357 pub fn into_complete(self) -> Self {
358 match self {
359 InitState::Pending(p) => InitState::Complete(match p {
360 InitMode::Fresh => InitModeComplete::Fresh,
361 InitMode::Recover { .. } => InitModeComplete::Recover,
362 }),
363 InitState::Complete(t) => InitState::Complete(t),
364 }
365 }
366
367 pub fn does_require_recovery(&self) -> Option<Option<ClientBackup>> {
368 match self {
369 InitState::Pending(p) => match p {
370 InitMode::Fresh => None,
371 InitMode::Recover { snapshot } => Some(snapshot.clone()),
372 },
373 InitState::Complete(_) => None,
374 }
375 }
376
377 pub fn is_pending(&self) -> bool {
378 match self {
379 InitState::Pending(_) => true,
380 InitState::Complete(_) => false,
381 }
382 }
383}
384
385impl_db_record!(
386 key = ClientInitStateKey,
387 value = InitState,
388 db_prefix = DbKeyPrefix::ClientInitState
389);
390
391impl_db_lookup!(
392 key = ClientInitStateKey,
393 query_prefix = ClientInitStatePrefix
394);
395
396#[derive(Debug, Encodable, Decodable, Serialize)]
397pub struct ClientModuleRecovery {
398 pub module_instance_id: ModuleInstanceId,
399}
400
401#[derive(Debug, Clone, Encodable, Decodable)]
402pub struct ClientModuleRecoveryState {
403 pub progress: RecoveryProgress,
404}
405
406impl ClientModuleRecoveryState {
407 pub fn is_done(&self) -> bool {
408 self.progress.is_done()
409 }
410}
411
412impl_db_record!(
413 key = ClientModuleRecovery,
414 value = ClientModuleRecoveryState,
415 db_prefix = DbKeyPrefix::ClientModuleRecovery,
416);
417
418#[derive(Debug, Encodable, Decodable, Serialize)]
425pub struct ClientModuleRecoveryIncorrectDoNotUse {
426 pub module_instance_id: ModuleInstanceId,
427}
428
429impl_db_record!(
430 key = ClientModuleRecoveryIncorrectDoNotUse,
431 value = ClientModuleRecoveryState,
432 db_prefix = DbKeyPrefix::ClientInitState,
434);
435
436#[derive(Debug, Encodable, Decodable)]
441pub struct LastBackupKey;
442
443impl_db_record!(
444 key = LastBackupKey,
445 value = ClientBackup,
446 db_prefix = DbKeyPrefix::ClientLastBackup
447);
448
449#[derive(Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
450pub(crate) struct MetaFieldPrefix;
451
452#[derive(Encodable, Decodable, Debug)]
453pub struct MetaServiceInfoKey;
454
455#[derive(Encodable, Decodable, Debug)]
456pub struct MetaServiceInfo {
457 pub last_updated: SystemTime,
458 pub revision: u64,
459}
460
461#[derive(
462 Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
463)]
464pub(crate) struct MetaFieldKey(pub fedimint_client_module::meta::MetaFieldKey);
465
466#[derive(Encodable, Decodable, Debug, Clone, Serialize, Deserialize)]
467pub(crate) struct MetaFieldValue(pub fedimint_client_module::meta::MetaFieldValue);
468
469impl_db_record!(
470 key = MetaFieldKey,
471 value = MetaFieldValue,
472 db_prefix = DbKeyPrefix::ClientMetaField
473);
474
475impl_db_record!(
476 key = MetaServiceInfoKey,
477 value = MetaServiceInfo,
478 db_prefix = DbKeyPrefix::ClientMetaServiceInfo
479);
480
481impl_db_lookup!(key = MetaFieldKey, query_prefix = MetaFieldPrefix);
482
483pub fn get_core_client_database_migrations()
484-> BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> {
485 let mut migrations: BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> =
486 BTreeMap::new();
487 migrations.insert(
488 DatabaseVersion(0),
489 Box::new(|mut ctx| {
490 Box::pin(async move {
491 let mut dbtx = ctx.dbtx();
492
493 let config_v0 = dbtx
494 .find_by_prefix(&ClientConfigKeyPrefixV0)
495 .await
496 .collect::<Vec<_>>()
497 .await;
498
499 assert!(config_v0.len() <= 1);
500 let Some((id, config_v0)) = config_v0.into_iter().next() else {
501 return Ok(());
502 };
503
504 let global = GlobalClientConfig {
505 api_endpoints: config_v0.global.api_endpoints,
506 broadcast_public_keys: None,
507 consensus_version: config_v0.global.consensus_version,
508 meta: config_v0.global.meta,
509 };
510
511 let config = ClientConfig {
512 global,
513 modules: config_v0.modules,
514 };
515
516 dbtx.remove_entry(&id).await;
517 dbtx.insert_new_entry(&ClientConfigKey, &config).await;
518 Ok(())
519 })
520 }),
521 );
522
523 migrations.insert(
525 DatabaseVersion(1),
526 Box::new(|mut ctx| {
527 Box::pin(async move {
528 let mut dbtx = ctx.dbtx();
529
530 let operation_logs = dbtx
532 .find_by_prefix(&OperationLogKeyPrefixV0)
533 .await
534 .collect::<Vec<_>>()
535 .await;
536
537 let mut op_id_max_time = BTreeMap::new();
539
540 {
542 let mut inactive_states_stream =
543 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes).await;
544
545 while let Some((state, meta)) = inactive_states_stream.next().await {
546 let entry = op_id_max_time
547 .entry(state.operation_id)
548 .or_insert(meta.exited_at);
549 *entry = (*entry).max(meta.exited_at);
550 }
551 }
552 for (op_key_v0, log_entry_v0) in operation_logs {
554 let new_entry = OperationLogEntry::new(
555 log_entry_v0.operation_module_kind,
556 log_entry_v0.meta,
557 log_entry_v0.outcome.map(|outcome| {
558 OperationOutcome {
559 outcome,
560 time: op_id_max_time
563 .get(&op_key_v0.operation_id)
564 .copied()
565 .unwrap_or_else(fedimint_core::time::now),
566 }
567 }),
568 );
569
570 dbtx.remove_entry(&op_key_v0).await;
571 dbtx.insert_entry(
572 &OperationLogKey {
573 operation_id: op_key_v0.operation_id,
574 },
575 &new_entry,
576 )
577 .await;
578 }
579
580 Ok(())
581 })
582 }),
583 );
584
585 migrations.insert(
587 DatabaseVersion(2),
588 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
589 Box::pin(async move {
590 let mut dbtx = ctx.dbtx();
591
592 {
594 let mut ordered_log_entries = dbtx
595 .raw_find_by_prefix(&[DB_KEY_PREFIX_EVENT_LOG])
596 .await
597 .expect("DB operation failed");
598 let mut keys_to_migrate = vec![];
599 while let Some((k, _v)) = ordered_log_entries.next().await {
600 trace!(target: LOG_CLIENT_DB,
601 k=%k.as_hex(),
602 "Checking ordered log key"
603 );
604 if EventLogId::consensus_decode_whole(&k[1..], &Default::default()).is_err()
605 {
606 assert!(
607 UnordedEventLogId::consensus_decode_whole(
608 &k[1..],
609 &Default::default()
610 )
611 .is_ok()
612 );
613 keys_to_migrate.push(k);
614 }
615 }
616 drop(ordered_log_entries);
617 for mut key_to_migrate in keys_to_migrate {
618 warn!(target: LOG_CLIENT_DB,
619 k=%key_to_migrate.as_hex(),
620 "Migrating unordered event log entry written to an ordered log"
621 );
622 let v = dbtx
623 .raw_remove_entry(&key_to_migrate)
624 .await
625 .expect("DB operation failed")
626 .expect("Was there a moment ago");
627 assert_eq!(key_to_migrate[0], 0x39);
628 key_to_migrate[0] = DB_KEY_PREFIX_UNORDERED_EVENT_LOG;
629 assert_eq!(key_to_migrate[0], 0x3a);
630 dbtx.raw_insert_bytes(&key_to_migrate, &v)
631 .await
632 .expect("DB operation failed");
633 }
634 }
635
636 {
638 let mut unordered_log_entries = dbtx
639 .raw_find_by_prefix(&[DB_KEY_PREFIX_UNORDERED_EVENT_LOG])
640 .await
641 .expect("DB operation failed");
642 let mut keys_to_migrate = vec![];
643 while let Some((k, _v)) = unordered_log_entries.next().await {
644 trace!(target: LOG_CLIENT_DB,
645 k=%k.as_hex(),
646 "Checking unordered log key"
647 );
648 if UnordedEventLogId::consensus_decode_whole(&k[1..], &Default::default())
649 .is_err()
650 {
651 assert!(
652 EventLogId::consensus_decode_whole(&k[1..], &Default::default())
653 .is_ok()
654 );
655 keys_to_migrate.push(k);
656 }
657 }
658 drop(unordered_log_entries);
659 for mut key_to_migrate in keys_to_migrate {
660 warn!(target: LOG_CLIENT_DB,
661 k=%key_to_migrate.as_hex(),
662 "Migrating ordered event log entry written to an unordered log"
663 );
664 let v = dbtx
665 .raw_remove_entry(&key_to_migrate)
666 .await
667 .expect("DB operation failed")
668 .expect("Was there a moment ago");
669 assert_eq!(key_to_migrate[0], 0x3a);
670 key_to_migrate[0] = DB_KEY_PREFIX_EVENT_LOG;
671 assert_eq!(key_to_migrate[0], 0x39);
672 dbtx.raw_insert_bytes(&key_to_migrate, &v)
673 .await
674 .expect("DB operation failed");
675 }
676 }
677 Ok(())
678 })
679 }),
680 );
681
682 migrations.insert(
684 DatabaseVersion(3),
685 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
686 Box::pin(async move {
687 let mut dbtx = ctx.dbtx();
688
689 for module_id in 0..u16::MAX {
690 let old_key = ClientModuleRecoveryIncorrectDoNotUse {
691 module_instance_id: module_id,
692 };
693 let new_key = ClientModuleRecovery {
694 module_instance_id: module_id,
695 };
696 let Some(value) = dbtx.get_value(&old_key).await else {
697 debug!(target: LOG_CLIENT_DB, %module_id, "No more ClientModuleRecovery keys found for migartion");
698 break;
699 };
700
701 debug!(target: LOG_CLIENT_DB, %module_id, "Migrating old ClientModuleRecovery key");
702 dbtx.remove_entry(&old_key).await.expect("Is there.");
703 assert!(dbtx.insert_entry(&new_key, &value).await.is_none());
704 }
705
706 Ok(())
707 })
708 }),
709 );
710 migrations
711}
712
713pub async fn apply_migrations_core_client_dbtx(
717 dbtx: &mut DatabaseTransaction<'_>,
718 kind: String,
719) -> Result<(), anyhow::Error> {
720 apply_migrations_dbtx(
721 dbtx,
722 (),
723 kind,
724 get_core_client_database_migrations(),
725 None,
726 Some(DbKeyPrefix::UserData as u8),
727 )
728 .await
729}
730
731pub async fn apply_migrations_client_module(
741 db: &Database,
742 kind: String,
743 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
744 module_instance_id: ModuleInstanceId,
745) -> Result<(), anyhow::Error> {
746 let mut dbtx = db.begin_transaction().await;
747 apply_migrations_client_module_dbtx(
748 &mut dbtx.to_ref_nc(),
749 kind,
750 migrations,
751 module_instance_id,
752 )
753 .await?;
754 dbtx.commit_tx_result()
755 .await
756 .map_err(|e| anyhow::Error::msg(e.to_string()))
757}
758
759pub async fn apply_migrations_client_module_dbtx(
760 dbtx: &mut DatabaseTransaction<'_>,
761 kind: String,
762 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
763 module_instance_id: ModuleInstanceId,
764) -> Result<(), anyhow::Error> {
765 let is_new_db = dbtx
768 .raw_find_by_prefix(&[MODULE_GLOBAL_PREFIX])
769 .await?
770 .next()
771 .await
772 .is_none();
773
774 let target_version = get_current_database_version(&migrations);
775
776 create_database_version_dbtx(
778 dbtx,
779 target_version,
780 Some(module_instance_id),
781 kind.clone(),
782 is_new_db,
783 )
784 .await?;
785
786 let current_version = dbtx
787 .get_value(&DatabaseVersionKey(module_instance_id))
788 .await;
789
790 let db_version = if let Some(mut current_version) = current_version {
791 if current_version == target_version {
792 trace!(
793 target: LOG_CLIENT_DB,
794 %current_version,
795 %target_version,
796 module_instance_id,
797 kind,
798 "Database version up to date"
799 );
800 return Ok(());
801 }
802
803 if target_version < current_version {
804 return Err(anyhow!(format!(
805 "On disk database version for module {kind} was higher ({}) than the target database version ({}).",
806 current_version, target_version,
807 )));
808 }
809
810 info!(
811 target: LOG_CLIENT_DB,
812 %current_version,
813 %target_version,
814 module_instance_id,
815 kind,
816 "Migrating client module database"
817 );
818 let mut active_states = get_active_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
819 let mut inactive_states =
820 get_inactive_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
821
822 while current_version < target_version {
823 let new_states = if let Some(migration) = migrations.get(¤t_version) {
824 debug!(
825 target: LOG_CLIENT_DB,
826 module_instance_id,
827 %kind,
828 %current_version,
829 %target_version,
830 "Running module db migration");
831
832 migration(
833 &mut dbtx
834 .to_ref_with_prefix_module_id(module_instance_id)
835 .0
836 .into_nc(),
837 active_states.clone(),
838 inactive_states.clone(),
839 )
840 .await?
841 } else {
842 warn!(
843 target: LOG_CLIENT_DB,
844 ?current_version, "Missing client db migration");
845 None
846 };
847
848 if let Some((new_active_states, new_inactive_states)) = new_states {
851 remove_old_and_persist_new_active_states(
852 &mut dbtx.to_ref_nc(),
853 new_active_states.clone(),
854 active_states.clone(),
855 module_instance_id,
856 )
857 .await;
858 remove_old_and_persist_new_inactive_states(
859 &mut dbtx.to_ref_nc(),
860 new_inactive_states.clone(),
861 inactive_states.clone(),
862 module_instance_id,
863 )
864 .await;
865
866 active_states = new_active_states;
868 inactive_states = new_inactive_states;
869 }
870
871 current_version = current_version.increment();
872 dbtx.insert_entry(&DatabaseVersionKey(module_instance_id), ¤t_version)
873 .await;
874 }
875
876 current_version
877 } else {
878 target_version
879 };
880
881 debug!(
882 target: LOG_CLIENT_DB,
883 ?kind, ?db_version, "Client DB Version");
884 Ok(())
885}
886
887pub async fn get_active_states(
893 dbtx: &mut DatabaseTransaction<'_>,
894 module_instance_id: ModuleInstanceId,
895) -> Vec<(Vec<u8>, OperationId)> {
896 dbtx.find_by_prefix(&ActiveStateKeyPrefixBytes)
897 .await
898 .filter_map(|(state, _)| async move {
899 if module_instance_id == state.module_instance_id {
900 Some((state.state, state.operation_id))
901 } else {
902 None
903 }
904 })
905 .collect::<Vec<_>>()
906 .await
907}
908
909pub async fn get_inactive_states(
915 dbtx: &mut DatabaseTransaction<'_>,
916 module_instance_id: ModuleInstanceId,
917) -> Vec<(Vec<u8>, OperationId)> {
918 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes)
919 .await
920 .filter_map(|(state, _)| async move {
921 if module_instance_id == state.module_instance_id {
922 Some((state.state, state.operation_id))
923 } else {
924 None
925 }
926 })
927 .collect::<Vec<_>>()
928 .await
929}
930
931pub async fn remove_old_and_persist_new_active_states(
935 dbtx: &mut DatabaseTransaction<'_>,
936 new_active_states: Vec<(Vec<u8>, OperationId)>,
937 states_to_remove: Vec<(Vec<u8>, OperationId)>,
938 module_instance_id: ModuleInstanceId,
939) {
940 for (bytes, operation_id) in states_to_remove {
942 dbtx.remove_entry(&ActiveStateKeyBytes {
943 operation_id,
944 module_instance_id,
945 state: bytes,
946 })
947 .await
948 .expect("Did not delete anything");
949 }
950
951 for (bytes, operation_id) in new_active_states {
953 dbtx.insert_new_entry(
954 &ActiveStateKeyBytes {
955 operation_id,
956 module_instance_id,
957 state: bytes,
958 },
959 &ActiveStateMeta::default(),
960 )
961 .await;
962 }
963}
964
965pub async fn remove_old_and_persist_new_inactive_states(
969 dbtx: &mut DatabaseTransaction<'_>,
970 new_inactive_states: Vec<(Vec<u8>, OperationId)>,
971 states_to_remove: Vec<(Vec<u8>, OperationId)>,
972 module_instance_id: ModuleInstanceId,
973) {
974 for (bytes, operation_id) in states_to_remove {
976 dbtx.remove_entry(&InactiveStateKeyBytes {
977 operation_id,
978 module_instance_id,
979 state: bytes,
980 })
981 .await
982 .expect("Did not delete anything");
983 }
984
985 for (bytes, operation_id) in new_inactive_states {
987 dbtx.insert_new_entry(
988 &InactiveStateKeyBytes {
989 operation_id,
990 module_instance_id,
991 state: bytes,
992 },
993 &InactiveStateMeta {
994 created_at: fedimint_core::time::now(),
995 exited_at: fedimint_core::time::now(),
996 },
997 )
998 .await;
999 }
1000}
1001
1002pub async fn get_decoded_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
1006 let mut tx = db.begin_transaction_nc().await;
1007 let client_secret = tx.get_value(&EncodedClientSecretKey).await;
1008
1009 match client_secret {
1010 Some(client_secret) => {
1011 T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
1012 .map_err(|e| anyhow!("Decoding failed: {e}"))
1013 }
1014 None => bail!("Encoded client secret not present in DB"),
1015 }
1016}
1017
1018#[derive(Debug, Serialize, Deserialize, Encodable, Decodable)]
1020pub struct OperationLogEntryV0 {
1021 pub(crate) operation_module_kind: String,
1022 pub(crate) meta: JsonStringed,
1023 pub(crate) outcome: Option<JsonStringed>,
1024}