fedimint_mint_client/
backup.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use fedimint_client::module::recovery::{DynModuleBackup, ModuleBackup};
use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, ModuleKind};
use fedimint_core::db::DatabaseTransaction;
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::{Amount, OutPoint, Tiered, TieredMulti};
use fedimint_mint_common::KIND;
use serde::{Deserialize, Serialize};

use super::MintClientModule;
use crate::output::{MintOutputStateMachine, NoteIssuanceRequest};
use crate::{MintClientStateMachines, NoteIndex, SpendableNote};

pub mod recovery;

#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Encodable, Decodable)]
pub enum EcashBackup {
    V0(EcashBackupV0),
    #[encodable_default]
    Default {
        variant: u64,
        bytes: Vec<u8>,
    },
}

impl EcashBackup {
    pub fn new_v0(
        spendable_notes: TieredMulti<SpendableNote>,
        pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)>,
        session_count: u64,
        next_note_idx: Tiered<NoteIndex>,
    ) -> EcashBackup {
        EcashBackup::V0(EcashBackupV0 {
            spendable_notes,
            pending_notes,
            session_count,
            next_note_idx,
        })
    }
}

/// Snapshot of a ecash state (notes)
///
/// Used to speed up and improve privacy of ecash recovery,
/// by avoiding scanning the whole history.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Encodable, Decodable)]
pub struct EcashBackupV0 {
    spendable_notes: TieredMulti<SpendableNote>,
    pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)>,
    session_count: u64,
    next_note_idx: Tiered<NoteIndex>,
}

impl EcashBackupV0 {
    /// An empty backup with, like a one created by a newly created client.
    pub fn new_empty() -> Self {
        Self {
            spendable_notes: TieredMulti::default(),
            pending_notes: vec![],
            session_count: 0,
            next_note_idx: Tiered::default(),
        }
    }
}

impl ModuleBackup for EcashBackup {
    const KIND: Option<ModuleKind> = Some(KIND);
}

impl IntoDynInstance for EcashBackup {
    type DynType = DynModuleBackup;

    fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
        DynModuleBackup::from_typed(instance_id, self)
    }
}

impl MintClientModule {
    pub async fn prepare_plaintext_ecash_backup(
        &self,
        dbtx: &mut DatabaseTransaction<'_>,
    ) -> anyhow::Result<EcashBackup> {
        // fetch consensus height first - so we dont miss anything when scanning
        let session_count = self.client_ctx.global_api().session_count().await?;

        let notes = Self::get_all_spendable_notes(dbtx).await;

        let pending_notes: Vec<(OutPoint, Amount, NoteIssuanceRequest)> = self
            .client_ctx
            .get_own_active_states()
            .await
            .into_iter()
            .filter_map(|(state, _active_state)| match state {
                MintClientStateMachines::Output(MintOutputStateMachine {
                    common,
                    state: crate::output::MintOutputStates::Created(created_state),
                }) => Some(vec![(
                    OutPoint {
                        txid: common.out_point_range.txid(),
                        // MintOutputStates::Created always has one out_idx
                        out_idx: common.out_point_range.start_idx(),
                    },
                    created_state.amount,
                    created_state.issuance_request,
                )]),
                MintClientStateMachines::Output(MintOutputStateMachine {
                    common,
                    state: crate::output::MintOutputStates::CreatedMulti(created_state),
                }) => Some(
                    common
                        .out_point_range
                        .into_iter()
                        .map(|out_point| {
                            let issuance_request = created_state
                                .issuance_requests
                                .get(&out_point.out_idx)
                                .expect("Must have corresponding out_idx");
                            (out_point, issuance_request.0, issuance_request.1)
                        })
                        .collect(),
                ),
                _ => None,
            })
            .flatten()
            .collect::<Vec<_>>();

        let mut idxes = vec![];
        for &amount in self.cfg.tbs_pks.tiers() {
            idxes.push((amount, self.get_next_note_index(dbtx, amount).await));
        }
        let next_note_idx = Tiered::from_iter(idxes);

        Ok(EcashBackup::new_v0(
            notes
                .into_iter_items()
                .map(|(amt, spendable_note)| Ok((amt, spendable_note.decode()?)))
                .collect::<anyhow::Result<TieredMulti<_>>>()?,
            pending_notes,
            session_count,
            next_note_idx,
        ))
    }
}