1use std::collections::BTreeMap;
2use std::iter::repeat_n;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, Result, bail};
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::{BitcoinRpcError, BlockchainInfo, 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, ChainId, 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 fail_mempool_tx_fetches: bool,
50 mempool_tx_fetch_delay: Duration,
53}
54
55#[derive(Clone, Debug)]
56pub struct FakeBitcoinTest {
57 inner: Arc<std::sync::RwLock<FakeBitcoinTestInner>>,
58}
59
60impl Default for FakeBitcoinTest {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl FakeBitcoinTest {
67 pub fn new() -> Self {
68 let inner = FakeBitcoinTestInner {
69 blocks: vec![genesis_block(Network::Regtest)],
70 pending: vec![],
71 addresses: BTreeMap::new(),
72 proofs: BTreeMap::new(),
73 scripts: BTreeMap::new(),
74 txid_to_block_height: BTreeMap::new(),
75 fail_mempool_tx_fetches: false,
76 mempool_tx_fetch_delay: Duration::ZERO,
77 };
78 let res = FakeBitcoinTest {
79 inner: std::sync::RwLock::new(inner).into(),
80 };
81
82 res.mine_blocks_no_async(1);
84
85 res
86 }
87
88 fn pending_merkle_tree(pending: &[Transaction]) -> PartialMerkleTree {
89 let txs = pending
90 .iter()
91 .map(Transaction::compute_txid)
92 .collect::<Vec<Txid>>();
93 let matches = repeat_n(true, txs.len()).collect::<Vec<bool>>();
94 PartialMerkleTree::from_txids(txs.as_slice(), matches.as_slice())
95 }
96
97 fn new_transaction(out: Vec<TxOut>, nonce: u32) -> Transaction {
102 Transaction {
103 version: bitcoin::transaction::Version(0),
104 lock_time: LockTime::from_height(nonce).unwrap(),
105 input: vec![],
106 output: out,
107 }
108 }
109
110 fn mine_block(
111 addresses: &mut BTreeMap<Txid, Amount>,
112 blocks: &mut Vec<Block>,
113 pending: &mut Vec<Transaction>,
114 txid_to_block_height: &mut BTreeMap<Txid, usize>,
115 ) -> bitcoin::BlockHash {
116 debug!(
117 "Mining block: {} transactions, {} blocks",
118 pending.len(),
119 blocks.len()
120 );
121 let root = BlockHash::hash(&[0]);
122 let block_height = blocks.len();
125 for tx in pending.iter() {
126 addresses.insert(tx.compute_txid(), Amount::from_sats(output_sum(tx)));
127 txid_to_block_height.insert(tx.compute_txid(), block_height);
128 }
129 if pending.is_empty() {
131 pending.push(Self::new_transaction(vec![], blocks.len() as u32));
132 }
133 let merkle_root = Self::pending_merkle_tree(pending)
134 .extract_matches(&mut vec![], &mut vec![])
135 .unwrap();
136 let block = Block {
137 header: BlockHeader {
138 version: Version::from_consensus(0),
139 prev_blockhash: blocks.last().map_or(root, |b| b.header.block_hash()),
140 merkle_root,
141 time: 0,
142 bits: CompactTarget::from_consensus(0),
143 nonce: 0,
144 },
145 txdata: pending.clone(),
146 };
147 pending.clear();
148 blocks.push(block.clone());
149 block.block_hash()
150 }
151
152 fn mine_blocks_no_async(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
153 let mut inner = self.inner.write().unwrap();
154
155 let FakeBitcoinTestInner {
156 ref mut blocks,
157 ref mut pending,
158 ref mut addresses,
159 ref mut txid_to_block_height,
160 ..
161 } = *inner;
162
163 (1..=block_num)
164 .map(|_| FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height))
165 .collect()
166 }
167}
168
169#[async_trait]
170impl BitcoinTest for FakeBitcoinTest {
171 async fn lock_exclusive(&self) -> Box<dyn BitcoinTest + Send + Sync> {
172 Box::new(self.clone())
175 }
176
177 async fn mine_blocks(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
178 self.mine_blocks_no_async(block_num)
179 }
180
181 async fn prepare_funding_wallet(&self) {
182 let block_count = self.inner.write().unwrap().blocks.len() as u64;
185 if block_count < 100 {
186 self.mine_blocks(100 - block_count).await;
187 }
188 }
189
190 async fn send_without_mining(&self, address: &Address, amount: bitcoin::Amount) -> Transaction {
191 let mut inner = self.inner.write().unwrap();
192
193 let transaction = FakeBitcoinTest::new_transaction(
194 vec![TxOut {
195 value: amount,
196 script_pubkey: address.script_pubkey(),
197 }],
198 inner.blocks.len() as u32,
199 );
200
201 inner
202 .addresses
203 .insert(transaction.compute_txid(), amount.into());
204
205 inner.pending.push(transaction.clone());
206
207 transaction
208 }
209
210 async fn send_and_mine_block(
211 &self,
212 address: &Address,
213 amount: bitcoin::Amount,
214 ) -> (TxOutProof, Transaction) {
215 let mut inner = self.inner.write().unwrap();
216
217 let transaction = FakeBitcoinTest::new_transaction(
218 vec![TxOut {
219 value: amount,
220 script_pubkey: address.script_pubkey(),
221 }],
222 inner.blocks.len() as u32,
223 );
224 inner
225 .addresses
226 .insert(transaction.compute_txid(), amount.into());
227
228 inner.pending.push(transaction.clone());
229 let merkle_proof = FakeBitcoinTest::pending_merkle_tree(&inner.pending);
230
231 let FakeBitcoinTestInner {
232 ref mut blocks,
233 ref mut pending,
234 ref mut addresses,
235 ref mut txid_to_block_height,
236 ..
237 } = *inner;
238 FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height);
239 let block_header = inner.blocks.last().unwrap().header;
240 let proof = TxOutProof {
241 block_header,
242 merkle_proof,
243 };
244 inner
245 .proofs
246 .insert(transaction.compute_txid(), proof.clone());
247 inner
248 .scripts
249 .insert(address.script_pubkey(), vec![transaction.clone()]);
250
251 (proof, transaction)
252 }
253
254 async fn get_new_address(&self) -> Address {
255 let ctx = bitcoin::secp256k1::Secp256k1::new();
256 let (_, public_key) = ctx.generate_keypair(&mut OsRng);
257
258 Address::p2wpkh(&bitcoin::CompressedPublicKey(public_key), Network::Regtest)
259 }
260
261 async fn mine_block_and_get_received(&self, address: &Address) -> Amount {
262 self.mine_blocks(1).await;
263 let sats = self
264 .inner
265 .read()
266 .unwrap()
267 .blocks
268 .iter()
269 .flat_map(|block| block.txdata.iter().flat_map(|tx| tx.output.clone()))
270 .find(|out| out.script_pubkey == address.script_pubkey())
271 .map_or(0, |tx| tx.value.to_sat());
272 Amount::from_sats(sats)
273 }
274
275 async fn get_mempool_tx_fee(&self, txid: &Txid) -> Amount {
276 loop {
277 let (pending, addresses) = {
278 let inner = self.inner.read().unwrap();
279 (inner.pending.clone(), inner.addresses.clone())
280 };
281
282 let mut fee = Amount::ZERO;
283 let maybe_tx = pending.iter().find(|tx| tx.compute_txid() == *txid);
284
285 let Some(tx) = maybe_tx else {
286 sleep_in_test("no transaction found", Duration::from_millis(100)).await;
287 continue;
288 };
289
290 for input in &tx.input {
291 fee += *addresses
292 .get(&input.previous_output.txid)
293 .expect("previous transaction should be known");
294 }
295
296 for output in &tx.output {
297 fee -= output.value.into();
298 }
299
300 return fee;
301 }
302 }
303
304 async fn get_tx_block_height(&self, txid: &Txid) -> Option<u64> {
305 self.inner
306 .read()
307 .expect("RwLock poisoned")
308 .txid_to_block_height
309 .get(txid)
310 .map(|height| height.to_owned() as u64)
311 }
312
313 async fn get_block_count(&self) -> u64 {
314 self.inner.read().expect("RwLock poisoned").blocks.len() as u64
315 }
316
317 async fn get_mempool_tx(&self, txid: &Txid) -> Option<bitcoin::Transaction> {
318 let inner = self.inner.read().unwrap();
319 let mempool_transactions = inner.pending.clone();
320 mempool_transactions
321 .iter()
322 .find(|tx| tx.compute_txid() == *txid)
323 .map(std::borrow::ToOwned::to_owned)
324 }
325
326 fn fail_mempool_tx_fetches(&self, failing: bool) {
327 self.inner
328 .write()
329 .expect("RwLock poisoned")
330 .fail_mempool_tx_fetches = failing;
331 }
332
333 fn delay_mempool_tx_fetches(&self, delay: Duration) {
334 self.inner
335 .write()
336 .expect("RwLock poisoned")
337 .mempool_tx_fetch_delay = delay;
338 }
339}
340
341#[async_trait]
342impl IBitcoindRpc for FakeBitcoinTest {
343 async fn get_tx_block_height(
344 &self,
345 txid: &bitcoin::Txid,
346 ) -> Result<Option<u64>, BitcoinRpcError> {
347 for (height, block) in self.inner.read().unwrap().blocks.iter().enumerate() {
348 if block.txdata.iter().any(|tx| &tx.compute_txid() == txid) {
349 return Ok(Some(height as u64));
350 }
351 }
352 Ok(None)
353 }
354
355 async fn watch_script_history(&self, _: &bitcoin::ScriptBuf) -> Result<(), BitcoinRpcError> {
356 Ok(())
357 }
358
359 async fn get_script_history(
360 &self,
361 script: &bitcoin::ScriptBuf,
362 ) -> Result<Vec<bitcoin::Transaction>, BitcoinRpcError> {
363 let inner = self.inner.read().unwrap();
364 Ok(inner.scripts.get(script).cloned().unwrap_or_default())
365 }
366
367 async fn get_txout_proof(&self, txid: bitcoin::Txid) -> Result<TxOutProof, BitcoinRpcError> {
368 let inner = self.inner.read().unwrap();
369 let proof = inner.proofs.get(&txid);
370 Ok(proof
371 .ok_or(BitcoinRpcError::ProofNotFound { txid })?
372 .clone())
373 }
374
375 async fn get_info(&self) -> Result<BlockchainInfo, BitcoinRpcError> {
376 let inner = self.inner.read().unwrap();
377 let count = inner.blocks.len() as u64;
378 let synced = inner.pending.is_empty();
379 Ok(BlockchainInfo {
380 block_height: count - 1,
381 synced,
382 })
383 }
384}
385
386fn output_sum(tx: &Transaction) -> u64 {
387 tx.output.iter().map(|output| output.value.to_sat()).sum()
388}
389
390fn inputs(tx: &Transaction) -> Vec<OutPoint> {
391 tx.input.iter().map(|input| input.previous_output).collect()
392}
393
394#[async_trait::async_trait]
395impl IServerBitcoinRpc for FakeBitcoinTest {
396 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
397 BitcoinRpcConfig {
398 kind: "mock_kind".to_string(),
399 url: "http://mock".parse().unwrap(),
400 }
401 }
402
403 fn get_url(&self) -> SafeUrl {
404 "http://mock".parse().unwrap()
405 }
406
407 async fn get_block_count(&self) -> Result<u64> {
408 Ok(self.inner.read().unwrap().blocks.len() as u64)
409 }
410
411 async fn get_block_hash(&self, height: u64) -> Result<bitcoin::BlockHash> {
412 self.inner
413 .read()
414 .unwrap()
415 .blocks
416 .get(height as usize)
417 .map(|block| block.header.block_hash())
418 .context("No block with that height found")
419 }
420
421 async fn get_block(&self, block_hash: &bitcoin::BlockHash) -> Result<bitcoin::Block> {
422 self.inner
423 .read()
424 .unwrap()
425 .blocks
426 .iter()
427 .find(|block| block.header.block_hash() == *block_hash)
428 .context("No block with that hash found")
429 .cloned()
430 }
431
432 async fn get_feerate(&self) -> Result<Option<Feerate>> {
433 Ok(Some(Feerate { sats_per_kvb: 2000 }))
434 }
435
436 async fn get_mempool_txids(&self) -> Result<Option<Vec<bitcoin::Txid>>> {
441 Ok(Some(
442 self.inner
443 .read()
444 .unwrap()
445 .pending
446 .iter()
447 .map(bitcoin::Transaction::compute_txid)
448 .collect(),
449 ))
450 }
451
452 async fn get_mempool_tx(&self, txid: &bitcoin::Txid) -> Result<Option<bitcoin::Transaction>> {
453 let delay = self.inner.read().unwrap().mempool_tx_fetch_delay;
455
456 if !delay.is_zero() {
457 sleep_in_test("delaying a mempool transaction fetch", delay).await;
458 }
459
460 let inner = self.inner.read().unwrap();
461
462 if inner.fail_mempool_tx_fetches {
466 bail!("Mempool transaction fetches are failing");
467 }
468
469 Ok(inner
470 .pending
471 .iter()
472 .find(|tx| tx.compute_txid() == *txid)
473 .cloned())
474 }
475
476 async fn submit_transaction(&self, transaction: bitcoin::Transaction) -> anyhow::Result<()> {
477 let mut inner = self.inner.write().unwrap();
478 inner.pending.push(transaction);
479
480 let mut filtered = BTreeMap::<Vec<OutPoint>, bitcoin::Transaction>::new();
481
482 for tx in &inner.pending {
487 match filtered.get(&inputs(tx)) {
488 Some(found) if output_sum(tx) > output_sum(found) => {}
489 _ => {
490 filtered.insert(inputs(tx), tx.clone());
491 }
492 }
493 }
494
495 inner.pending = filtered.into_values().collect();
496
497 Ok(())
498 }
499
500 async fn get_sync_progress(&self) -> anyhow::Result<Option<f64>> {
501 Ok(None)
502 }
503
504 async fn get_chain_id(&self) -> anyhow::Result<ChainId> {
505 self.get_block_hash(1).await.map(ChainId::new)
506 }
507}