1use std::collections::{BTreeMap, BTreeSet};
2use std::time::SystemTime;
3
4use bitcoin::hex::DisplayHex as _;
5use fedimint_api_client::api::ApiVersionSet;
6use fedimint_client_module::db::ClientModuleMigrationFn;
7use fedimint_client_module::module::recovery::RecoveryProgress;
8use fedimint_client_module::oplog::{JsonStringed, OperationLogEntry, OperationOutcome};
9use fedimint_client_module::sm::{ActiveStateMeta, InactiveStateMeta};
10use fedimint_core::config::{ClientConfig, ClientConfigV0, FederationId, GlobalClientConfig};
11use fedimint_core::core::{ModuleInstanceId, OperationId};
12use fedimint_core::db::{
13 Database, DatabaseTransaction, DatabaseVersion, DatabaseVersionKey, DbMigrationError,
14 IDatabaseTransactionOpsCore, IDatabaseTransactionOpsCoreTyped, MODULE_GLOBAL_PREFIX,
15 apply_migrations_dbtx, create_database_version_dbtx, get_current_database_version,
16};
17use fedimint_core::encoding::{
18 Decodable, DecodeError, Encodable, decode_field_from_finite_reader,
19 decode_legacy_system_time_from_finite_reader, encode_legacy_system_time, with_decoding_context,
20};
21use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
22use fedimint_core::module::{Amounts, SupportedApiVersionsSummary};
23use fedimint_core::{ChainId, PeerId, TransactionId, impl_db_lookup, impl_db_record};
24use fedimint_eventlog::{
25 DB_KEY_PREFIX_EVENT_LOG, DB_KEY_PREFIX_UNORDERED_EVENT_LOG, EventLogId, UnordedEventLogId,
26};
27use fedimint_logging::LOG_CLIENT_DB;
28use futures::StreamExt;
29use serde::{Deserialize, Serialize};
30use strum::IntoEnumIterator as _;
31use strum_macros::EnumIter;
32use tracing::{debug, info, trace, warn};
33
34use crate::backup::{ClientBackup, Metadata};
35use crate::error::ClientSecretError;
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)]
192pub struct ChronologicalOperationLogKey {
193 pub creation_time: std::time::SystemTime,
194 pub operation_id: OperationId,
195}
196
197impl Encodable for ChronologicalOperationLogKey {
198 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
199 encode_legacy_system_time(&self.creation_time, writer)?;
200 self.operation_id.consensus_encode(writer)
201 }
202}
203
204impl Decodable for ChronologicalOperationLogKey {
205 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
206 decoder: &mut D,
207 modules: &ModuleDecoderRegistry,
208 ) -> Result<Self, DecodeError> {
209 Ok(Self {
210 creation_time: with_decoding_context(
211 decode_legacy_system_time_from_finite_reader(decoder, modules),
212 "Decoding named block field: ChronologicalOperationLogKey{ ... creation_time ... }",
213 )?,
214 operation_id: decode_field_from_finite_reader(
215 decoder,
216 modules,
217 "Decoding named block field: ChronologicalOperationLogKey{ ... operation_id ... }",
218 )?,
219 })
220 }
221}
222
223#[derive(Debug, Encodable)]
224pub struct ChronologicalOperationLogKeyPrefix;
225
226impl_db_record!(
227 key = ChronologicalOperationLogKey,
228 value = (),
229 db_prefix = DbKeyPrefix::ChronologicalOperationLog
230);
231
232impl_db_lookup!(
233 key = ChronologicalOperationLogKey,
234 query_prefix = ChronologicalOperationLogKeyPrefix
235);
236
237#[derive(Debug, Encodable, Decodable)]
238pub struct CachedApiVersionSetKey;
239
240#[derive(Debug, Encodable, Decodable)]
241pub struct CachedApiVersionSet(pub ApiVersionSet);
242
243impl_db_record!(
244 key = CachedApiVersionSetKey,
245 value = CachedApiVersionSet,
246 db_prefix = DbKeyPrefix::CommonApiVersionCache
247);
248
249#[derive(Debug, Encodable, Decodable)]
250pub struct PeerLastApiVersionsSummaryKey(pub PeerId);
251
252#[derive(Debug, Encodable, Decodable)]
253pub struct PeerLastApiVersionsSummary(pub SupportedApiVersionsSummary);
254
255impl_db_record!(
256 key = PeerLastApiVersionsSummaryKey,
257 value = PeerLastApiVersionsSummary,
258 db_prefix = DbKeyPrefix::PeerLastApiVersionsSummaryCache
259);
260
261#[derive(Debug, Encodable, Decodable, Serialize)]
262pub struct ClientConfigKey;
263
264impl_db_record!(
265 key = ClientConfigKey,
266 value = ClientConfig,
267 db_prefix = DbKeyPrefix::ClientConfig
268);
269
270#[derive(Debug, Encodable, Decodable, Serialize)]
271pub struct PendingClientConfigKey;
272
273impl_db_record!(
274 key = PendingClientConfigKey,
275 value = ClientConfig,
276 db_prefix = DbKeyPrefix::PendingClientConfig
277);
278
279#[derive(Debug, Encodable, Decodable, Serialize)]
280pub struct ClientConfigKeyV0 {
281 pub id: FederationId,
282}
283
284#[derive(Debug, Encodable)]
285pub struct ClientConfigKeyPrefixV0;
286
287impl_db_record!(
288 key = ClientConfigKeyV0,
289 value = ClientConfigV0,
290 db_prefix = DbKeyPrefix::ClientConfig
291);
292
293impl_db_lookup!(
294 key = ClientConfigKeyV0,
295 query_prefix = ClientConfigKeyPrefixV0
296);
297
298#[derive(Debug, Encodable, Decodable, Serialize)]
299pub struct ApiSecretKey;
300
301#[derive(Debug, Encodable)]
302pub struct ApiSecretKeyPrefix;
303
304impl_db_record!(
305 key = ApiSecretKey,
306 value = String,
307 db_prefix = DbKeyPrefix::ApiSecret
308);
309
310impl_db_lookup!(key = ApiSecretKey, query_prefix = ApiSecretKeyPrefix);
311
312#[derive(Debug, Encodable, Decodable, Serialize)]
314pub struct ChainIdKey;
315
316impl_db_record!(
317 key = ChainIdKey,
318 value = ChainId,
319 db_prefix = DbKeyPrefix::ChainId
320);
321
322#[derive(Debug, Encodable, Decodable)]
324pub struct TransactionFeesKey(pub TransactionId);
325
326impl_db_record!(
327 key = TransactionFeesKey,
328 value = Amounts,
329 db_prefix = DbKeyPrefix::TransactionFees,
330);
331
332#[derive(Debug, Encodable, Decodable, Serialize)]
334pub struct ClientMetadataKey;
335
336#[derive(Debug, Encodable)]
337pub struct ClientMetadataPrefix;
338
339impl_db_record!(
340 key = ClientMetadataKey,
341 value = Metadata,
342 db_prefix = DbKeyPrefix::ClientMetadata
343);
344
345impl_db_lookup!(key = ClientMetadataKey, query_prefix = ClientMetadataPrefix);
346
347#[derive(Debug, Encodable, Decodable, Serialize)]
349pub struct ClientInitStateKey;
350
351#[derive(Debug, Encodable)]
352pub struct ClientInitStatePrefix;
353
354#[derive(Debug, Encodable, Decodable)]
356pub enum InitMode {
357 Fresh,
359 Recover { snapshot: Option<ClientBackup> },
362}
363
364#[derive(Debug, Encodable, Decodable)]
370pub enum InitModeComplete {
371 Fresh,
372 Recover,
373}
374
375#[derive(Debug, Encodable, Decodable)]
377pub enum InitState {
378 Pending(InitMode),
381 Complete(InitModeComplete),
383}
384
385impl InitState {
386 pub fn into_complete(self) -> Self {
387 match self {
388 InitState::Pending(p) => InitState::Complete(match p {
389 InitMode::Fresh => InitModeComplete::Fresh,
390 InitMode::Recover { .. } => InitModeComplete::Recover,
391 }),
392 InitState::Complete(t) => InitState::Complete(t),
393 }
394 }
395
396 pub fn does_require_recovery(&self) -> Option<Option<ClientBackup>> {
397 match self {
398 InitState::Pending(p) => match p {
399 InitMode::Fresh => None,
400 InitMode::Recover { snapshot } => Some(snapshot.clone()),
401 },
402 InitState::Complete(_) => None,
403 }
404 }
405
406 pub fn is_pending(&self) -> bool {
407 match self {
408 InitState::Pending(_) => true,
409 InitState::Complete(_) => false,
410 }
411 }
412}
413
414impl_db_record!(
415 key = ClientInitStateKey,
416 value = InitState,
417 db_prefix = DbKeyPrefix::ClientInitState
418);
419
420impl_db_lookup!(
421 key = ClientInitStateKey,
422 query_prefix = ClientInitStatePrefix
423);
424
425#[derive(Debug, Encodable, Decodable, Serialize)]
426pub struct ClientModuleRecovery {
427 pub module_instance_id: ModuleInstanceId,
428}
429
430#[derive(Debug, Clone, Encodable, Decodable)]
431pub struct ClientModuleRecoveryState {
432 pub progress: RecoveryProgress,
433}
434
435impl ClientModuleRecoveryState {
436 pub fn is_done(&self) -> bool {
437 self.progress.is_done()
438 }
439}
440
441impl_db_record!(
442 key = ClientModuleRecovery,
443 value = ClientModuleRecoveryState,
444 db_prefix = DbKeyPrefix::ClientModuleRecovery,
445);
446
447#[derive(Debug, Encodable, Decodable, Serialize)]
454pub struct ClientModuleRecoveryIncorrectDoNotUse {
455 pub module_instance_id: ModuleInstanceId,
456}
457
458impl_db_record!(
459 key = ClientModuleRecoveryIncorrectDoNotUse,
460 value = ClientModuleRecoveryState,
461 db_prefix = DbKeyPrefix::ClientInitState,
463);
464
465#[derive(Debug, Encodable, Decodable)]
470pub struct LastBackupKey;
471
472impl_db_record!(
473 key = LastBackupKey,
474 value = ClientBackup,
475 db_prefix = DbKeyPrefix::ClientLastBackup
476);
477
478#[derive(Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
479pub(crate) struct MetaFieldPrefix;
480
481#[derive(Encodable, Decodable, Debug)]
482pub struct MetaServiceInfoKey;
483
484#[derive(Debug)]
485pub struct MetaServiceInfo {
486 pub last_updated: SystemTime,
487 pub revision: u64,
488}
489
490impl Encodable for MetaServiceInfo {
491 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
492 encode_legacy_system_time(&self.last_updated, writer)?;
493 self.revision.consensus_encode(writer)
494 }
495}
496
497impl Decodable for MetaServiceInfo {
498 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
499 decoder: &mut D,
500 modules: &ModuleDecoderRegistry,
501 ) -> Result<Self, DecodeError> {
502 Ok(Self {
503 last_updated: with_decoding_context(
504 decode_legacy_system_time_from_finite_reader(decoder, modules),
505 "Decoding named block field: MetaServiceInfo{ ... last_updated ... }",
506 )?,
507 revision: decode_field_from_finite_reader(
508 decoder,
509 modules,
510 "Decoding named block field: MetaServiceInfo{ ... revision ... }",
511 )?,
512 })
513 }
514}
515
516#[derive(
517 Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
518)]
519pub(crate) struct MetaFieldKey(pub fedimint_client_module::meta::MetaFieldKey);
520
521#[derive(Encodable, Decodable, Debug, Clone, Serialize, Deserialize)]
522pub(crate) struct MetaFieldValue(pub fedimint_client_module::meta::MetaFieldValue);
523
524impl_db_record!(
525 key = MetaFieldKey,
526 value = MetaFieldValue,
527 db_prefix = DbKeyPrefix::ClientMetaField
528);
529
530impl_db_record!(
531 key = MetaServiceInfoKey,
532 value = MetaServiceInfo,
533 db_prefix = DbKeyPrefix::ClientMetaServiceInfo
534);
535
536impl_db_lookup!(key = MetaFieldKey, query_prefix = MetaFieldPrefix);
537
538pub fn get_core_client_database_migrations()
539-> BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> {
540 let mut migrations: BTreeMap<DatabaseVersion, fedimint_core::db::ClientCoreDbMigrationFn> =
541 BTreeMap::new();
542 migrations.insert(
543 DatabaseVersion(0),
544 Box::new(|mut ctx| {
545 Box::pin(async move {
546 let mut dbtx = ctx.dbtx();
547
548 let config_v0 = dbtx
549 .find_by_prefix(&ClientConfigKeyPrefixV0)
550 .await
551 .collect::<Vec<_>>()
552 .await;
553
554 assert!(config_v0.len() <= 1);
555 let Some((id, config_v0)) = config_v0.into_iter().next() else {
556 return Ok(());
557 };
558
559 let global = GlobalClientConfig {
560 api_endpoints: config_v0.global.api_endpoints,
561 broadcast_public_keys: None,
562 consensus_version: config_v0.global.consensus_version,
563 meta: config_v0.global.meta,
564 };
565
566 let config = ClientConfig {
567 global,
568 modules: config_v0.modules,
569 };
570
571 dbtx.remove_entry(&id).await;
572 dbtx.insert_new_entry(&ClientConfigKey, &config).await;
573 Ok(())
574 })
575 }),
576 );
577
578 migrations.insert(
580 DatabaseVersion(1),
581 Box::new(|mut ctx| {
582 Box::pin(async move {
583 let mut dbtx = ctx.dbtx();
584
585 let operation_logs = dbtx
587 .find_by_prefix(&OperationLogKeyPrefixV0)
588 .await
589 .collect::<Vec<_>>()
590 .await;
591
592 let mut op_id_max_time = BTreeMap::new();
594
595 {
597 let mut inactive_states_stream =
598 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes).await;
599
600 while let Some((state, meta)) = inactive_states_stream.next().await {
601 let entry = op_id_max_time
602 .entry(state.operation_id)
603 .or_insert(meta.exited_at);
604 *entry = (*entry).max(meta.exited_at);
605 }
606 }
607 for (op_key_v0, log_entry_v0) in operation_logs {
609 let new_entry = OperationLogEntry::new(
610 log_entry_v0.operation_module_kind,
611 log_entry_v0.meta,
612 log_entry_v0.outcome.map(|outcome| {
613 OperationOutcome {
614 outcome,
615 time: op_id_max_time
618 .get(&op_key_v0.operation_id)
619 .copied()
620 .unwrap_or_else(fedimint_core::time::now),
621 }
622 }),
623 );
624
625 dbtx.remove_entry(&op_key_v0).await;
626 dbtx.insert_entry(
627 &OperationLogKey {
628 operation_id: op_key_v0.operation_id,
629 },
630 &new_entry,
631 )
632 .await;
633 }
634
635 Ok(())
636 })
637 }),
638 );
639
640 migrations.insert(
642 DatabaseVersion(2),
643 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
644 Box::pin(async move {
645 let mut dbtx = ctx.dbtx();
646
647 {
649 let mut ordered_log_entries = dbtx
650 .raw_find_by_prefix(&[DB_KEY_PREFIX_EVENT_LOG])
651 .await
652 .expect("DB operation failed");
653 let mut keys_to_migrate = vec![];
654 while let Some((k, _v)) = ordered_log_entries.next().await {
655 trace!(target: LOG_CLIENT_DB,
656 k=%k.as_hex(),
657 "Checking ordered log key"
658 );
659 if EventLogId::consensus_decode_whole(&k[1..], &Default::default()).is_err()
660 {
661 assert!(
662 UnordedEventLogId::consensus_decode_whole(
663 &k[1..],
664 &Default::default()
665 )
666 .is_ok()
667 );
668 keys_to_migrate.push(k);
669 }
670 }
671 drop(ordered_log_entries);
672 for mut key_to_migrate in keys_to_migrate {
673 warn!(target: LOG_CLIENT_DB,
674 k=%key_to_migrate.as_hex(),
675 "Migrating unordered event log entry written to an ordered log"
676 );
677 let v = dbtx
678 .raw_remove_entry(&key_to_migrate)
679 .await
680 .expect("DB operation failed")
681 .expect("Was there a moment ago");
682 assert_eq!(key_to_migrate[0], 0x39);
683 key_to_migrate[0] = DB_KEY_PREFIX_UNORDERED_EVENT_LOG;
684 assert_eq!(key_to_migrate[0], 0x3a);
685 dbtx.raw_insert_bytes(&key_to_migrate, &v)
686 .await
687 .expect("DB operation failed");
688 }
689 }
690
691 {
693 let mut unordered_log_entries = dbtx
694 .raw_find_by_prefix(&[DB_KEY_PREFIX_UNORDERED_EVENT_LOG])
695 .await
696 .expect("DB operation failed");
697 let mut keys_to_migrate = vec![];
698 while let Some((k, _v)) = unordered_log_entries.next().await {
699 trace!(target: LOG_CLIENT_DB,
700 k=%k.as_hex(),
701 "Checking unordered log key"
702 );
703 if UnordedEventLogId::consensus_decode_whole(&k[1..], &Default::default())
704 .is_err()
705 {
706 assert!(
707 EventLogId::consensus_decode_whole(&k[1..], &Default::default())
708 .is_ok()
709 );
710 keys_to_migrate.push(k);
711 }
712 }
713 drop(unordered_log_entries);
714 for mut key_to_migrate in keys_to_migrate {
715 warn!(target: LOG_CLIENT_DB,
716 k=%key_to_migrate.as_hex(),
717 "Migrating ordered event log entry written to an unordered log"
718 );
719 let v = dbtx
720 .raw_remove_entry(&key_to_migrate)
721 .await
722 .expect("DB operation failed")
723 .expect("Was there a moment ago");
724 assert_eq!(key_to_migrate[0], 0x3a);
725 key_to_migrate[0] = DB_KEY_PREFIX_EVENT_LOG;
726 assert_eq!(key_to_migrate[0], 0x39);
727 dbtx.raw_insert_bytes(&key_to_migrate, &v)
728 .await
729 .expect("DB operation failed");
730 }
731 }
732 Ok(())
733 })
734 }),
735 );
736
737 migrations.insert(
739 DatabaseVersion(3),
740 Box::new(|mut ctx: fedimint_core::db::DbMigrationFnContext<'_, _>| {
741 Box::pin(async move {
742 let mut dbtx = ctx.dbtx();
743
744 for module_id in 0..u16::MAX {
745 let old_key = ClientModuleRecoveryIncorrectDoNotUse {
746 module_instance_id: module_id,
747 };
748 let new_key = ClientModuleRecovery {
749 module_instance_id: module_id,
750 };
751 let Some(value) = dbtx.get_value(&old_key).await else {
752 debug!(target: LOG_CLIENT_DB, %module_id, "No more ClientModuleRecovery keys found for migration");
753 break;
754 };
755
756 debug!(target: LOG_CLIENT_DB, %module_id, "Migrating old ClientModuleRecovery key");
757 dbtx.remove_entry(&old_key).await.expect("Is there.");
758 assert!(dbtx.insert_entry(&new_key, &value).await.is_none());
759 }
760
761 Ok(())
762 })
763 }),
764 );
765 migrations
766}
767
768pub async fn apply_migrations_core_client_dbtx(
772 dbtx: &mut DatabaseTransaction<'_>,
773 kind: String,
774) -> Result<(), DbMigrationError> {
775 apply_migrations_dbtx(
776 dbtx,
777 (),
778 kind,
779 get_core_client_database_migrations(),
780 None,
781 Some(DbKeyPrefix::UserData as u8),
782 )
783 .await
784}
785
786pub async fn apply_migrations_client_module(
796 db: &Database,
797 kind: String,
798 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
799 module_instance_id: ModuleInstanceId,
800) -> Result<(), DbMigrationError> {
801 let mut dbtx = db.begin_transaction().await;
802 apply_migrations_client_module_dbtx(
803 &mut dbtx.to_ref_nc(),
804 kind,
805 migrations,
806 module_instance_id,
807 )
808 .await?;
809 Ok(dbtx.commit_tx_result().await?)
810}
811
812pub async fn apply_migrations_client_module_dbtx(
813 dbtx: &mut DatabaseTransaction<'_>,
814 kind: String,
815 migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn>,
816 module_instance_id: ModuleInstanceId,
817) -> Result<(), DbMigrationError> {
818 let is_new_db = dbtx
821 .raw_find_by_prefix(&[MODULE_GLOBAL_PREFIX])
822 .await?
823 .next()
824 .await
825 .is_none();
826
827 let target_version = get_current_database_version(&migrations);
828
829 create_database_version_dbtx(
831 dbtx,
832 target_version,
833 Some(module_instance_id),
834 kind.clone(),
835 is_new_db,
836 )
837 .await;
838
839 let current_version = dbtx
840 .get_value(&DatabaseVersionKey(module_instance_id))
841 .await;
842
843 let db_version = if let Some(mut current_version) = current_version {
844 if current_version == target_version {
845 trace!(
846 target: LOG_CLIENT_DB,
847 %current_version,
848 %target_version,
849 module_instance_id,
850 kind,
851 "Database version up to date"
852 );
853 return Ok(());
854 }
855
856 if target_version < current_version {
857 return Err(DbMigrationError::VersionTooHigh {
858 kind,
859 on_disk: current_version,
860 target: target_version,
861 });
862 }
863
864 info!(
865 target: LOG_CLIENT_DB,
866 %current_version,
867 %target_version,
868 module_instance_id,
869 kind,
870 "Migrating client module database"
871 );
872 let mut active_states = get_active_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
873 let mut inactive_states =
874 get_inactive_states(&mut dbtx.to_ref_nc(), module_instance_id).await;
875
876 while current_version < target_version {
877 let new_states = if let Some(migration) = migrations.get(¤t_version) {
878 debug!(
879 target: LOG_CLIENT_DB,
880 module_instance_id,
881 %kind,
882 %current_version,
883 %target_version,
884 "Running module db migration");
885
886 migration(
887 &mut dbtx
888 .to_ref_with_prefix_module_id(module_instance_id)
889 .0
890 .into_nc(),
891 active_states.clone(),
892 inactive_states.clone(),
893 )
894 .await?
895 } else {
896 warn!(
897 target: LOG_CLIENT_DB,
898 ?current_version, "Missing client db migration");
899 None
900 };
901
902 if let Some((new_active_states, new_inactive_states)) = new_states {
905 remove_old_and_persist_new_active_states(
906 &mut dbtx.to_ref_nc(),
907 new_active_states.clone(),
908 active_states.clone(),
909 module_instance_id,
910 )
911 .await;
912 remove_old_and_persist_new_inactive_states(
913 &mut dbtx.to_ref_nc(),
914 new_inactive_states.clone(),
915 inactive_states.clone(),
916 module_instance_id,
917 )
918 .await;
919
920 active_states = new_active_states;
922 inactive_states = new_inactive_states;
923 }
924
925 current_version = current_version.increment();
926 dbtx.insert_entry(&DatabaseVersionKey(module_instance_id), ¤t_version)
927 .await;
928 }
929
930 current_version
931 } else {
932 target_version
933 };
934
935 debug!(
936 target: LOG_CLIENT_DB,
937 ?kind, ?db_version, "Client DB Version");
938 Ok(())
939}
940
941pub async fn get_active_states(
947 dbtx: &mut DatabaseTransaction<'_>,
948 module_instance_id: ModuleInstanceId,
949) -> Vec<(Vec<u8>, OperationId)> {
950 dbtx.find_by_prefix(&ActiveStateKeyPrefixBytes)
951 .await
952 .filter_map(|(state, _)| async move {
953 if module_instance_id == state.module_instance_id {
954 Some((state.state, state.operation_id))
955 } else {
956 None
957 }
958 })
959 .collect::<Vec<_>>()
960 .await
961}
962
963pub async fn get_inactive_states(
969 dbtx: &mut DatabaseTransaction<'_>,
970 module_instance_id: ModuleInstanceId,
971) -> Vec<(Vec<u8>, OperationId)> {
972 dbtx.find_by_prefix(&InactiveStateKeyPrefixBytes)
973 .await
974 .filter_map(|(state, _)| async move {
975 if module_instance_id == state.module_instance_id {
976 Some((state.state, state.operation_id))
977 } else {
978 None
979 }
980 })
981 .collect::<Vec<_>>()
982 .await
983}
984
985pub async fn remove_old_and_persist_new_active_states(
989 dbtx: &mut DatabaseTransaction<'_>,
990 new_active_states: Vec<(Vec<u8>, OperationId)>,
991 states_to_remove: Vec<(Vec<u8>, OperationId)>,
992 module_instance_id: ModuleInstanceId,
993) {
994 for (bytes, operation_id) in states_to_remove {
996 dbtx.remove_entry(&ActiveStateKeyBytes {
997 operation_id,
998 module_instance_id,
999 state: bytes,
1000 })
1001 .await
1002 .expect("Did not delete anything");
1003 }
1004
1005 for (bytes, operation_id) in new_active_states {
1007 dbtx.insert_new_entry(
1008 &ActiveStateKeyBytes {
1009 operation_id,
1010 module_instance_id,
1011 state: bytes,
1012 },
1013 &ActiveStateMeta::default(),
1014 )
1015 .await;
1016 }
1017}
1018
1019pub async fn remove_old_and_persist_new_inactive_states(
1023 dbtx: &mut DatabaseTransaction<'_>,
1024 new_inactive_states: Vec<(Vec<u8>, OperationId)>,
1025 states_to_remove: Vec<(Vec<u8>, OperationId)>,
1026 module_instance_id: ModuleInstanceId,
1027) {
1028 for (bytes, operation_id) in states_to_remove {
1030 dbtx.remove_entry(&InactiveStateKeyBytes {
1031 operation_id,
1032 module_instance_id,
1033 state: bytes,
1034 })
1035 .await
1036 .expect("Did not delete anything");
1037 }
1038
1039 for (bytes, operation_id) in new_inactive_states {
1041 dbtx.insert_new_entry(
1042 &InactiveStateKeyBytes {
1043 operation_id,
1044 module_instance_id,
1045 state: bytes,
1046 },
1047 &InactiveStateMeta {
1048 created_at: fedimint_core::time::now(),
1049 exited_at: fedimint_core::time::now(),
1050 },
1051 )
1052 .await;
1053 }
1054}
1055
1056pub async fn get_decoded_client_secret<T: Decodable>(
1060 db: &Database,
1061) -> Result<T, ClientSecretError> {
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) => Ok(T::consensus_decode_whole(
1067 &client_secret,
1068 &ModuleRegistry::default(),
1069 )?),
1070 None => Err(ClientSecretError::NotPresent),
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}