|
| 1 | +use crate::manager::MempoolTransactionResult; |
| 2 | +use crate::manager::{BlockProcessingResult, WalletEvent, WalletInterface}; |
| 3 | +use crate::transaction_checking::TransactionContext; |
| 4 | +use dashcore::address::NetworkUnchecked; |
| 5 | +use dashcore::prelude::CoreBlockHeight; |
| 6 | +use dashcore::{Address, Block, OutPoint, Transaction, Txid}; |
| 7 | +use std::collections::BTreeMap; |
| 8 | +use std::str::FromStr; |
| 9 | +use std::sync::Arc; |
| 10 | +use tokio::sync::{broadcast, Mutex}; |
| 11 | + |
| 12 | +// Type alias for transaction effects map |
| 13 | +type TransactionEffectsMap = Arc<Mutex<BTreeMap<Txid, (i64, Vec<String>)>>>; |
| 14 | + |
| 15 | +pub struct MockWallet { |
| 16 | + processed_blocks: Arc<Mutex<Vec<(dashcore::BlockHash, u32)>>>, |
| 17 | + processed_transactions: Arc<Mutex<Vec<dashcore::Txid>>>, |
| 18 | + // Map txid -> (net_amount, addresses) |
| 19 | + effects: TransactionEffectsMap, |
| 20 | + synced_height: CoreBlockHeight, |
| 21 | + event_sender: broadcast::Sender<WalletEvent>, |
| 22 | + /// When true, process_mempool_transaction returns is_relevant=true. |
| 23 | + mempool_relevant: bool, |
| 24 | + /// Addresses returned by monitored_addresses. |
| 25 | + addresses: Vec<Address>, |
| 26 | + /// Outpoints returned by watched_outpoints. |
| 27 | + outpoints: Vec<OutPoint>, |
| 28 | + /// New addresses returned by process_mempool_transaction. |
| 29 | + mempool_new_addresses: Vec<Address>, |
| 30 | + /// Recorded status change notifications for test assertions. |
| 31 | + status_changes: Arc<Mutex<Vec<(Txid, TransactionContext)>>>, |
| 32 | + /// Monitor revision counter for staleness detection. |
| 33 | + monitor_revision: u64, |
| 34 | +} |
| 35 | + |
| 36 | +impl Default for MockWallet { |
| 37 | + fn default() -> Self { |
| 38 | + Self::new() |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +impl MockWallet { |
| 43 | + pub fn new() -> Self { |
| 44 | + let (event_sender, _) = broadcast::channel(16); |
| 45 | + Self { |
| 46 | + processed_blocks: Arc::new(Mutex::new(Vec::new())), |
| 47 | + processed_transactions: Arc::new(Mutex::new(Vec::new())), |
| 48 | + effects: Arc::new(Mutex::new(BTreeMap::new())), |
| 49 | + synced_height: 0, |
| 50 | + event_sender, |
| 51 | + mempool_relevant: false, |
| 52 | + addresses: Vec::new(), |
| 53 | + outpoints: Vec::new(), |
| 54 | + mempool_new_addresses: Vec::new(), |
| 55 | + status_changes: Arc::new(Mutex::new(Vec::new())), |
| 56 | + monitor_revision: 0, |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + /// Configure whether mempool transactions are reported as relevant. |
| 61 | + pub fn set_mempool_relevant(&mut self, relevant: bool) { |
| 62 | + self.mempool_relevant = relevant; |
| 63 | + } |
| 64 | + |
| 65 | + /// Set the addresses returned by monitored_addresses. |
| 66 | + pub fn set_addresses(&mut self, addresses: Vec<Address>) { |
| 67 | + self.addresses = addresses; |
| 68 | + self.monitor_revision += 1; |
| 69 | + } |
| 70 | + |
| 71 | + /// Set the outpoints returned by watched_outpoints. |
| 72 | + pub fn set_outpoints(&mut self, outpoints: Vec<OutPoint>) { |
| 73 | + self.outpoints = outpoints; |
| 74 | + self.monitor_revision += 1; |
| 75 | + } |
| 76 | + |
| 77 | + /// Set new addresses returned by process_mempool_transaction. |
| 78 | + pub fn set_mempool_new_addresses(&mut self, addresses: Vec<Address>) { |
| 79 | + self.mempool_new_addresses = addresses; |
| 80 | + } |
| 81 | + |
| 82 | + pub fn status_changes(&self) -> Arc<Mutex<Vec<(Txid, TransactionContext)>>> { |
| 83 | + self.status_changes.clone() |
| 84 | + } |
| 85 | + |
| 86 | + pub async fn set_effect(&self, txid: dashcore::Txid, net: i64, addresses: Vec<String>) { |
| 87 | + let mut map = self.effects.lock().await; |
| 88 | + map.insert(txid, (net, addresses)); |
| 89 | + } |
| 90 | + |
| 91 | + pub fn processed_blocks(&self) -> Arc<Mutex<Vec<(dashcore::BlockHash, u32)>>> { |
| 92 | + self.processed_blocks.clone() |
| 93 | + } |
| 94 | + |
| 95 | + pub fn processed_transactions(&self) -> Arc<Mutex<Vec<dashcore::Txid>>> { |
| 96 | + self.processed_transactions.clone() |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +#[async_trait::async_trait] |
| 101 | +impl WalletInterface for MockWallet { |
| 102 | + async fn process_block(&mut self, block: &Block, height: u32) -> BlockProcessingResult { |
| 103 | + let mut processed = self.processed_blocks.lock().await; |
| 104 | + processed.push((block.block_hash(), height)); |
| 105 | + |
| 106 | + BlockProcessingResult { |
| 107 | + new_txids: block.txdata.iter().map(|tx| tx.txid()).collect(), |
| 108 | + existing_txids: Vec::new(), |
| 109 | + new_addresses: Vec::new(), |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + async fn process_mempool_transaction( |
| 114 | + &mut self, |
| 115 | + tx: &Transaction, |
| 116 | + _is_instant_send: bool, |
| 117 | + ) -> MempoolTransactionResult { |
| 118 | + let mut processed = self.processed_transactions.lock().await; |
| 119 | + processed.push(tx.txid()); |
| 120 | + |
| 121 | + if !self.mempool_relevant { |
| 122 | + return MempoolTransactionResult::default(); |
| 123 | + } |
| 124 | + |
| 125 | + let effects = self.effects.lock().await; |
| 126 | + let (net_amount, addresses) = if let Some((net, addr_strs)) = effects.get(&tx.txid()) { |
| 127 | + let addrs = addr_strs |
| 128 | + .iter() |
| 129 | + .filter_map(|s| { |
| 130 | + Address::<NetworkUnchecked>::from_str(s).ok().map(|a| a.assume_checked()) |
| 131 | + }) |
| 132 | + .collect(); |
| 133 | + (*net, addrs) |
| 134 | + } else { |
| 135 | + (0, Vec::new()) |
| 136 | + }; |
| 137 | + |
| 138 | + MempoolTransactionResult { |
| 139 | + is_relevant: true, |
| 140 | + net_amount, |
| 141 | + is_outgoing: net_amount < 0, |
| 142 | + addresses, |
| 143 | + new_addresses: self.mempool_new_addresses.clone(), |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + async fn describe(&self) -> String { |
| 148 | + "MockWallet (test implementation)".to_string() |
| 149 | + } |
| 150 | + |
| 151 | + async fn transaction_effect(&self, tx: &Transaction) -> Option<(i64, Vec<String>)> { |
| 152 | + let map = self.effects.lock().await; |
| 153 | + map.get(&tx.txid()).cloned() |
| 154 | + } |
| 155 | + |
| 156 | + fn monitored_addresses(&self) -> Vec<Address> { |
| 157 | + self.addresses.clone() |
| 158 | + } |
| 159 | + |
| 160 | + fn watched_outpoints(&self) -> Vec<OutPoint> { |
| 161 | + self.outpoints.clone() |
| 162 | + } |
| 163 | + |
| 164 | + fn synced_height(&self) -> CoreBlockHeight { |
| 165 | + self.synced_height |
| 166 | + } |
| 167 | + |
| 168 | + fn update_synced_height(&mut self, height: CoreBlockHeight) { |
| 169 | + self.synced_height = height; |
| 170 | + } |
| 171 | + |
| 172 | + fn monitor_revision(&self) -> u64 { |
| 173 | + self.monitor_revision |
| 174 | + } |
| 175 | + |
| 176 | + fn subscribe_events(&self) -> broadcast::Receiver<WalletEvent> { |
| 177 | + self.event_sender.subscribe() |
| 178 | + } |
| 179 | + |
| 180 | + fn process_instant_send_lock(&mut self, txid: Txid) { |
| 181 | + let mut changes = |
| 182 | + self.status_changes.try_lock().expect("status_changes lock contention in test helper"); |
| 183 | + changes.push((txid, TransactionContext::InstantSend)); |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +/// Mock wallet that returns false for filter checks |
| 188 | +pub struct NonMatchingMockWallet { |
| 189 | + synced_height: CoreBlockHeight, |
| 190 | + event_sender: broadcast::Sender<WalletEvent>, |
| 191 | +} |
| 192 | + |
| 193 | +impl Default for NonMatchingMockWallet { |
| 194 | + fn default() -> Self { |
| 195 | + Self::new() |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +impl NonMatchingMockWallet { |
| 200 | + pub fn new() -> Self { |
| 201 | + let (event_sender, _) = broadcast::channel(16); |
| 202 | + Self { |
| 203 | + synced_height: 0, |
| 204 | + event_sender, |
| 205 | + } |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +#[async_trait::async_trait] |
| 210 | +impl WalletInterface for NonMatchingMockWallet { |
| 211 | + async fn process_block(&mut self, _block: &Block, _height: u32) -> BlockProcessingResult { |
| 212 | + BlockProcessingResult::default() |
| 213 | + } |
| 214 | + |
| 215 | + async fn process_mempool_transaction( |
| 216 | + &mut self, |
| 217 | + _tx: &Transaction, |
| 218 | + _is_instant_send: bool, |
| 219 | + ) -> MempoolTransactionResult { |
| 220 | + MempoolTransactionResult::default() |
| 221 | + } |
| 222 | + |
| 223 | + fn monitored_addresses(&self) -> Vec<Address> { |
| 224 | + Vec::new() |
| 225 | + } |
| 226 | + |
| 227 | + fn watched_outpoints(&self) -> Vec<OutPoint> { |
| 228 | + Vec::new() |
| 229 | + } |
| 230 | + |
| 231 | + fn synced_height(&self) -> CoreBlockHeight { |
| 232 | + self.synced_height |
| 233 | + } |
| 234 | + |
| 235 | + fn update_synced_height(&mut self, height: CoreBlockHeight) { |
| 236 | + self.synced_height = height; |
| 237 | + } |
| 238 | + |
| 239 | + fn subscribe_events(&self) -> broadcast::Receiver<WalletEvent> { |
| 240 | + self.event_sender.subscribe() |
| 241 | + } |
| 242 | + |
| 243 | + async fn describe(&self) -> String { |
| 244 | + "NonMatchingWallet (test implementation)".to_string() |
| 245 | + } |
| 246 | +} |
0 commit comments