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