fedimint_mint_client/
backup.rs

1use fedimint_client_module::module::recovery::{DynModuleBackup, ModuleBackup};
2use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, ModuleKind};
3use fedimint_core::db::DatabaseTransaction;
4use fedimint_core::encoding::{Decodable, Encodable};
5use fedimint_core::{Amount, OutPoint, Tiered, TieredMulti};
6use fedimint_mint_common::KIND;
7use serde::{Deserialize, Serialize};
8
9use super::MintClientModule;
10use crate::output::{MintOutputStateMachine, NoteIssuanceRequest};
11use crate::{MintClientStateMachines, NoteIndex, SpendableNote};
12
13pub mod recovery;
14
15#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Encodable, Decodable)]
16pub enum EcashBackup {
17    V0(EcashBackupV0),
18    #[encodable_default]
19    Default {
20        variant: u64,
21        bytes: Vec<u8>,
22    },
23}
24
25impl EcashBackup {
26    pub fn new_v0(
27        spendable_notes: TieredMulti<SpendableNote>,
28        pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)>,
29        session_count: u64,
30        next_note_idx: Tiered<NoteIndex>,
31    ) -> EcashBackup {
32        EcashBackup::V0(EcashBackupV0 {
33            spendable_notes,
34            pending_notes,
35            session_count,
36            next_note_idx,
37        })
38    }
39}
40
41/// Snapshot of a ecash state (notes)
42///
43/// Used to speed up and improve privacy of ecash recovery,
44/// by avoiding scanning the whole history.
45#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Encodable, Decodable)]
46pub struct EcashBackupV0 {
47    spendable_notes: TieredMulti<SpendableNote>,
48    pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)>,
49    session_count: u64,
50    next_note_idx: Tiered<NoteIndex>,
51}
52
53impl EcashBackupV0 {
54    /// An empty backup with, like a one created by a newly created client.
55    pub fn new_empty() -> Self {
56        Self {
57            spendable_notes: TieredMulti::default(),
58            pending_notes: vec![],
59            session_count: 0,
60            next_note_idx: Tiered::default(),
61        }
62    }
63}
64
65impl ModuleBackup for EcashBackup {
66    const KIND: Option<ModuleKind> = Some(KIND);
67}
68
69impl IntoDynInstance for EcashBackup {
70    type DynType = DynModuleBackup;
71
72    fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
73        DynModuleBackup::from_typed(instance_id, self)
74    }
75}
76
77impl MintClientModule {
78    pub async fn prepare_plaintext_ecash_backup(
79        &self,
80        dbtx: &mut DatabaseTransaction<'_>,
81    ) -> anyhow::Result<EcashBackup> {
82        // fetch consensus height first - so we dont miss anything when scanning
83        let session_count = self.client_ctx.global_api().session_count().await?;
84
85        let notes = Self::get_all_spendable_notes(dbtx).await;
86
87        let pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)> = self
88            .client_ctx
89            .get_own_active_states()
90            .await
91            .into_iter()
92            .filter_map(|(state, _active_state)| match state {
93                MintClientStateMachines::Output(MintOutputStateMachine {
94                    common,
95                    state: crate::output::MintOutputStates::Created(created_state),
96                }) => Some(vec![(
97                    OutPoint {
98                        txid: common.out_point_range.txid(),
99                        // MintOutputStates::Created always has one out_idx
100                        out_idx: common.out_point_range.start_idx(),
101                    },
102                    created_state.amount,
103                    created_state.issuance_request,
104                )]),
105                MintClientStateMachines::Output(MintOutputStateMachine {
106                    common,
107                    state: crate::output::MintOutputStates::CreatedMulti(created_state),
108                }) => Some(
109                    common
110                        .out_point_range
111                        .into_iter()
112                        .map(|out_point| {
113                            let issuance_request = created_state
114                                .issuance_requests
115                                .get(&out_point.out_idx)
116                                .expect("Must have corresponding out_idx");
117                            (out_point, issuance_request.0, issuance_request.1)
118                        })
119                        .collect(),
120                ),
121                _ => None,
122            })
123            .flatten()
124            .collect::<Vec<_>>();
125
126        let mut idxes = vec![];
127        for &amount in self.cfg.tbs_pks.tiers() {
128            idxes.push((amount, self.get_next_note_index(dbtx, amount).await));
129        }
130        let next_note_idx = Tiered::from_iter(idxes);
131
132        Ok(EcashBackup::new_v0(
133            notes
134                .into_iter_items()
135                .map(|(amt, spendable_note)| Ok((amt, spendable_note.decode()?)))
136                .collect::<anyhow::Result<TieredMulti<_>>>()?,
137            pending_notes,
138            session_count,
139            next_note_idx,
140        ))
141    }
142}