1use std::collections::BTreeMap;
2use std::iter::repeat;
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::{DynBitcoindRpc, IBitcoindRpc, IBitcoindRpcFactory, register_bitcoind};
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 rand::rngs::OsRng;
24use tracing::debug;
25
26use super::BitcoinTest;
27
28#[derive(Debug, Clone)]
29pub struct FakeBitcoinFactory {
30 pub bitcoin: FakeBitcoinTest,
31 pub config: BitcoinRpcConfig,
32}
33
34impl FakeBitcoinFactory {
35 pub fn register_new() -> FakeBitcoinFactory {
37 let kind = format!("test_btc-{}", rand::random::<u64>());
38 let factory = FakeBitcoinFactory {
39 bitcoin: FakeBitcoinTest::new(),
40 config: BitcoinRpcConfig {
41 kind: kind.clone(),
42 url: "http://ignored".parse().unwrap(),
43 },
44 };
45 register_bitcoind(kind, factory.clone().into());
46 factory
47 }
48}
49
50impl IBitcoindRpcFactory for FakeBitcoinFactory {
51 fn create_connection(&self, _url: &SafeUrl) -> Result<DynBitcoindRpc> {
52 Ok(self.bitcoin.clone().into())
53 }
54}
55
56#[derive(Debug)]
57struct FakeBitcoinTestInner {
58 blocks: Vec<Block>,
60 pending: Vec<Transaction>,
62 addresses: BTreeMap<Txid, Amount>,
65 proofs: BTreeMap<Txid, TxOutProof>,
67 scripts: BTreeMap<ScriptBuf, Vec<Transaction>>,
69 txid_to_block_height: BTreeMap<Txid, usize>,
71}
72
73#[derive(Clone, Debug)]
74pub struct FakeBitcoinTest {
75 inner: Arc<std::sync::RwLock<FakeBitcoinTestInner>>,
76}
77
78impl Default for FakeBitcoinTest {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84impl FakeBitcoinTest {
85 pub fn new() -> Self {
86 let inner = FakeBitcoinTestInner {
87 blocks: vec![genesis_block(Network::Regtest)],
88 pending: vec![],
89 addresses: BTreeMap::new(),
90 proofs: BTreeMap::new(),
91 scripts: BTreeMap::new(),
92 txid_to_block_height: BTreeMap::new(),
93 };
94 FakeBitcoinTest {
95 inner: std::sync::RwLock::new(inner).into(),
96 }
97 }
98
99 fn pending_merkle_tree(pending: &[Transaction]) -> PartialMerkleTree {
100 let txs = pending
101 .iter()
102 .map(Transaction::compute_txid)
103 .collect::<Vec<Txid>>();
104 let matches = repeat(true).take(txs.len()).collect::<Vec<bool>>();
105 PartialMerkleTree::from_txids(txs.as_slice(), matches.as_slice())
106 }
107
108 fn new_transaction(out: Vec<TxOut>, nonce: u32) -> Transaction {
113 Transaction {
114 version: bitcoin::transaction::Version(0),
115 lock_time: LockTime::from_height(nonce).unwrap(),
116 input: vec![],
117 output: out,
118 }
119 }
120
121 fn mine_block(
122 addresses: &mut BTreeMap<Txid, Amount>,
123 blocks: &mut Vec<Block>,
124 pending: &mut Vec<Transaction>,
125 txid_to_block_height: &mut BTreeMap<Txid, usize>,
126 ) -> bitcoin::BlockHash {
127 debug!(
128 "Mining block: {} transactions, {} blocks",
129 pending.len(),
130 blocks.len()
131 );
132 let root = BlockHash::hash(&[0]);
133 let block_height = blocks.len();
136 for tx in pending.iter() {
137 addresses.insert(tx.compute_txid(), Amount::from_sats(output_sum(tx)));
138 txid_to_block_height.insert(tx.compute_txid(), block_height);
139 }
140 if pending.is_empty() {
142 pending.push(Self::new_transaction(vec![], blocks.len() as u32));
143 }
144 let merkle_root = Self::pending_merkle_tree(pending)
145 .extract_matches(&mut vec![], &mut vec![])
146 .unwrap();
147 let block = Block {
148 header: BlockHeader {
149 version: Version::from_consensus(0),
150 prev_blockhash: blocks.last().map_or(root, |b| b.header.block_hash()),
151 merkle_root,
152 time: 0,
153 bits: CompactTarget::from_consensus(0),
154 nonce: 0,
155 },
156 txdata: pending.clone(),
157 };
158 pending.clear();
159 blocks.push(block.clone());
160 block.block_hash()
161 }
162}
163
164#[async_trait]
165impl BitcoinTest for FakeBitcoinTest {
166 async fn lock_exclusive(&self) -> Box<dyn BitcoinTest + Send + Sync> {
167 Box::new(self.clone())
170 }
171
172 async fn mine_blocks(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
173 let mut inner = self.inner.write().unwrap();
174
175 let FakeBitcoinTestInner {
176 ref mut blocks,
177 ref mut pending,
178 ref mut addresses,
179 ref mut txid_to_block_height,
180 ..
181 } = *inner;
182
183 (1..=block_num)
184 .map(|_| FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height))
185 .collect()
186 }
187
188 async fn prepare_funding_wallet(&self) {
189 let block_count = self.inner.write().unwrap().blocks.len() as u64;
192 if block_count < 100 {
193 self.mine_blocks(100 - block_count).await;
194 }
195 }
196
197 async fn send_and_mine_block(
198 &self,
199 address: &Address,
200 amount: bitcoin::Amount,
201 ) -> (TxOutProof, Transaction) {
202 let mut inner = self.inner.write().unwrap();
203
204 let transaction = FakeBitcoinTest::new_transaction(
205 vec![TxOut {
206 value: amount,
207 script_pubkey: address.script_pubkey(),
208 }],
209 inner.blocks.len() as u32,
210 );
211 inner
212 .addresses
213 .insert(transaction.compute_txid(), amount.into());
214
215 inner.pending.push(transaction.clone());
216 let merkle_proof = FakeBitcoinTest::pending_merkle_tree(&inner.pending);
217
218 let FakeBitcoinTestInner {
219 ref mut blocks,
220 ref mut pending,
221 ref mut addresses,
222 ref mut txid_to_block_height,
223 ..
224 } = *inner;
225 FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height);
226 let block_header = inner.blocks.last().unwrap().header;
227 let proof = TxOutProof {
228 block_header,
229 merkle_proof,
230 };
231 inner
232 .proofs
233 .insert(transaction.compute_txid(), proof.clone());
234 inner
235 .scripts
236 .insert(address.script_pubkey(), vec![transaction.clone()]);
237
238 (proof, transaction)
239 }
240
241 async fn get_new_address(&self) -> Address {
242 let ctx = bitcoin::secp256k1::Secp256k1::new();
243 let (_, public_key) = ctx.generate_keypair(&mut OsRng);
244
245 Address::p2wpkh(&bitcoin::CompressedPublicKey(public_key), Network::Regtest)
246 }
247
248 async fn mine_block_and_get_received(&self, address: &Address) -> Amount {
249 self.mine_blocks(1).await;
250 let sats = self
251 .inner
252 .read()
253 .unwrap()
254 .blocks
255 .iter()
256 .flat_map(|block| block.txdata.iter().flat_map(|tx| tx.output.clone()))
257 .find(|out| out.script_pubkey == address.script_pubkey())
258 .map_or(0, |tx| tx.value.to_sat());
259 Amount::from_sats(sats)
260 }
261
262 async fn get_mempool_tx_fee(&self, txid: &Txid) -> Amount {
263 loop {
264 let (pending, addresses) = {
265 let inner = self.inner.read().unwrap();
266 (inner.pending.clone(), inner.addresses.clone())
267 };
268
269 let mut fee = Amount::ZERO;
270 let maybe_tx = pending.iter().find(|tx| tx.compute_txid() == *txid);
271
272 let tx = match maybe_tx {
273 None => {
274 sleep_in_test("no transaction found", Duration::from_millis(100)).await;
275 continue;
276 }
277 Some(tx) => tx,
278 };
279
280 for input in &tx.input {
281 fee += *addresses
282 .get(&input.previous_output.txid)
283 .expect("previous transaction should be known");
284 }
285
286 for output in &tx.output {
287 fee -= output.value.into();
288 }
289
290 return fee;
291 }
292 }
293
294 async fn get_tx_block_height(&self, txid: &Txid) -> Option<u64> {
295 self.inner
296 .read()
297 .expect("RwLock poisoned")
298 .txid_to_block_height
299 .get(txid)
300 .map(|height| height.to_owned() as u64)
301 }
302
303 async fn get_block_count(&self) -> u64 {
304 self.inner.read().expect("RwLock poisoned").blocks.len() as u64
305 }
306
307 async fn get_mempool_tx(&self, txid: &Txid) -> Option<bitcoin::Transaction> {
308 let inner = self.inner.read().unwrap();
309 let mempool_transactions = inner.pending.clone();
310 mempool_transactions
311 .iter()
312 .find(|tx| tx.compute_txid() == *txid)
313 .map(std::borrow::ToOwned::to_owned)
314 }
315}
316
317#[async_trait]
318impl IBitcoindRpc for FakeBitcoinTest {
319 async fn get_network(&self) -> Result<bitcoin::Network> {
320 Ok(bitcoin::Network::Regtest)
321 }
322
323 async fn get_block_count(&self) -> Result<u64> {
324 Ok(self.inner.read().unwrap().blocks.len() as u64)
325 }
326
327 async fn get_block_hash(&self, height: u64) -> Result<bitcoin::BlockHash> {
328 self.inner
329 .read()
330 .unwrap()
331 .blocks
332 .get(height as usize)
333 .map(|block| block.header.block_hash())
334 .context("No block with that height found")
335 }
336
337 async fn get_block(&self, block_hash: &bitcoin::BlockHash) -> Result<bitcoin::Block> {
338 self.inner
339 .read()
340 .unwrap()
341 .blocks
342 .iter()
343 .find(|block| block.header.block_hash() == *block_hash)
344 .context("No block with that hash found")
345 .cloned()
346 }
347
348 async fn get_fee_rate(&self, _confirmation_target: u16) -> Result<Option<Feerate>> {
349 Ok(Some(Feerate { sats_per_kvb: 2000 }))
350 }
351
352 async fn submit_transaction(&self, transaction: bitcoin::Transaction) {
353 let mut inner = self.inner.write().unwrap();
354 inner.pending.push(transaction);
355
356 let mut filtered = BTreeMap::<Vec<OutPoint>, bitcoin::Transaction>::new();
357
358 for tx in &inner.pending {
363 match filtered.get(&inputs(tx)) {
364 Some(found) if output_sum(tx) > output_sum(found) => {}
365 _ => {
366 filtered.insert(inputs(tx), tx.clone());
367 }
368 }
369 }
370
371 inner.pending = filtered.into_values().collect();
372 }
373
374 async fn get_tx_block_height(&self, txid: &bitcoin::Txid) -> Result<Option<u64>> {
375 for (height, block) in self.inner.read().unwrap().blocks.iter().enumerate() {
376 if block.txdata.iter().any(|tx| &tx.compute_txid() == txid) {
377 return Ok(Some(height as u64));
378 }
379 }
380 Ok(None)
381 }
382
383 async fn is_tx_in_block(
384 &self,
385 txid: &bitcoin::Txid,
386 block_hash: &bitcoin::BlockHash,
387 block_height: u64,
388 ) -> Result<bool> {
389 let block = &self.inner.read().unwrap().blocks[block_height as usize];
390 assert!(
391 &block.block_hash() == block_hash,
392 "Block height for hash does not match expected height"
393 );
394
395 Ok(block.txdata.iter().any(|tx| &tx.compute_txid() == txid))
396 }
397
398 async fn watch_script_history(&self, _: &bitcoin::ScriptBuf) -> Result<()> {
399 Ok(())
400 }
401
402 async fn get_script_history(
403 &self,
404 script: &bitcoin::ScriptBuf,
405 ) -> Result<Vec<bitcoin::Transaction>> {
406 let inner = self.inner.read().unwrap();
407 Ok(inner.scripts.get(script).cloned().unwrap_or_default())
408 }
409
410 async fn get_txout_proof(&self, txid: bitcoin::Txid) -> Result<TxOutProof> {
411 let inner = self.inner.read().unwrap();
412 let proof = inner.proofs.get(&txid);
413 Ok(proof.ok_or(format_err!("No proof stored"))?.clone())
414 }
415
416 async fn get_sync_percentage(&self) -> anyhow::Result<Option<f64>> {
417 Ok(None)
418 }
419
420 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
421 BitcoinRpcConfig {
422 kind: "mock_kind".to_string(),
423 url: "http://mock".parse().unwrap(),
424 }
425 }
426}
427
428fn output_sum(tx: &Transaction) -> u64 {
429 tx.output.iter().map(|output| output.value.to_sat()).sum()
430}
431
432fn inputs(tx: &Transaction) -> Vec<OutPoint> {
433 tx.input.iter().map(|input| input.previous_output).collect()
434}