fedimint_mint_client/
client_db.rs

1use std::io::Cursor;
2
3use fedimint_client_module::module::init::recovery::RecoveryFromHistoryCommon;
4use fedimint_client_module::module::{IdxRange, OutPointRange};
5use fedimint_core::core::OperationId;
6use fedimint_core::db::{DatabaseRecord, DatabaseTransaction, IDatabaseTransactionOpsCore};
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::module::registry::ModuleDecoderRegistry;
9use fedimint_core::{Amount, impl_db_lookup, impl_db_record};
10use fedimint_logging::LOG_CLIENT_MODULE_MINT;
11use fedimint_mint_common::Nonce;
12use serde::Serialize;
13use strum_macros::EnumIter;
14use tracing::debug;
15
16use crate::backup::recovery::MintRecoveryState;
17use crate::input::{MintInputCommon, MintInputStateMachine, MintInputStateMachineV0};
18use crate::oob::{MintOOBStateMachine, MintOOBStateMachineV0, MintOOBStates, MintOOBStatesV0};
19use crate::output::{MintOutputCommon, MintOutputStateMachine, MintOutputStateMachineV0};
20use crate::{MintClientStateMachines, NoteIndex, SpendableNoteUndecoded};
21
22#[repr(u8)]
23#[derive(Clone, EnumIter, Debug)]
24pub enum DbKeyPrefix {
25    Note = 0x20,
26    NextECashNoteIndex = 0x2a,
27    CancelledOOBSpend = 0x2b,
28    RecoveryState = 0x2c,
29    RecoveryFinalized = 0x2d,
30    ReusedNoteIndices = 0x2e,
31    RecoveryStateV2 = 0x2f,
32    /// Prefixes between 0xb0..=0xcf shall all be considered allocated for
33    /// historical and future external use
34    ExternalReservedStart = 0xb0,
35    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
36    /// historical and future internal use
37    CoreInternalReservedStart = 0xd0,
38    CoreInternalReservedEnd = 0xff,
39}
40
41impl std::fmt::Display for DbKeyPrefix {
42    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43        write!(f, "{self:?}")
44    }
45}
46
47#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
48pub struct NoteKey {
49    pub amount: Amount,
50    pub nonce: Nonce,
51}
52
53#[derive(Debug, Clone, Encodable, Decodable)]
54pub struct NoteKeyPrefix;
55
56impl_db_record!(
57    key = NoteKey,
58    value = SpendableNoteUndecoded,
59    db_prefix = DbKeyPrefix::Note,
60);
61impl_db_lookup!(key = NoteKey, query_prefix = NoteKeyPrefix);
62
63#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
64pub struct NextECashNoteIndexKey(pub Amount);
65
66#[derive(Debug, Clone, Encodable, Decodable)]
67pub struct NextECashNoteIndexKeyPrefix;
68
69impl_db_record!(
70    key = NextECashNoteIndexKey,
71    value = u64,
72    db_prefix = DbKeyPrefix::NextECashNoteIndex,
73);
74impl_db_lookup!(
75    key = NextECashNoteIndexKey,
76    query_prefix = NextECashNoteIndexKeyPrefix
77);
78
79#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
80pub struct RecoveryStateKey;
81
82#[derive(Debug, Clone, Encodable, Decodable)]
83pub struct RestoreStateKeyPrefix;
84
85impl_db_record!(
86    key = RecoveryStateKey,
87    value = (MintRecoveryState, RecoveryFromHistoryCommon),
88    db_prefix = DbKeyPrefix::RecoveryState,
89);
90
91#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
92pub struct RecoveryFinalizedKey;
93
94#[derive(Debug, Clone, Encodable, Decodable)]
95pub struct RecoveryFinalizedKeyPrefix;
96
97impl_db_record!(
98    key = RecoveryFinalizedKey,
99    value = bool,
100    db_prefix = DbKeyPrefix::RecoveryFinalized,
101);
102
103#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
104pub struct ReusedNoteIndices;
105
106impl_db_record!(
107    key = ReusedNoteIndices,
108    value = Vec<(Amount, NoteIndex)>,
109    db_prefix = DbKeyPrefix::ReusedNoteIndices,
110);
111
112#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
113pub struct CancelledOOBSpendKey(pub OperationId);
114
115#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
116pub struct CancelledOOBSpendKeyPrefix;
117
118impl_db_record!(
119    key = CancelledOOBSpendKey,
120    value = (),
121    db_prefix = DbKeyPrefix::CancelledOOBSpend,
122    notify_on_modify = true,
123);
124
125impl_db_lookup!(
126    key = CancelledOOBSpendKey,
127    query_prefix = CancelledOOBSpendKeyPrefix,
128);
129
130#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
131pub struct RecoveryStateV2Key;
132
133impl_db_record!(
134    key = RecoveryStateV2Key,
135    value = crate::backup::recovery::RecoveryStateV2,
136    db_prefix = DbKeyPrefix::RecoveryStateV2,
137);
138
139pub async fn migrate_to_v1(
140    dbtx: &mut DatabaseTransaction<'_>,
141) -> anyhow::Result<Option<(Vec<(Vec<u8>, OperationId)>, Vec<(Vec<u8>, OperationId)>)>> {
142    dbtx.ensure_isolated().expect("Must be in our database");
143    // between v0 and v1, we changed the format of `MintRecoveryState`, and instead
144    // of migrating it, we can just delete it, so the recovery will just start
145    // again, ignoring any existing state from before the migration
146    if dbtx
147        .raw_remove_entry(&[RecoveryStateKey::DB_PREFIX])
148        .await
149        .expect("Raw operations only fail on low level errors")
150        .is_some()
151    {
152        debug!(target: LOG_CLIENT_MODULE_MINT, "Deleted previous recovery state");
153    }
154
155    Ok(None)
156}
157
158/// Migrates `MintClientStateMachinesV0`
159pub(crate) fn migrate_state_to_v2(
160    operation_id: OperationId,
161    cursor: &mut Cursor<&[u8]>,
162) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
163    let decoders = ModuleDecoderRegistry::default();
164
165    let mint_client_state_machine_variant = u16::consensus_decode_partial(cursor, &decoders)?;
166
167    let new_mint_state_machine = match mint_client_state_machine_variant {
168        0 => {
169            let _output_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
170            let old_state = MintOutputStateMachineV0::consensus_decode_partial(cursor, &decoders)?;
171
172            MintClientStateMachines::Output(MintOutputStateMachine {
173                common: MintOutputCommon {
174                    operation_id: old_state.common.operation_id,
175                    out_point_range: OutPointRange::new_single(
176                        old_state.common.out_point.txid,
177                        old_state.common.out_point.out_idx,
178                    )
179                    .expect("Can't possibly overflow"),
180                },
181                state: old_state.state,
182            })
183        }
184        1 => {
185            let _input_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
186            let old_state = MintInputStateMachineV0::consensus_decode_partial(cursor, &decoders)?;
187
188            MintClientStateMachines::Input(MintInputStateMachine {
189                common: MintInputCommon {
190                    operation_id: old_state.common.operation_id,
191                    out_point_range: OutPointRange::new(
192                        old_state.common.txid,
193                        IdxRange::new_single(old_state.common.input_idx)
194                            .expect("Can't possibly overflow"),
195                    ),
196                },
197                state: old_state.state,
198            })
199        }
200        2 => {
201            let _oob_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
202            let old_state = MintOOBStateMachineV0::consensus_decode_partial(cursor, &decoders)?;
203
204            let new_state = match old_state.state {
205                MintOOBStatesV0::Created(created) => MintOOBStates::Created(created),
206                MintOOBStatesV0::UserRefund(refund) => MintOOBStates::UserRefund(refund),
207                MintOOBStatesV0::TimeoutRefund(refund) => MintOOBStates::TimeoutRefund(refund),
208            };
209            MintClientStateMachines::OOB(MintOOBStateMachine {
210                operation_id: old_state.operation_id,
211                state: new_state,
212            })
213        }
214        _ => return Ok(None),
215    };
216    Ok(Some((
217        new_mint_state_machine.consensus_encode_to_vec(),
218        operation_id,
219    )))
220}