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)]
189#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
190pub struct ChronologicalOperationLogKey {
191 pub creation_time: std::time::SystemTime,
192 pub operation_id: OperationId,
193}
194
195#[derive(Debug, Encodable)]
196pub struct ChronologicalOperationLogKeyPrefix;
197
198impl_db_record!(
199 key = ChronologicalOperationLogKey,
200 value = (),
201 db_prefix = DbKeyPrefix::ChronologicalOperationLog
202);
203
204impl_db_lookup!(
205 key = ChronologicalOperationLogKey,
206 query_prefix = ChronologicalOperationLogKeyPrefix
207);
208
209#[derive(Debug, Encodable, Decodable)]
210pub struct CachedApiVersionSetKey;
211
212#[derive(Debug, Encodable, Decodable)]
213pub struct CachedApiVersionSet(pub ApiVersionSet);
214
215impl_db_record!(
216 key = CachedApiVersionSetKey,
217 value = CachedApiVersionSet,
218 db_prefix = DbKeyPrefix::CommonApiVersionCache
219);
220
221#[derive(Debug, Encodable, Decodable)]
222pub struct PeerLastApiVersionsSummaryKey(pub PeerId);
223
224#[derive(Debug, Encodable, Decodable)]
225pub struct PeerLastApiVersionsSummary(pub SupportedApiVersionsSummary);
226
227impl_db_record!(
228 key = PeerLastApiVersionsSummaryKey,
229 value = PeerLastApiVersionsSummary,
230 db_prefix = DbKeyPrefix::PeerLastApiVersionsSummaryCache
231);
232
233#[derive(Debug, Encodable, Decodable, Serialize)]
234pub struct ClientConfigKey;
235
236impl_db_record!(
237 key = ClientConfigKey,
238 value = ClientConfig,
239 db_prefix = DbKeyPrefix::ClientConfig
240);
241
242#[derive(Debug, Encodable, Decodable, Serialize)]
243pub struct PendingClientConfigKey;
244
245impl_db_record!(
246 key = PendingClientConfigKey,
247 value = ClientConfig,
248 db_prefix = DbKeyPrefix::PendingClientConfig
249);
250
251#[derive(Debug, Encodable, Decodable, Serialize)]
252pub struct ClientConfigKeyV0 {
253 pub id: FederationId,
254}
255
256#[derive(Debug, Encodable)]
257pub struct ClientConfigKeyPrefixV0;
258
259impl_db_record!(
260 key = ClientConfigKeyV0,
261 value = ClientConfigV0,
262 db_prefix = DbKeyPrefix::ClientConfig
263);
264
265impl_db_lookup!(
266 key = ClientConfigKeyV0,
267 query_prefix = ClientConfigKeyPrefixV0
268);
269
270#[derive(Debug, Encodable, Decodable, Serialize)]
271pub struct ApiSecretKey;
272
273#[derive(Debug, Encodable)]
274pub struct ApiSecretKeyPrefix;
275
276impl_db_record!(
277 key = ApiSecretKey,
278 value = String,
279 db_prefix = DbKeyPrefix::ApiSecret
280);
281
282impl_db_lookup!(key = ApiSecretKey, query_prefix = ApiSecretKeyPrefix);
283
284#[derive(Debug, Encodable, Decodable, Serialize)]
286pub struct ChainIdKey;
287
288impl_db_record!(
289 key = ChainIdKey,
290 value = ChainId,
291 db_prefix = DbKeyPrefix::ChainId
292);
293
294#[derive(Debug, Encodable, Decodable)]
296pub struct TransactionFeesKey(pub TransactionId);
297
298impl_db_record!(
299 key = TransactionFeesKey,
300 value = Amounts,
301 db_prefix = DbKeyPrefix::TransactionFees,
302);
303
304#[derive(Debug, Encodable, Decodable, Serialize)]
306pub struct ClientMetadataKey;
307
308#[derive(Debug, Encodable)]
309pub struct ClientMetadataPrefix;
310
311impl_db_record!(
312 key = ClientMetadataKey,
313 value = Metadata,
314 db_prefix = DbKeyPrefix::ClientMetadata
315);
316
317impl_db_lookup!(key = ClientMetadataKey, query_prefix = ClientMetadataPrefix);
318
319#[derive(Debug, Encodable, Decodable, Serialize)]
321pub struct ClientInitStateKey;
322
323#[derive(Debug, Encodable)]
324pub struct ClientInitStatePrefix;
325
326#[derive(Debug, Encodable, Decodable)]
328pub enum InitMode {
329 Fresh,
331 Recover { snapshot: Option<ClientBackup> },
334}
335
336#[derive(Debug, Encodable, Decodable)]
342pub enum InitModeComplete {
343 Fresh,
344 Recover,
345}
346
347#[derive(Debug, Encodable, Decodable)]
349pub enum InitState {
350 Pending(InitMode),
353 Complete(InitModeComplete),
355}
356
357impl InitState {
358 pub fn into_complete(self) -> Self {
359 match self {
360 InitState::Pending(p) => InitState::Complete(match p {
361 InitMode::Fresh => InitModeComplete::Fresh,
362 InitMode::Recover { .. } => InitModeComplete::Recover,
363 }),
364 InitState::Complete(t) => InitState::Complete(t),
365 }
366 }
367
368 pub fn does_require_recovery(&self) -> Option<Option<ClientBackup>> {
369 match self {
370 InitState::Pending(p) => match p {
371 InitMode::Fresh => None,
372 InitMode::Recover { snapshot } => Some(snapshot.clone()),
373 },
374 InitState::Complete(_) => None,
375 }
376 }
377
378 pub fn is_pending(&self) -> bool {
379 match self {
380 InitState::Pending(_) => true,
381 InitState::Complete(_) => false,
382 }
383 }
384}
385
386impl_db_record!(
387 key = ClientInitStateKey,
388 value = InitState,
389 db_prefix = DbKeyPrefix::ClientInitState
390);
391
392impl_db_lookup!(
393 key = ClientInitStateKey,
394 query_prefix = ClientInitStatePrefix
395);
396
397#[derive(Debug, Encodable, Decodable, Serialize)]
398pub struct ClientModuleRecovery {
399 pub module_instance_id: ModuleInstanceId,
400}
401
402#[derive(Debug, Clone, Encodable, Decodable)]
403pub struct ClientModuleRecoveryState {
404 pub progress: RecoveryProgress,
405}
406
407impl ClientModuleRecoveryState {
408 pub fn is_done(&self) -> bool {
409 self.progress.is_done()
410 }
411}
412
413impl_db_record!(
414 key = ClientModuleRecovery,
415 value = ClientModuleRecoveryState,
416 db_prefix = DbKeyPrefix::ClientModuleRecovery,
417);
418
419#[derive(Debug, Encodable, Decodable, Serialize)]
426pub struct ClientModuleRecoveryIncorrectDoNotUse {
427 pub module_instance_id: ModuleInstanceId,
428}
429
430impl_db_record!(
431 key = ClientModuleRecoveryIncorrectDoNotUse,
432 value = ClientModuleRecoveryState,
433 db_prefix = DbKeyPrefix::ClientInitState,
435);
436
437#[derive(Debug, Encodable, Decodable)]
442pub struct LastBackupKey;
443
444impl_db_record!(
445 key = LastBackupKey,
446 value = ClientBackup,
447 db_prefix = DbKeyPrefix::ClientLastBackup
448);
449
450#[derive(Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
451pub(crate) struct MetaFieldPrefix;
452
453#[derive(Encodable, Decodable, Debug)]
454pub struct MetaServiceInfoKey;
455
456#[derive(Encodable, Decodable, Debug)]
457pub struct MetaServiceInfo {
458 pub last_updated: SystemTime,
459 pub revision: u64,
460}
461
462#[derive(
463 Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
464)]
465pub(crate) struct MetaFieldKey(pub fedimint_client_module::meta::MetaFieldKey);
466
467#[derive(Encodable, Decodable, Debug, Clone, Serialize, Deserialize)]
468pub(crate) struct MetaFieldValue(pub fedimint_client_module::meta::MetaFieldValue);
469
470impl_db_record!(
471 key = MetaFieldKey,
472 value = MetaFieldValue,
473 db_prefix = DbKeyPrefix::ClientMetaField
474);
475
476impl_db_record!(
477 key = MetaServiceInfoKey,
478 value = MetaServiceInfo,
479 db_prefix = DbKeyPrefix::ClientMetaServiceInfo
480);
481
482impl_db_lookup!(key = MetaFieldKey, query_prefix = MetaFieldPrefix);
483
484pub fn get_core_client_database_migrations()
485-> BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> {
486 let mut migrations: BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> =
487 BTreeMap::new();
488 migrations.insert(
489 DatabaseVersion(0),
490 Box::new(|mut ctx| {
491 Box::pin(async move {
492 let mut dbtx = ctx.dbtx();
493
494 let config_v0 = dbtx
495 .find_by_prefix(&ClientConfigKeyPrefixV0)
496 .await
497 .collect::<Vec<_>>()
498 .await;
499
500 assert!(config_v0.len() <= 1);
501 let Some((id, config_v0)) = config_v0.into_iter().next() else {
502 return Ok(());
503 };
504
505 let global = GlobalClientConfig {
506 api_endpoints: config_v0.global.api_endpoints,
507 broadcast_public_keys: None,
508 consensus_version: config_v0.global.consensus_version,
509 meta: config_v0.global.meta,
510 };
511
512 let config = ClientConfig {
513 global,
514 modules: config_v0.modules,
515 };
516
517 dbtx.remove_entry(&id).await;
518 dbtx.insert_new_entry(&ClientConfigKey, &config).await;
519 Ok(())
520 })
521 }),
522 );
523
524 migrations.insert(
526 DatabaseVersion(1),
527 Box::new(|mut ctx| {
528 Box::pin(async move {
529 let mut dbtx = ctx.dbtx();
530
531 let operation_logs = dbtx
533 .find_by_prefix(&OperationLogKeyPrefixV0)
534 .await
535 .collect::<Vec<_>>()
536 .await;
537
538 let mut op_id_max_time = BTreeMap::new();
540
541 {
543 let mut inactive_states_stream =
544 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes).await;
545
546 while let Some((state, meta)) = inactive_states_stream.next().await {
547 let entry = op_id_max_time
548 .entry(state.operation_id)
549 .or_insert(meta.exited_at);
550 *entry = (*entry).max(meta.exited_at);
551 }
552 }
553 for (op_key_v0, log_entry_v0) in operation_logs {
555 let new_entry = OperationLogEntry::new(
556 log_entry_v0.operation_module_kind,
557 log_entry_v0.meta,
558 log_entry_v0.outcome.map(|outcome| {
559 OperationOutcome {
560 outcome,
561 time: op_id_max_time
564 .get(&op_key_v0.operation_id)
565 .copied()
566 .unwrap_or_else(fedimint_core::time::now),
567 }
568 }),
569 );
570
571 dbtx.remove_entry(&op_key_v0).await;
572 dbtx.insert_entry(
573 &OperationLogKey {
574 operation_id: op_key_v0.operation_id,
575 },
576 &new_entry,
577 )
578 .await;
579 }
580
581 Ok(())
582 })
583 }),
584 );
585
586 migrations.insert(
588 DatabaseVersion(2),
589 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
590 Box::pin(async move {
591 let mut dbtx = ctx.dbtx();
592
593 {
595 let mut ordered_log_entries = dbtx
596 .raw_find_by_prefix(&[DB_KEY_PREFIX_EVENT_LOG])
597 .await
598 .expect("DB operation failed");
599 let mut keys_to_migrate = vec![];
600 while let Some((k, _v)) = ordered_log_entries.next().await {
601 trace!(target: LOG_CLIENT_DB,
602 k=%k.as_hex(),
603 "Checking ordered log key"
604 );
605 if EventLogId::consensus_decode_whole(&k[1..], &Default::default()).is_err()
606 {
607 assert!(
608 UnordedEventLogId::consensus_decode_whole(
609 &k[1..],
610 &Default::default()
611 )
612 .is_ok()
613 );
614 keys_to_migrate.push(k);
615 }
616 }
617 drop(ordered_log_entries);
618 for mut key_to_migrate in keys_to_migrate {
619 warn!(target: LOG_CLIENT_DB,
620 k=%key_to_migrate.as_hex(),
621 "Migrating unordered event log entry written to an ordered log"
622 );
623 let v = dbtx
624 .raw_remove_entry(&key_to_migrate)
625 .await
626 .expect("DB operation failed")
627 .expect("Was there a moment ago");
628 assert_eq!(key_to_migrate[0], 0x39);
629 key_to_migrate[0] = DB_KEY_PREFIX_UNORDERED_EVENT_LOG;
630 assert_eq!(key_to_migrate[0], 0x3a);
631 dbtx.raw_insert_bytes(&key_to_migrate, &v)
632 .await
633 .expect("DB operation failed");
634 }
635 }
636
637 {
639 let mut unordered_log_entries = dbtx
640 .raw_find_by_prefix(&[DB_KEY_PREFIX_UNORDERED_EVENT_LOG])
641 .await
642 .expect("DB operation failed");
643 let mut keys_to_migrate = vec![];
644 while let Some((k, _v)) = unordered_log_entries.next().await {
645 trace!(target: LOG_CLIENT_DB,
646 k=%k.as_hex(),
647 "Checking unordered log key"
648 );
649 if UnordedEventLogId::consensus_decode_whole(&k[1..], &Default::default())
650 .is_err()
651 {
652 assert!(
653 EventLogId::consensus_decode_whole(&k[1..], &Default::default())
654 .is_ok()
655 );
656 keys_to_migrate.push(k);
657 }
658 }
659 drop(unordered_log_entries);
660 for mut key_to_migrate in keys_to_migrate {
661 warn!(target: LOG_CLIENT_DB,
662 k=%key_to_migrate.as_hex(),
663 "Migrating ordered event log entry written to an unordered log"
664 );
665 let v = dbtx
666 .raw_remove_entry(&key_to_migrate)
667 .await
668 .expect("DB operation failed")
669 .expect("Was there a moment ago");
670 assert_eq!(key_to_migrate[0], 0x3a);
671 key_to_migrate[0] = DB_KEY_PREFIX_EVENT_LOG;
672 assert_eq!(key_to_migrate[0], 0x39);
673 dbtx.raw_insert_bytes(&key_to_migrate, &v)
674 .await
675 .expect("DB operation failed");
676 }
677 }
678 Ok(())
679 })
680 }),
681 );
682
683 migrations.insert(
685 DatabaseVersion(3),
686 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
687 Box::pin(async move {
688 let mut dbtx = ctx.dbtx();
689
690 for module_id in 0..u16::MAX {
691 let old_key = ClientModuleRecoveryIncorrectDoNotUse {
692 module_instance_id: module_id,
693 };
694 let new_key = ClientModuleRecovery {
695 module_instance_id: module_id,
696 };
697 let Some(value) = dbtx.get_value(&old_key).await else {
698 debug!(target: LOG_CLIENT_DB, %module_id, "No more ClientModuleRecovery keys found for migartion");
699 break;
700 };
701
702 debug!(target: LOG_CLIENT_DB, %module_id, "Migrating old ClientModuleRecovery key");
703 dbtx.remove_entry(&old_key).await.expect("Is there.");
704 assert!(dbtx.insert_entry(&new_key, &value).await.is_none());
705 }
706
707 Ok(())
708 })
709 }),
710 );
711 migrations
712}
713
714pub async fn apply_migrations_core_client_dbtx(
718 dbtx: &mut DatabaseTransaction<'_>,
719 kind: String,
720) -> Result<(), anyhow::Error> {
721 apply_migrations_dbtx(
722 dbtx,
723 (),
724 kind,
725 get_core_client_database_migrations(),
726 None,
727 Some(DbKeyPrefix::UserData as u8),
728 )
729 .await
730}
731
732pub async fn apply_migrations_client_module(
742 db: &Database,
743 kind: String,
744 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
745 module_instance_id: ModuleInstanceId,
746) -> Result<(), anyhow::Error> {
747 let mut dbtx = db.begin_transaction().await;
748 apply_migrations_client_module_dbtx(
749 &mut dbtx.to_ref_nc(),
750 kind,
751 migrations,
752 module_instance_id,
753 )
754 .await?;
755 dbtx.commit_tx_result()
756 .await
757 .map_err(|e| anyhow::Error::msg(e.to_string()))
758}
759
760pub async fn apply_migrations_client_module_dbtx(
761 dbtx: &mut DatabaseTransaction<'_>,
762 kind: String,
763 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
764 module_instance_id: ModuleInstanceId,
765) -> Result<(), anyhow::Error> {
766 let is_new_db = dbtx
769 .raw_find_by_prefix(&[MODULE_GLOBAL_PREFIX])
770 .await?
771 .next()
772 .await
773 .is_none();
774
775 let target_version = get_current_database_version(&migrations);
776
777 create_database_version_dbtx(
779 dbtx,
780 target_version,
781 Some(module_instance_id),
782 kind.clone(),
783 is_new_db,
784 )
785 .await?;
786
787 let current_version = dbtx
788 .get_value(&DatabaseVersionKey(module_instance_id))
789 .await;
790
791 let db_version = if let Some(mut current_version) = current_version {
792 if current_version == target_version {
793 trace!(
794 target: LOG_CLIENT_DB,
795 %current_version,
796 %target_version,
797 module_instance_id,
798 kind,
799 "Database version up to date"
800 );
801 return Ok(());
802 }
803
804 if target_version < current_version {
805 return Err(anyhow!(format!(
806 "On disk database version for module {kind} was higher ({}) than the target database version ({}).",
807 current_version, target_version,
808 )));
809 }
810
811 info!(
812 target: LOG_CLIENT_DB,
813 %current_version,
814 %target_version,
815 module_instance_id,
816 kind,
817 "Migrating client module database"
818 );
819 let mut active_states = get_active_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
820 let mut inactive_states =
821 get_inactive_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
822
823 while current_version < target_version {
824 let new_states = if let Some(migration) = migrations.get(¤t_version) {
825 debug!(
826 target: LOG_CLIENT_DB,
827 module_instance_id,
828 %kind,
829 %current_version,
830 %target_version,
831 "Running module db migration");
832
833 migration(
834 &mut dbtx
835 .to_ref_with_prefix_module_id(module_instance_id)
836 .0
837 .into_nc(),
838 active_states.clone(),
839 inactive_states.clone(),
840 )
841 .await?
842 } else {
843 warn!(
844 target: LOG_CLIENT_DB,
845 ?current_version, "Missing client db migration");
846 None
847 };
848
849 if let Some((new_active_states, new_inactive_states)) = new_states {
852 remove_old_and_persist_new_active_states(
853 &mut dbtx.to_ref_nc(),
854 new_active_states.clone(),
855 active_states.clone(),
856 module_instance_id,
857 )
858 .await;
859 remove_old_and_persist_new_inactive_states(
860 &mut dbtx.to_ref_nc(),
861 new_inactive_states.clone(),
862 inactive_states.clone(),
863 module_instance_id,
864 )
865 .await;
866
867 active_states = new_active_states;
869 inactive_states = new_inactive_states;
870 }
871
872 current_version = current_version.increment();
873 dbtx.insert_entry(&DatabaseVersionKey(module_instance_id), ¤t_version)
874 .await;
875 }
876
877 current_version
878 } else {
879 target_version
880 };
881
882 debug!(
883 target: LOG_CLIENT_DB,
884 ?kind, ?db_version, "Client DB Version");
885 Ok(())
886}
887
888pub async fn get_active_states(
894 dbtx: &mut DatabaseTransaction<'_>,
895 module_instance_id: ModuleInstanceId,
896) -> Vec<(Vec<u8>, OperationId)> {
897 dbtx.find_by_prefix(&ActiveStateKeyPrefixBytes)
898 .await
899 .filter_map(|(state, _)| async move {
900 if module_instance_id == state.module_instance_id {
901 Some((state.state, state.operation_id))
902 } else {
903 None
904 }
905 })
906 .collect::<Vec<_>>()
907 .await
908}
909
910pub async fn get_inactive_states(
916 dbtx: &mut DatabaseTransaction<'_>,
917 module_instance_id: ModuleInstanceId,
918) -> Vec<(Vec<u8>, OperationId)> {
919 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes)
920 .await
921 .filter_map(|(state, _)| async move {
922 if module_instance_id == state.module_instance_id {
923 Some((state.state, state.operation_id))
924 } else {
925 None
926 }
927 })
928 .collect::<Vec<_>>()
929 .await
930}
931
932pub async fn remove_old_and_persist_new_active_states(
936 dbtx: &mut DatabaseTransaction<'_>,
937 new_active_states: Vec<(Vec<u8>, OperationId)>,
938 states_to_remove: Vec<(Vec<u8>, OperationId)>,
939 module_instance_id: ModuleInstanceId,
940) {
941 for (bytes, operation_id) in states_to_remove {
943 dbtx.remove_entry(&ActiveStateKeyBytes {
944 operation_id,
945 module_instance_id,
946 state: bytes,
947 })
948 .await
949 .expect("Did not delete anything");
950 }
951
952 for (bytes, operation_id) in new_active_states {
954 dbtx.insert_new_entry(
955 &ActiveStateKeyBytes {
956 operation_id,
957 module_instance_id,
958 state: bytes,
959 },
960 &ActiveStateMeta::default(),
961 )
962 .await;
963 }
964}
965
966pub async fn remove_old_and_persist_new_inactive_states(
970 dbtx: &mut DatabaseTransaction<'_>,
971 new_inactive_states: Vec<(Vec<u8>, OperationId)>,
972 states_to_remove: Vec<(Vec<u8>, OperationId)>,
973 module_instance_id: ModuleInstanceId,
974) {
975 for (bytes, operation_id) in states_to_remove {
977 dbtx.remove_entry(&InactiveStateKeyBytes {
978 operation_id,
979 module_instance_id,
980 state: bytes,
981 })
982 .await
983 .expect("Did not delete anything");
984 }
985
986 for (bytes, operation_id) in new_inactive_states {
988 dbtx.insert_new_entry(
989 &InactiveStateKeyBytes {
990 operation_id,
991 module_instance_id,
992 state: bytes,
993 },
994 &InactiveStateMeta {
995 created_at: fedimint_core::time::now(),
996 exited_at: fedimint_core::time::now(),
997 },
998 )
999 .await;
1000 }
1001}
1002
1003pub async fn get_decoded_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
1007 let mut tx = db.begin_transaction_nc().await;
1008 let client_secret = tx.get_value(&EncodedClientSecretKey).await;
1009
1010 match client_secret {
1011 Some(client_secret) => {
1012 T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
1013 .map_err(|e| anyhow!("Decoding failed: {e}"))
1014 }
1015 None => bail!("Encoded client secret not present in DB"),
1016 }
1017}
1018
1019#[derive(Debug, Serialize, Deserialize, Encodable, Decodable)]
1021pub struct OperationLogEntryV0 {
1022 pub(crate) operation_module_kind: String,
1023 pub(crate) meta: JsonStringed,
1024 pub(crate) outcome: Option<JsonStringed>,
1025}