1use std::collections::BTreeMap;
2use std::iter::repeat_n;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, Result, format_err};
7use async_trait::async_trait;
8use bitcoin::absolute::LockTime;
9use bitcoin::block::{Header as BlockHeader, Version};
10use bitcoin::constants::genesis_block;
11use bitcoin::hash_types::Txid;
12use bitcoin::hashes::Hash;
13use bitcoin::merkle_tree::PartialMerkleTree;
14use bitcoin::{
15 Address, Block, BlockHash, CompactTarget, Network, OutPoint, ScriptBuf, Transaction, TxOut,
16};
17use fedimint_bitcoind::IBitcoindRpc;
18use fedimint_core::envs::BitcoinRpcConfig;
19use fedimint_core::task::sleep_in_test;
20use fedimint_core::txoproof::TxOutProof;
21use fedimint_core::util::SafeUrl;
22use fedimint_core::{Amount, Feerate};
23use fedimint_server_core::bitcoin_rpc::IServerBitcoinRpc;
24use rand::rngs::OsRng;
25use tracing::debug;
26
27use super::BitcoinTest;
28
29#[derive(Debug)]
30struct FakeBitcoinTestInner {
31 blocks: Vec<Block>,
33 pending: Vec<Transaction>,
35 addresses: BTreeMap<Txid, Amount>,
38 proofs: BTreeMap<Txid, TxOutProof>,
40 scripts: BTreeMap<ScriptBuf, Vec<Transaction>>,
42 txid_to_block_height: BTreeMap<Txid, usize>,
44}
45
46#[derive(Clone, Debug)]
47pub struct FakeBitcoinTest {
48 inner: Arc<std::sync::RwLock<FakeBitcoinTestInner>>,
49}
50
51impl Default for FakeBitcoinTest {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl FakeBitcoinTest {
58 pub fn new() -> Self {
59 let inner = FakeBitcoinTestInner {
60 blocks: vec![genesis_block(Network::Regtest)],
61 pending: vec![],
62 addresses: BTreeMap::new(),
63 proofs: BTreeMap::new(),
64 scripts: BTreeMap::new(),
65 txid_to_block_height: BTreeMap::new(),
66 };
67 FakeBitcoinTest {
68 inner: std::sync::RwLock::new(inner).into(),
69 }
70 }
71
72 fn pending_merkle_tree(pending: &[Transaction]) -> PartialMerkleTree {
73 let txs = pending
74 .iter()
75 .map(Transaction::compute_txid)
76 .collect::<Vec<Txid>>();
77 let matches = repeat_n(true, txs.len()).collect::<Vec<bool>>();
78 PartialMerkleTree::from_txids(txs.as_slice(), matches.as_slice())
79 }
80
81 fn new_transaction(out: Vec<TxOut>, nonce: u32) -> Transaction {
86 Transaction {
87 version: bitcoin::transaction::Version(0),
88 lock_time: LockTime::from_height(nonce).unwrap(),
89 input: vec![],
90 output: out,
91 }
92 }
93
94 fn mine_block(
95 addresses: &mut BTreeMap<Txid, Amount>,
96 blocks: &mut Vec<Block>,
97 pending: &mut Vec<Transaction>,
98 txid_to_block_height: &mut BTreeMap<Txid, usize>,
99 ) -> bitcoin::BlockHash {
100 debug!(
101 "Mining block: {} transactions, {} blocks",
102 pending.len(),
103 blocks.len()
104 );
105 let root = BlockHash::hash(&[0]);
106 let block_height = blocks.len();
109 for tx in pending.iter() {
110 addresses.insert(tx.compute_txid(), Amount::from_sats(output_sum(tx)));
111 txid_to_block_height.insert(tx.compute_txid(), block_height);
112 }
113 if pending.is_empty() {
115 pending.push(Self::new_transaction(vec![], blocks.len() as u32));
116 }
117 let merkle_root = Self::pending_merkle_tree(pending)
118 .extract_matches(&mut vec![], &mut vec![])
119 .unwrap();
120 let block = Block {
121 header: BlockHeader {
122 version: Version::from_consensus(0),
123 prev_blockhash: blocks.last().map_or(root, |b| b.header.block_hash()),
124 merkle_root,
125 time: 0,
126 bits: CompactTarget::from_consensus(0),
127 nonce: 0,
128 },
129 txdata: pending.clone(),
130 };
131 pending.clear();
132 blocks.push(block.clone());
133 block.block_hash()
134 }
135}
136
137#[async_trait]
138impl BitcoinTest for FakeBitcoinTest {
139 async fn lock_exclusive(&self) -> Box<dyn BitcoinTest + Send + Sync> {
140 Box::new(self.clone())
143 }
144
145 async fn mine_blocks(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
146 let mut inner = self.inner.write().unwrap();
147
148 let FakeBitcoinTestInner {
149 ref mut blocks,
150 ref mut pending,
151 ref mut addresses,
152 ref mut txid_to_block_height,
153 ..
154 } = *inner;
155
156 (1..=block_num)
157 .map(|_| FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height))
158 .collect()
159 }
160
161 async fn prepare_funding_wallet(&self) {
162 let block_count = self.inner.write().unwrap().blocks.len() as u64;
165 if block_count < 100 {
166 self.mine_blocks(100 - block_count).await;
167 }
168 }
169
170 async fn send_and_mine_block(
171 &self,
172 address: &Address,
173 amount: bitcoin::Amount,
174 ) -> (TxOutProof, Transaction) {
175 let mut inner = self.inner.write().unwrap();
176
177 let transaction = FakeBitcoinTest::new_transaction(
178 vec![TxOut {
179 value: amount,
180 script_pubkey: address.script_pubkey(),
181 }],
182 inner.blocks.len() as u32,
183 );
184 inner
185 .addresses
186 .insert(transaction.compute_txid(), amount.into());
187
188 inner.pending.push(transaction.clone());
189 let merkle_proof = FakeBitcoinTest::pending_merkle_tree(&inner.pending);
190
191 let FakeBitcoinTestInner {
192 ref mut blocks,
193 ref mut pending,
194 ref mut addresses,
195 ref mut txid_to_block_height,
196 ..
197 } = *inner;
198 FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height);
199 let block_header = inner.blocks.last().unwrap().header;
200 let proof = TxOutProof {
201 block_header,
202 merkle_proof,
203 };
204 inner
205 .proofs
206 .insert(transaction.compute_txid(), proof.clone());
207 inner
208 .scripts
209 .insert(address.script_pubkey(), vec![transaction.clone()]);
210
211 (proof, transaction)
212 }
213
214 async fn get_new_address(&self) -> Address {
215 let ctx = bitcoin::secp256k1::Secp256k1::new();
216 let (_, public_key) = ctx.generate_keypair(&mut OsRng);
217
218 Address::p2wpkh(&bitcoin::CompressedPublicKey(public_key), Network::Regtest)
219 }
220
221 async fn mine_block_and_get_received(&self, address: &Address) -> Amount {
222 self.mine_blocks(1).await;
223 let sats = self
224 .inner
225 .read()
226 .unwrap()
227 .blocks
228 .iter()
229 .flat_map(|block| block.txdata.iter().flat_map(|tx| tx.output.clone()))
230 .find(|out| out.script_pubkey == address.script_pubkey())
231 .map_or(0, |tx| tx.value.to_sat());
232 Amount::from_sats(sats)
233 }
234
235 async fn get_mempool_tx_fee(&self, txid: &Txid) -> Amount {
236 loop {
237 let (pending, addresses) = {
238 let inner = self.inner.read().unwrap();
239 (inner.pending.clone(), inner.addresses.clone())
240 };
241
242 let mut fee = Amount::ZERO;
243 let maybe_tx = pending.iter().find(|tx| tx.compute_txid() == *txid);
244
245 let tx = match maybe_tx {
246 None => {
247 sleep_in_test("no transaction found", Duration::from_millis(100)).await;
248 continue;
249 }
250 Some(tx) => tx,
251 };
252
253 for input in &tx.input {
254 fee += *addresses
255 .get(&input.previous_output.txid)
256 .expect("previous transaction should be known");
257 }
258
259 for output in &tx.output {
260 fee -= output.value.into();
261 }
262
263 return fee;
264 }
265 }
266
267 async fn get_tx_block_height(&self, txid: &Txid) -> Option<u64> {
268 self.inner
269 .read()
270 .expect("RwLock poisoned")
271 .txid_to_block_height
272 .get(txid)
273 .map(|height| height.to_owned() as u64)
274 }
275
276 async fn get_block_count(&self) -> u64 {
277 self.inner.read().expect("RwLock poisoned").blocks.len() as u64
278 }
279
280 async fn get_mempool_tx(&self, txid: &Txid) -> Option<bitcoin::Transaction> {
281 let inner = self.inner.read().unwrap();
282 let mempool_transactions = inner.pending.clone();
283 mempool_transactions
284 .iter()
285 .find(|tx| tx.compute_txid() == *txid)
286 .map(std::borrow::ToOwned::to_owned)
287 }
288}
289
290#[async_trait]
291impl IBitcoindRpc for FakeBitcoinTest {
292 async fn get_tx_block_height(&self, txid: &bitcoin::Txid) -> Result<Option<u64>> {
293 for (height, block) in self.inner.read().unwrap().blocks.iter().enumerate() {
294 if block.txdata.iter().any(|tx| &tx.compute_txid() == txid) {
295 return Ok(Some(height as u64));
296 }
297 }
298 Ok(None)
299 }
300
301 async fn get_script_history(
302 &self,
303 script: &bitcoin::ScriptBuf,
304 ) -> Result<Vec<bitcoin::Transaction>> {
305 let inner = self.inner.read().unwrap();
306 Ok(inner.scripts.get(script).cloned().unwrap_or_default())
307 }
308
309 async fn get_txout_proof(&self, txid: bitcoin::Txid) -> Result<TxOutProof> {
310 let inner = self.inner.read().unwrap();
311 let proof = inner.proofs.get(&txid);
312 Ok(proof.ok_or(format_err!("No proof stored"))?.clone())
313 }
314}
315
316fn output_sum(tx: &Transaction) -> u64 {
317 tx.output.iter().map(|output| output.value.to_sat()).sum()
318}
319
320fn inputs(tx: &Transaction) -> Vec<OutPoint> {
321 tx.input.iter().map(|input| input.previous_output).collect()
322}
323
324#[async_trait::async_trait]
325impl IServerBitcoinRpc for FakeBitcoinTest {
326 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
327 BitcoinRpcConfig {
328 kind: "mock_kind".to_string(),
329 url: "http://mock".parse().unwrap(),
330 }
331 }
332
333 fn get_url(&self) -> SafeUrl {
334 "http://mock".parse().unwrap()
335 }
336
337 async fn get_network(&self) -> Result<bitcoin::Network> {
338 Ok(bitcoin::Network::Regtest)
339 }
340
341 async fn get_block_count(&self) -> Result<u64> {
342 Ok(self.inner.read().unwrap().blocks.len() as u64)
343 }
344
345 async fn get_block_hash(&self, height: u64) -> Result<bitcoin::BlockHash> {
346 self.inner
347 .read()
348 .unwrap()
349 .blocks
350 .get(height as usize)
351 .map(|block| block.header.block_hash())
352 .context("No block with that height found")
353 }
354
355 async fn get_block(&self, block_hash: &bitcoin::BlockHash) -> Result<bitcoin::Block> {
356 self.inner
357 .read()
358 .unwrap()
359 .blocks
360 .iter()
361 .find(|block| block.header.block_hash() == *block_hash)
362 .context("No block with that hash found")
363 .cloned()
364 }
365
366 async fn get_feerate(&self) -> Result<Option<Feerate>> {
367 Ok(Some(Feerate { sats_per_kvb: 2000 }))
368 }
369
370 async fn submit_transaction(&self, transaction: bitcoin::Transaction) {
371 let mut inner = self.inner.write().unwrap();
372 inner.pending.push(transaction);
373
374 let mut filtered = BTreeMap::<Vec<OutPoint>, bitcoin::Transaction>::new();
375
376 for tx in &inner.pending {
381 match filtered.get(&inputs(tx)) {
382 Some(found) if output_sum(tx) > output_sum(found) => {}
383 _ => {
384 filtered.insert(inputs(tx), tx.clone());
385 }
386 }
387 }
388
389 inner.pending = filtered.into_values().collect();
390 }
391
392 async fn get_sync_percentage(&self) -> anyhow::Result<Option<f64>> {
393 Ok(None)
394 }
395}