Skip to main content

fedimint_wallet_client/
client_db.rs

1use core::fmt;
2use std::ops;
3use std::str::FromStr;
4use std::time::SystemTime;
5
6use fedimint_client_module::module::init::recovery::RecoveryFromHistoryCommon;
7use fedimint_core::core::OperationId;
8use fedimint_core::encoding::{
9    Decodable, DecodeError, Encodable, decode_field_from_finite_reader,
10    decode_legacy_option_system_time_from_finite_reader,
11    decode_legacy_system_time_from_finite_reader, encode_legacy_option_system_time,
12    encode_legacy_system_time, with_decoding_context,
13};
14use fedimint_core::module::registry::ModuleDecoderRegistry;
15use fedimint_core::{TransactionId, impl_db_lookup, impl_db_record};
16use serde::{Deserialize, Serialize};
17use strum_macros::EnumIter;
18
19use crate::backup::WalletRecoveryState;
20
21#[derive(Clone, EnumIter, Debug)]
22pub enum DbKeyPrefix {
23    NextPegInTweakIndex = 0x2c,
24    PegInTweakIndex = 0x2d,
25    ClaimedPegIn = 0x2e,
26    RecoveryFinalized = 0x2f,
27    RecoveryState = 0x30,
28    SupportsSafeDeposit = 0x31,
29    PegInPoolCursor = 0x32,
30    /// Prefixes between 0xb0..=0xcf shall all be considered allocated for
31    /// historical and future external use
32    ExternalReservedStart = 0xb0,
33    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
34    /// historical and future internal use
35    CoreInternalReservedStart = 0xd0,
36    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
37    /// historical and future internal use
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/// An index of a deposit address
48///
49/// Under the hood it's similar to `ChildId`, but in a wallet module
50/// it's used often enough to deserve own newtype.
51#[derive(
52    Copy,
53    Clone,
54    Debug,
55    Encodable,
56    Decodable,
57    Serialize,
58    Deserialize,
59    Default,
60    PartialEq,
61    Eq,
62    PartialOrd,
63    Ord,
64)]
65pub struct TweakIdx(pub u64);
66
67impl fmt::Display for TweakIdx {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.write_fmt(format_args!("TweakIdx({})", self.0))
70    }
71}
72
73impl FromStr for TweakIdx {
74    type Err = <u64 as FromStr>::Err;
75
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        Ok(Self(FromStr::from_str(s)?))
78    }
79}
80
81impl TweakIdx {
82    #[must_use]
83    pub fn next(self) -> Self {
84        Self(self.0 + 1)
85    }
86
87    #[must_use]
88    pub fn prev(self) -> Option<Self> {
89        self.0.checked_sub(1).map(Self)
90    }
91
92    #[must_use]
93    pub fn advance(self, i: u64) -> Self {
94        Self(self.0 + i)
95    }
96
97    pub fn saturating_sub(&self, rhs: TweakIdx) -> u64 {
98        self.0.saturating_sub(rhs.0)
99    }
100}
101
102impl ops::Sub for TweakIdx {
103    type Output = u64;
104
105    fn sub(self, rhs: Self) -> Self::Output {
106        self.0 - rhs.0
107    }
108}
109
110/// A counter tracking next index to use to derive a peg-in address
111#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
112pub struct NextPegInTweakIndexKey;
113
114impl_db_record!(
115    key = NextPegInTweakIndexKey,
116    value = TweakIdx,
117    db_prefix = DbKeyPrefix::NextPegInTweakIndex,
118);
119
120/// Peg in index that was already allocated and is being tracked for deposits to
121/// claim
122#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
123pub struct PegInTweakIndexKey(pub TweakIdx);
124
125#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
126pub struct PegInTweakIndexPrefix;
127
128#[derive(Clone, Debug, Serialize)]
129pub struct PegInTweakIndexData {
130    /// [`OperationId`] corresponding to this peg-in address.
131    pub operation_id: OperationId,
132    /// Time the address was allocated (created)
133    pub creation_time: SystemTime,
134    /// Last time the client checked the address for pegins
135    pub last_check_time: Option<SystemTime>,
136    /// Next time client is going to checked the address for pegins
137    pub next_check_time: Option<SystemTime>,
138    /// All previous on chain outputs claimed for this peg-in address.
139    pub claimed: Vec<bitcoin::OutPoint>,
140}
141
142impl Encodable for PegInTweakIndexData {
143    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
144        self.operation_id.consensus_encode(writer)?;
145        encode_legacy_system_time(&self.creation_time, writer)?;
146        encode_legacy_option_system_time(&self.last_check_time, writer)?;
147        encode_legacy_option_system_time(&self.next_check_time, writer)?;
148        self.claimed.consensus_encode(writer)
149    }
150}
151
152impl Decodable for PegInTweakIndexData {
153    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
154        decoder: &mut D,
155        modules: &ModuleDecoderRegistry,
156    ) -> Result<Self, DecodeError> {
157        Ok(Self {
158            operation_id: decode_field_from_finite_reader(
159                decoder,
160                modules,
161                "Decoding named block field: PegInTweakIndexData{ ... operation_id ... }",
162            )?,
163            creation_time: with_decoding_context(
164                decode_legacy_system_time_from_finite_reader(decoder, modules),
165                "Decoding named block field: PegInTweakIndexData{ ... creation_time ... }",
166            )?,
167            last_check_time: with_decoding_context(
168                decode_legacy_option_system_time_from_finite_reader(decoder, modules),
169                "Decoding named block field: PegInTweakIndexData{ ... last_check_time ... }",
170            )?,
171            next_check_time: with_decoding_context(
172                decode_legacy_option_system_time_from_finite_reader(decoder, modules),
173                "Decoding named block field: PegInTweakIndexData{ ... next_check_time ... }",
174            )?,
175            claimed: decode_field_from_finite_reader(
176                decoder,
177                modules,
178                "Decoding named block field: PegInTweakIndexData{ ... claimed ... }",
179            )?,
180        })
181    }
182}
183
184impl_db_record!(
185    key = PegInTweakIndexKey,
186    value = PegInTweakIndexData,
187    db_prefix = DbKeyPrefix::PegInTweakIndex,
188);
189
190impl_db_lookup!(
191    key = PegInTweakIndexKey,
192    query_prefix = PegInTweakIndexPrefix
193);
194
195#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
196pub struct ClaimedPegInKey {
197    pub peg_in_index: TweakIdx,
198    pub btc_out_point: bitcoin::OutPoint,
199}
200
201#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
202pub struct ClaimedPegInPrefix;
203
204#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
205pub struct ClaimedPegInData {
206    /// The Fedimint transaction id of the claim transaction. If there was no
207    /// claim transaction due to the deposit being smaller than the deposit fee
208    /// there will be no claim transaction and the transaction id will be all
209    /// zeros.
210    pub claim_txid: TransactionId,
211    pub change: Vec<fedimint_core::OutPoint>,
212}
213
214impl_db_record!(
215    key = ClaimedPegInKey,
216    value = ClaimedPegInData,
217    db_prefix = DbKeyPrefix::ClaimedPegIn,
218    notify_on_modify = true,
219);
220impl_db_lookup!(key = ClaimedPegInKey, query_prefix = ClaimedPegInPrefix);
221
222#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
223pub struct RecoveryFinalizedKey;
224
225#[derive(Debug, Clone, Encodable, Decodable)]
226pub struct RecoveryFinalizedKeyPrefix;
227
228impl_db_record!(
229    key = RecoveryFinalizedKey,
230    value = bool,
231    db_prefix = DbKeyPrefix::RecoveryFinalized,
232);
233
234#[derive(Debug, Clone, Encodable, Decodable, Serialize)]
235pub struct RecoveryStateKey;
236
237#[derive(Debug, Clone, Encodable, Decodable)]
238pub struct RestoreStateKeyPrefix;
239
240impl_db_record!(
241    key = RecoveryStateKey,
242    value = (WalletRecoveryState, RecoveryFromHistoryCommon),
243    db_prefix = DbKeyPrefix::RecoveryState,
244);
245
246#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
247pub struct SupportsSafeDepositKey;
248
249#[derive(Clone, Debug, Encodable, Decodable)]
250pub struct SupportsSafeDepositPrefix;
251
252impl_db_record!(
253    key = SupportsSafeDepositKey,
254    value = (),
255    db_prefix = DbKeyPrefix::SupportsSafeDeposit,
256);
257
258impl_db_lookup!(
259    key = SupportsSafeDepositKey,
260    query_prefix = SupportsSafeDepositPrefix
261);
262
263/// Round-robin cursor for
264/// [`crate::WalletClientModule::allocate_deposit_address_pooled`].
265///
266/// Stores the [`TweakIdx`] of the most recently reused unused address. The
267/// next reuse picks the smallest unused tweak with `tweak_idx > cursor`,
268/// wrapping back to the smallest unused tweak when none exists.
269#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
270pub struct PegInPoolCursorKey;
271
272impl_db_record!(
273    key = PegInPoolCursorKey,
274    value = TweakIdx,
275    db_prefix = DbKeyPrefix::PegInPoolCursor,
276);