fedimint_client/module/
recovery.rs

1use std::any::Any;
2use std::fmt::{self, Debug};
3
4use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, ModuleKind};
5use fedimint_core::encoding::{Decodable, DynEncodable, Encodable};
6use fedimint_core::task::{MaybeSend, MaybeSync};
7use fedimint_core::{
8    maybe_add_send_sync, module_plugin_dyn_newtype_clone_passthrough,
9    module_plugin_dyn_newtype_define, module_plugin_dyn_newtype_encode_decode,
10    module_plugin_dyn_newtype_eq_passthrough,
11};
12use serde::{Deserialize, Serialize};
13
14pub trait IModuleBackup: Debug + DynEncodable {
15    fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any));
16    fn module_kind(&self) -> Option<ModuleKind>;
17    fn clone(&self, instance_id: ModuleInstanceId) -> DynModuleBackup;
18    fn erased_eq_no_instance_id(&self, other: &DynModuleBackup) -> bool;
19}
20
21pub trait ModuleBackup:
22    std::fmt::Debug
23    + IntoDynInstance<DynType = DynModuleBackup>
24    + std::cmp::PartialEq
25    + DynEncodable
26    + Decodable
27    + Clone
28    + MaybeSend
29    + MaybeSync
30    + 'static
31{
32    const KIND: Option<ModuleKind>;
33}
34
35impl IModuleBackup for ::fedimint_core::core::DynUnknown {
36    fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
37        self
38    }
39
40    fn module_kind(&self) -> Option<ModuleKind> {
41        None
42    }
43
44    fn clone(&self, instance_id: ::fedimint_core::core::ModuleInstanceId) -> DynModuleBackup {
45        DynModuleBackup::from_typed(instance_id, <Self as Clone>::clone(self))
46    }
47
48    fn erased_eq_no_instance_id(&self, other: &DynModuleBackup) -> bool {
49        let other: &Self = other
50            .as_any()
51            .downcast_ref()
52            .expect("Type is ensured in previous step");
53
54        self == other
55    }
56}
57
58impl<T> IModuleBackup for T
59where
60    T: ModuleBackup,
61{
62    fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
63        self
64    }
65
66    fn module_kind(&self) -> Option<ModuleKind> {
67        T::KIND
68    }
69
70    fn clone(&self, instance_id: ::fedimint_core::core::ModuleInstanceId) -> DynModuleBackup {
71        DynModuleBackup::from_typed(instance_id, <Self as Clone>::clone(self))
72    }
73
74    fn erased_eq_no_instance_id(&self, other: &DynModuleBackup) -> bool {
75        let other: &Self = other
76            .as_any()
77            .downcast_ref()
78            .expect("Type is ensured in previous step");
79
80        self == other
81    }
82}
83
84module_plugin_dyn_newtype_define! {
85    pub DynModuleBackup(Box<IModuleBackup>)
86}
87
88module_plugin_dyn_newtype_encode_decode!(DynModuleBackup);
89
90module_plugin_dyn_newtype_clone_passthrough!(DynModuleBackup);
91
92module_plugin_dyn_newtype_eq_passthrough!(DynModuleBackup);
93
94/// A backup type for modules without a backup implementation. The default
95/// variant allows implementing a backup strategy for the module later on by
96/// copying this enum into the module and adding a second variant to it.
97#[derive(Clone, PartialEq, Eq, Debug, Encodable, Decodable)]
98pub enum NoModuleBackup {
99    NoModuleBackup,
100    #[encodable_default]
101    Default {
102        variant: u64,
103        bytes: Vec<u8>,
104    },
105}
106
107impl ModuleBackup for NoModuleBackup {
108    const KIND: Option<ModuleKind> = None;
109}
110
111impl IntoDynInstance for NoModuleBackup {
112    type DynType = DynModuleBackup;
113
114    fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
115        DynModuleBackup::from_typed(instance_id, self)
116    }
117}
118
119/// Progress of the recovery
120///
121/// This includes "magic" value: if `total` is `0` the progress is "not started
122/// yet"/"empty"/"none"
123#[derive(Debug, Copy, Clone, Encodable, Decodable, Serialize, Deserialize)]
124pub struct RecoveryProgress {
125    pub complete: u32,
126    pub total: u32,
127}
128
129impl RecoveryProgress {
130    pub fn is_done(self) -> bool {
131        !self.is_none() && self.total <= self.complete
132    }
133
134    pub fn none() -> RecoveryProgress {
135        Self {
136            complete: 0,
137            total: 0,
138        }
139    }
140
141    pub fn is_none(self) -> bool {
142        self.total == 0
143    }
144
145    pub fn to_complete(self) -> RecoveryProgress {
146        if self.is_none() {
147            // Since we don't have a valid "total", we make up a 1 out of 1
148            Self {
149                complete: 1,
150                total: 1,
151            }
152        } else {
153            Self {
154                complete: self.total,
155                total: self.total,
156            }
157        }
158    }
159}
160
161impl fmt::Display for RecoveryProgress {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        f.write_fmt(format_args!("{}/{}", self.complete, self.total))
164    }
165}