Skip to main content

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