fedimint_wallet_server/
db.rs

1use bitcoin::secp256k1::ecdsa::Signature;
2use bitcoin::{BlockHash, OutPoint, TxOut, Txid};
3use fedimint_core::db::IDatabaseTransactionOpsCoreTyped;
4use fedimint_core::encoding::{Decodable, Encodable};
5use fedimint_core::module::ModuleConsensusVersion;
6use fedimint_core::{PeerId, impl_db_lookup, impl_db_record};
7use fedimint_server_core::migration::{
8    ModuleHistoryItem, ServerModuleDbMigrationFnContext, ServerModuleDbMigrationFnContextExt as _,
9};
10use futures::StreamExt;
11use serde::Serialize;
12use strum_macros::EnumIter;
13
14use crate::common::{RecoveryItem, WalletInput};
15use crate::{PendingTransaction, SpendableUTXO, UnsignedTransaction, Wallet, WalletOutputOutcome};
16
17#[repr(u8)]
18#[derive(Clone, EnumIter, Debug)]
19pub enum DbKeyPrefix {
20    BlockHash = 0x30,
21    Utxo = 0x31,
22    BlockCountVote = 0x32,
23    FeeRateVote = 0x33,
24    UnsignedTransaction = 0x34,
25    PendingTransaction = 0x35,
26    PegOutTxSigCi = 0x36,
27    PegOutBitcoinOutPoint = 0x37,
28    PegOutNonce = 0x38,
29    ClaimedPegInOutpoint = 0x39,
30    ConsensusVersionVote = 0x40,
31    UnspentTxOut = 0x41,
32    ConsensusVersionVotingActivation = 0x42,
33    // Note: this key was added in 0.8, and it is not guaranteed
34    // to be present for all past processed blocks, unless Federation
35    // was started with fedimint 0.8 or later
36    BlockHashByHeight = 0x43,
37    RecoveryItem = 0x44,
38}
39
40impl 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(Clone, Debug, Encodable, Decodable, Serialize)]
47pub struct BlockHashKey(pub BlockHash);
48
49#[derive(Clone, Debug, Encodable, Decodable)]
50pub struct BlockHashKeyPrefix;
51
52impl_db_record!(
53    key = BlockHashKey,
54    value = (),
55    db_prefix = DbKeyPrefix::BlockHash,
56);
57impl_db_lookup!(key = BlockHashKey, query_prefix = BlockHashKeyPrefix);
58
59/// Note: only added in 0.8 and not backfilled. See
60/// [`DbKeyPrefix::BlockHashByHeight`]
61#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
62pub struct BlockHashByHeightKey(pub u32);
63
64#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
65pub struct BlockHashByHeightValue(pub BlockHash);
66
67#[derive(Clone, Debug, Encodable, Decodable)]
68pub struct BlockHashByHeightKeyPrefix;
69
70impl_db_record!(
71    key = BlockHashByHeightKey,
72    value = BlockHashByHeightValue,
73    db_prefix = DbKeyPrefix::BlockHashByHeight,
74);
75impl_db_lookup!(
76    key = BlockHashByHeightKey,
77    query_prefix = BlockHashByHeightKeyPrefix
78);
79
80#[derive(Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize)]
81pub struct UTXOKey(pub bitcoin::OutPoint);
82
83#[derive(Clone, Debug, Encodable, Decodable)]
84pub struct UTXOPrefixKey;
85
86impl_db_record!(
87    key = UTXOKey,
88    value = SpendableUTXO,
89    db_prefix = DbKeyPrefix::Utxo,
90);
91impl_db_lookup!(key = UTXOKey, query_prefix = UTXOPrefixKey);
92
93#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
94pub struct UnsignedTransactionKey(pub Txid);
95
96#[derive(Clone, Debug, Encodable, Decodable)]
97pub struct UnsignedTransactionPrefixKey;
98
99impl_db_record!(
100    key = UnsignedTransactionKey,
101    value = UnsignedTransaction,
102    db_prefix = DbKeyPrefix::UnsignedTransaction,
103);
104impl_db_lookup!(
105    key = UnsignedTransactionKey,
106    query_prefix = UnsignedTransactionPrefixKey
107);
108
109#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
110pub struct PendingTransactionKey(pub Txid);
111
112#[derive(Clone, Debug, Encodable, Decodable)]
113pub struct PendingTransactionPrefixKey;
114
115impl_db_record!(
116    key = PendingTransactionKey,
117    value = PendingTransaction,
118    db_prefix = DbKeyPrefix::PendingTransaction,
119);
120impl_db_lookup!(
121    key = PendingTransactionKey,
122    query_prefix = PendingTransactionPrefixKey
123);
124
125#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
126pub struct PegOutTxSignatureCI(pub Txid);
127
128#[derive(Clone, Debug, Encodable, Decodable)]
129pub struct PegOutTxSignatureCIPrefix;
130
131impl_db_record!(
132    key = PegOutTxSignatureCI,
133    value = Vec<Signature>,
134    db_prefix = DbKeyPrefix::PegOutTxSigCi,
135);
136impl_db_lookup!(
137    key = PegOutTxSignatureCI,
138    query_prefix = PegOutTxSignatureCIPrefix
139);
140
141#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
142pub struct PegOutBitcoinTransaction(pub fedimint_core::OutPoint);
143
144#[derive(Clone, Debug, Encodable, Decodable)]
145pub struct PegOutBitcoinTransactionPrefix;
146
147impl_db_record!(
148    key = PegOutBitcoinTransaction,
149    value = WalletOutputOutcome,
150    db_prefix = DbKeyPrefix::PegOutBitcoinOutPoint,
151);
152
153impl_db_lookup!(
154    key = PegOutBitcoinTransaction,
155    query_prefix = PegOutBitcoinTransactionPrefix
156);
157
158#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
159pub struct BlockCountVoteKey(pub PeerId);
160
161#[derive(Clone, Debug, Encodable, Decodable)]
162pub struct BlockCountVotePrefix;
163
164impl_db_record!(
165    key = BlockCountVoteKey,
166    value = u32,
167    db_prefix = DbKeyPrefix::BlockCountVote
168);
169
170impl_db_lookup!(key = BlockCountVoteKey, query_prefix = BlockCountVotePrefix);
171
172#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
173pub struct FeeRateVoteKey(pub PeerId);
174
175#[derive(Clone, Debug, Encodable, Decodable)]
176pub struct FeeRateVotePrefix;
177
178impl_db_record!(
179    key = FeeRateVoteKey,
180    value = fedimint_core::Feerate,
181    db_prefix = DbKeyPrefix::FeeRateVote
182);
183
184impl_db_lookup!(key = FeeRateVoteKey, query_prefix = FeeRateVotePrefix);
185
186#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
187pub struct ConsensusVersionVoteKey(pub PeerId);
188
189#[derive(Clone, Debug, Encodable, Decodable)]
190pub struct ConsensusVersionVotePrefix;
191
192impl_db_record!(
193    key = ConsensusVersionVoteKey,
194    value = ModuleConsensusVersion,
195    db_prefix = DbKeyPrefix::ConsensusVersionVote
196);
197
198impl_db_lookup!(
199    key = ConsensusVersionVoteKey,
200    query_prefix = ConsensusVersionVotePrefix
201);
202
203#[derive(Clone, Debug, Encodable, Decodable)]
204pub struct PegOutNonceKey;
205
206impl_db_record!(
207    key = PegOutNonceKey,
208    value = u64,
209    db_prefix = DbKeyPrefix::PegOutNonce
210);
211
212#[derive(Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize)]
213pub struct ClaimedPegInOutpointKey(pub OutPoint);
214
215#[derive(Clone, Debug, Encodable, Decodable)]
216pub struct ClaimedPegInOutpointPrefixKey;
217
218impl_db_record!(
219    key = ClaimedPegInOutpointKey,
220    value = (),
221    db_prefix = DbKeyPrefix::ClaimedPegInOutpoint,
222);
223impl_db_lookup!(
224    key = ClaimedPegInOutpointKey,
225    query_prefix = ClaimedPegInOutpointPrefixKey
226);
227
228/// Migrate to v1, backfilling all previously pegged-in outpoints
229pub async fn migrate_to_v1(
230    mut ctx: ServerModuleDbMigrationFnContext<'_, Wallet>,
231) -> Result<(), anyhow::Error> {
232    let outpoints = ctx
233        .get_typed_module_history_stream()
234        .await
235        .filter_map(|item| async {
236            match item {
237                ModuleHistoryItem::Input(input) => {
238                    let outpoint = input
239                        .maybe_v0_ref()
240                        .expect("can only support V0 wallet inputs")
241                        .0
242                        .outpoint();
243
244                    Some(outpoint)
245                }
246                ModuleHistoryItem::Output(_, _) | ModuleHistoryItem::ConsensusItem(_) => None,
247            }
248        })
249        .collect::<Vec<_>>()
250        .await;
251
252    let mut dbtx = ctx.dbtx();
253    for outpoint in outpoints {
254        dbtx.insert_new_entry(&ClaimedPegInOutpointKey(outpoint), &())
255            .await;
256    }
257
258    Ok(())
259}
260
261#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
262pub struct UnspentTxOutKey(pub bitcoin::OutPoint);
263
264#[derive(Clone, Debug, Encodable, Decodable)]
265pub struct UnspentTxOutPrefix;
266
267impl_db_record!(
268    key = UnspentTxOutKey,
269    value = TxOut,
270    db_prefix = DbKeyPrefix::UnspentTxOut,
271);
272impl_db_lookup!(key = UnspentTxOutKey, query_prefix = UnspentTxOutPrefix);
273
274#[derive(Clone, Debug, Encodable, Decodable, Serialize)]
275pub struct ConsensusVersionVotingActivationKey;
276
277#[derive(Clone, Debug, Encodable, Decodable)]
278pub struct ConsensusVersionVotingActivationPrefix;
279
280impl_db_record!(
281    key = ConsensusVersionVotingActivationKey,
282    value = (),
283    db_prefix = DbKeyPrefix::ConsensusVersionVotingActivation,
284);
285impl_db_lookup!(
286    key = ConsensusVersionVotingActivationKey,
287    query_prefix = ConsensusVersionVotingActivationPrefix
288);
289
290#[derive(Debug, Clone, Copy, Encodable, Decodable, Serialize)]
291pub struct RecoveryItemKey(pub u64);
292
293#[derive(Debug, Encodable, Decodable)]
294pub struct RecoveryItemKeyPrefix;
295
296impl_db_record!(
297    key = RecoveryItemKey,
298    value = RecoveryItem,
299    db_prefix = DbKeyPrefix::RecoveryItem,
300);
301impl_db_lookup!(key = RecoveryItemKey, query_prefix = RecoveryItemKeyPrefix);
302
303/// Migrate to v2, backfilling recovery items from module history
304pub async fn migrate_to_v2(
305    mut ctx: ServerModuleDbMigrationFnContext<'_, Wallet>,
306) -> Result<(), anyhow::Error> {
307    let mut recovery_items = Vec::new();
308    let mut stream = ctx.get_typed_module_history_stream().await;
309
310    while let Some(history_item) = stream.next().await {
311        if let ModuleHistoryItem::Input(input) = history_item {
312            let (outpoint, script) = match &input {
313                WalletInput::V0(input) => {
314                    (input.0.outpoint(), input.tx_output().script_pubkey.clone())
315                }
316                WalletInput::V1(input) => (input.outpoint, input.tx_out.script_pubkey.clone()),
317                WalletInput::Default { .. } => continue,
318            };
319            recovery_items.push(RecoveryItem::Input { outpoint, script });
320        }
321    }
322
323    drop(stream);
324
325    for (index, item) in recovery_items.into_iter().enumerate() {
326        ctx.dbtx()
327            .insert_new_entry(&RecoveryItemKey(index as u64), &item)
328            .await;
329    }
330
331    Ok(())
332}