fedimint_dummy_client/
db.rs

1use std::io::Cursor;
2
3use fedimint_core::core::OperationId;
4use fedimint_core::db::{DatabaseTransaction, IDatabaseTransactionOpsCoreTyped};
5use fedimint_core::encoding::{Decodable, Encodable};
6use fedimint_core::module::AmountUnit;
7use fedimint_core::module::registry::ModuleDecoderRegistry;
8use fedimint_core::{Amount, TransactionId, impl_db_lookup, impl_db_record};
9use strum_macros::EnumIter;
10use tracing::warn;
11
12use crate::states::{DummyStateMachine, DummyStateMachineV1};
13
14#[repr(u8)]
15#[derive(Clone, Debug, EnumIter)]
16pub enum DbKeyPrefix {
17    ClientFunds = 0x04,
18    // Used to verify that 0x50 key can be written to, which used to conflict with
19    // `DatabaseVersionKeyV0`
20    ClientName = 0x50,
21    /// Prefixes between 0xb0..=0xcf shall all be considered allocated for
22    /// historical and future external use
23    ExternalReservedStart = 0xb0,
24    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
25    /// historical and future internal use
26    CoreInternalReservedStart = 0xd0,
27    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
28    /// historical and future internal use
29    CoreInternalReservedEnd = 0xff,
30}
31
32impl std::fmt::Display for DbKeyPrefix {
33    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
34        write!(f, "{self:?}")
35    }
36}
37
38#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash)]
39pub struct DummyClientFundsKeyV0;
40
41impl_db_record!(
42    key = DummyClientFundsKeyV0,
43    value = (),
44    db_prefix = DbKeyPrefix::ClientFunds,
45);
46
47#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash)]
48pub struct DummyClientFundsKeyV1;
49
50impl_db_record!(
51    key = DummyClientFundsKeyV1,
52    value = Amount,
53    db_prefix = DbKeyPrefix::ClientFunds,
54);
55
56#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash)]
57pub struct DummyClientFundsKey {
58    pub unit: AmountUnit,
59}
60
61impl_db_record!(
62    key = DummyClientFundsKey,
63    value = Amount,
64    db_prefix = DbKeyPrefix::ClientFunds,
65);
66
67#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash)]
68pub struct DummyClientFundsKeyV2PrefixAll;
69
70impl_db_lookup!(
71    key = DummyClientFundsKey,
72    query_prefix = DummyClientFundsKeyV2PrefixAll,
73);
74
75#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash)]
76pub struct DummyClientNameKey;
77
78impl_db_record!(
79    key = DummyClientNameKey,
80    value = String,
81    db_prefix = DbKeyPrefix::ClientName,
82);
83
84/// Migrates the database from version 0 to version 1 by
85/// removing `DummyClientFundsKeyV0` and inserting `DummyClientFundsKeyV1`.
86/// The new key/value pair has an `Amount` as the value.
87pub async fn migrate_to_v1(
88    dbtx: &mut DatabaseTransaction<'_>,
89) -> anyhow::Result<Option<(Vec<(Vec<u8>, OperationId)>, Vec<(Vec<u8>, OperationId)>)>> {
90    if dbtx.remove_entry(&DummyClientFundsKeyV0).await.is_some() {
91        // Since this is a dummy migration, we can insert any value for the client
92        // funds. Real modules should handle the funds properly.
93
94        dbtx.insert_new_entry(&DummyClientFundsKeyV1, &Amount::from_sats(1000))
95            .await;
96    } else {
97        warn!("Dummy client did not have client funds, skipping database migration");
98    }
99
100    Ok(None)
101}
102
103/// Maps all `Unreachable` states in the state machine to `OutputDone`
104pub(crate) fn get_v1_migrated_state(
105    operation_id: OperationId,
106    cursor: &mut Cursor<&[u8]>,
107) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
108    let decoders = ModuleDecoderRegistry::default();
109    let dummy_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
110
111    // We are only migrating the type of one of the variants, so we do nothing on
112    // other discriminants.
113    if dummy_sm_variant != 5 {
114        return Ok(None);
115    }
116
117    let _unreachable_state_length = u16::consensus_decode_partial(cursor, &decoders)?;
118
119    // Migrate `Unreachable` states to `OutputDone`
120    let unreachable = Unreachable::consensus_decode_partial(cursor, &decoders)?;
121    let new_state = DummyStateMachineV1::OutputDone(
122        unreachable.amount,
123        unreachable.txid,
124        unreachable.operation_id,
125    );
126    let bytes = new_state.consensus_encode_to_vec();
127    Ok(Some((bytes, operation_id)))
128}
129
130/// [`AmountUnit`] was added to the state
131pub(crate) fn get_v2_migrated_state(
132    operation_id: OperationId,
133    cursor: &mut Cursor<&[u8]>,
134) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
135    let decoders = ModuleDecoderRegistry::default();
136    let dummy_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
137
138    let _state_length = u16::consensus_decode_partial(cursor, &decoders)?;
139
140    match dummy_sm_variant {
141        0 | 1 | 3 => {}
142        _ => {
143            return Ok(None);
144        }
145    }
146
147    let (amount, txid, op_id) =
148        <(Amount, TransactionId, OperationId)>::consensus_decode_partial(cursor, &decoders)?;
149
150    // TODO: should this be the case? Does not seem like it.
151    // debug_assert_eq!(operation_id, op_id);
152
153    let bytes = match dummy_sm_variant {
154        0 => DummyStateMachine::Input(amount, AmountUnit::BITCOIN, txid, op_id)
155            .consensus_encode_to_vec(),
156        1 => DummyStateMachine::Output(amount, AmountUnit::BITCOIN, txid, op_id)
157            .consensus_encode_to_vec(),
158        3 => DummyStateMachine::OutputDone(amount, AmountUnit::BITCOIN, txid, op_id)
159            .consensus_encode_to_vec(),
160        _ => unreachable!(),
161    };
162
163    debug_assert!(DummyStateMachine::consensus_decode_whole(&bytes, &decoders).is_ok());
164
165    Ok(Some((bytes, operation_id)))
166}
167
168#[derive(Debug)]
169struct Unreachable {
170    operation_id: OperationId,
171    txid: TransactionId,
172    amount: Amount,
173}
174
175impl Decodable for Unreachable {
176    fn consensus_decode_partial<R: std::io::Read>(
177        reader: &mut R,
178        modules: &ModuleDecoderRegistry,
179    ) -> Result<Self, fedimint_core::encoding::DecodeError> {
180        let operation_id = OperationId::consensus_decode_partial(reader, modules)?;
181        let txid = TransactionId::consensus_decode_partial(reader, modules)?;
182        let amount = Amount::consensus_decode_partial(reader, modules)?;
183
184        Ok(Unreachable {
185            operation_id,
186            txid,
187            amount,
188        })
189    }
190}