fedimint_wallet_client/backup/
recovery_history_tracker.rs1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2
3use fedimint_core::encoding::{Decodable, Encodable};
4use fedimint_logging::LOG_CLIENT_MODULE_WALLET;
5use tracing::debug;
6
7use crate::WalletClientModuleData;
8use crate::backup::FEDERATION_RECOVER_MAX_GAP;
9use crate::client_db::TweakIdx;
10
11#[derive(Clone, Debug, Encodable, Decodable)]
24pub struct ConsensusPegInTweakIdxesUsedTracker {
25 used_tweak_idxes: BTreeSet<TweakIdx>,
29 pending_pubkey_scripts: BTreeMap<bitcoin::ScriptBuf, TweakIdx>,
32 next_pending_tweak_idx: TweakIdx,
34
35 decoys: VecDeque<bitcoin::ScriptBuf>,
38 decoy_session_threshold: u64,
41}
42
43impl ConsensusPegInTweakIdxesUsedTracker {
44 pub(crate) fn new(
45 previous_next_unused_idx: TweakIdx,
46 start_session_idx: u64,
47 current_session_count: u64,
48 data: &WalletClientModuleData,
49 ) -> Self {
50 debug_assert!(start_session_idx <= current_session_count);
51
52 let mut s = Self {
53 next_pending_tweak_idx: previous_next_unused_idx,
54 pending_pubkey_scripts: BTreeMap::new(),
55 decoys: VecDeque::new(),
56 decoy_session_threshold: current_session_count
57 .saturating_sub((current_session_count.saturating_sub(current_session_count)) / 20),
58 used_tweak_idxes: BTreeSet::new(),
59 };
60
61 s.init(data);
62
63 s
64 }
65
66 fn init(&mut self, data: &WalletClientModuleData) {
67 for _ in 0..super::ONCHAIN_RECOVER_MAX_GAP {
68 self.generate_next_pending_tweak_idx(data);
69 }
70 debug_assert_eq!(
71 self.pending_pubkey_scripts.len(),
72 super::ONCHAIN_RECOVER_MAX_GAP as usize
73 );
74 }
75
76 pub fn used_tweak_idxes(&self) -> &BTreeSet<TweakIdx> {
77 &self.used_tweak_idxes
78 }
79
80 fn generate_next_pending_tweak_idx(&mut self, data: &WalletClientModuleData) {
81 let (script, _address, _tweak_key, _operation_id) =
82 data.derive_peg_in_script(self.next_pending_tweak_idx);
83
84 self.pending_pubkey_scripts
85 .insert(script, self.next_pending_tweak_idx);
86 self.next_pending_tweak_idx = self.next_pending_tweak_idx.next();
87 }
88
89 fn refill_pending_pool_up_to_tweak_idx(
90 &mut self,
91 data: &WalletClientModuleData,
92 tweak_idx: TweakIdx,
93 ) {
94 while self.next_pending_tweak_idx < tweak_idx {
95 self.generate_next_pending_tweak_idx(data);
96 }
97 }
98
99 pub(crate) fn handle_script(
100 &mut self,
101 data: &WalletClientModuleData,
102 script: &bitcoin::ScriptBuf,
103 session_idx: u64,
104 ) {
105 if let Some(tweak_idx) = self.pending_pubkey_scripts.get(script).copied() {
106 debug!(target: LOG_CLIENT_MODULE_WALLET, %session_idx, ?tweak_idx, "Found previously used tweak_idx in federation history");
107
108 self.used_tweak_idxes.insert(tweak_idx);
109
110 self.refill_pending_pool_up_to_tweak_idx(
111 data,
112 tweak_idx.advance(FEDERATION_RECOVER_MAX_GAP),
113 );
114 } else if self.decoy_session_threshold < session_idx {
115 self.push_decoy(script);
116 }
117 }
118
119 fn push_decoy(&mut self, script: &bitcoin::ScriptBuf) {
121 self.decoys.push_front(script.clone());
122 if 50 < self.decoys.len() {
123 self.decoys.pop_back();
124 }
125 }
126
127 pub(crate) fn pop_decoy(&mut self) -> Option<bitcoin::ScriptBuf> {
129 self.decoys.pop_front()
130 }
131}