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