Skip to main content

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