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::{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}
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 let res = FakeBitcoinTest {
68 inner: std::sync::RwLock::new(inner).into(),
69 };
70
71 res.mine_blocks_no_async(1);
73
74 res
75 }
76
77 fn pending_merkle_tree(pending: &[Transaction]) -> PartialMerkleTree {
78 let txs = pending
79 .iter()
80 .map(Transaction::compute_txid)
81 .collect::<Vec<Txid>>();
82 let matches = repeat_n(true, txs.len()).collect::<Vec<bool>>();
83 PartialMerkleTree::from_txids(txs.as_slice(), matches.as_slice())
84 }
85
86 fn new_transaction(out: Vec<TxOut>, nonce: u32) -> Transaction {
91 Transaction {
92 version: bitcoin::transaction::Version(0),
93 lock_time: LockTime::from_height(nonce).unwrap(),
94 input: vec![],
95 output: out,
96 }
97 }
98
99 fn mine_block(
100 addresses: &mut BTreeMap<Txid, Amount>,
101 blocks: &mut Vec<Block>,
102 pending: &mut Vec<Transaction>,
103 txid_to_block_height: &mut BTreeMap<Txid, usize>,
104 ) -> bitcoin::BlockHash {
105 debug!(
106 "Mining block: {} transactions, {} blocks",
107 pending.len(),
108 blocks.len()
109 );
110 let root = BlockHash::hash(&[0]);
111 let block_height = blocks.len();
114 for tx in pending.iter() {
115 addresses.insert(tx.compute_txid(), Amount::from_sats(output_sum(tx)));
116 txid_to_block_height.insert(tx.compute_txid(), block_height);
117 }
118 if pending.is_empty() {
120 pending.push(Self::new_transaction(vec![], blocks.len() as u32));
121 }
122 let merkle_root = Self::pending_merkle_tree(pending)
123 .extract_matches(&mut vec![], &mut vec![])
124 .unwrap();
125 let block = Block {
126 header: BlockHeader {
127 version: Version::from_consensus(0),
128 prev_blockhash: blocks.last().map_or(root, |b| b.header.block_hash()),
129 merkle_root,
130 time: 0,
131 bits: CompactTarget::from_consensus(0),
132 nonce: 0,
133 },
134 txdata: pending.clone(),
135 };
136 pending.clear();
137 blocks.push(block.clone());
138 block.block_hash()
139 }
140
141 fn mine_blocks_no_async(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
142 let mut inner = self.inner.write().unwrap();
143
144 let FakeBitcoinTestInner {
145 ref mut blocks,
146 ref mut pending,
147 ref mut addresses,
148 ref mut txid_to_block_height,
149 ..
150 } = *inner;
151
152 (1..=block_num)
153 .map(|_| FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height))
154 .collect()
155 }
156}
157
158#[async_trait]
159impl BitcoinTest for FakeBitcoinTest {
160 async fn lock_exclusive(&self) -> Box<dyn BitcoinTest + Send + Sync> {
161 Box::new(self.clone())
164 }
165
166 async fn mine_blocks(&self, block_num: u64) -> Vec<bitcoin::BlockHash> {
167 self.mine_blocks_no_async(block_num)
168 }
169
170 async fn prepare_funding_wallet(&self) {
171 let block_count = self.inner.write().unwrap().blocks.len() as u64;
174 if block_count < 100 {
175 self.mine_blocks(100 - block_count).await;
176 }
177 }
178
179 async fn send_and_mine_block(
180 &self,
181 address: &Address,
182 amount: bitcoin::Amount,
183 ) -> (TxOutProof, Transaction) {
184 let mut inner = self.inner.write().unwrap();
185
186 let transaction = FakeBitcoinTest::new_transaction(
187 vec![TxOut {
188 value: amount,
189 script_pubkey: address.script_pubkey(),
190 }],
191 inner.blocks.len() as u32,
192 );
193 inner
194 .addresses
195 .insert(transaction.compute_txid(), amount.into());
196
197 inner.pending.push(transaction.clone());
198 let merkle_proof = FakeBitcoinTest::pending_merkle_tree(&inner.pending);
199
200 let FakeBitcoinTestInner {
201 ref mut blocks,
202 ref mut pending,
203 ref mut addresses,
204 ref mut txid_to_block_height,
205 ..
206 } = *inner;
207 FakeBitcoinTest::mine_block(addresses, blocks, pending, txid_to_block_height);
208 let block_header = inner.blocks.last().unwrap().header;
209 let proof = TxOutProof {
210 block_header,
211 merkle_proof,
212 };
213 inner
214 .proofs
215 .insert(transaction.compute_txid(), proof.clone());
216 inner
217 .scripts
218 .insert(address.script_pubkey(), vec![transaction.clone()]);
219
220 (proof, transaction)
221 }
222
223 async fn get_new_address(&self) -> Address {
224 let ctx = bitcoin::secp256k1::Secp256k1::new();
225 let (_, public_key) = ctx.generate_keypair(&mut OsRng);
226
227 Address::p2wpkh(&bitcoin::CompressedPublicKey(public_key), Network::Regtest)
228 }
229
230 async fn mine_block_and_get_received(&self, address: &Address) -> Amount {
231 self.mine_blocks(1).await;
232 let sats = self
233 .inner
234 .read()
235 .unwrap()
236 .blocks
237 .iter()
238 .flat_map(|block| block.txdata.iter().flat_map(|tx| tx.output.clone()))
239 .find(|out| out.script_pubkey == address.script_pubkey())
240 .map_or(0, |tx| tx.value.to_sat());
241 Amount::from_sats(sats)
242 }
243
244 async fn get_mempool_tx_fee(&self, txid: &Txid) -> Amount {
245 loop {
246 let (pending, addresses) = {
247 let inner = self.inner.read().unwrap();
248 (inner.pending.clone(), inner.addresses.clone())
249 };
250
251 let mut fee = Amount::ZERO;
252 let maybe_tx = pending.iter().find(|tx| tx.compute_txid() == *txid);
253
254 let Some(tx) = maybe_tx else {
255 sleep_in_test("no transaction found", Duration::from_millis(100)).await;
256 continue;
257 };
258
259 for input in &tx.input {
260 fee += *addresses
261 .get(&input.previous_output.txid)
262 .expect("previous transaction should be known");
263 }
264
265 for output in &tx.output {
266 fee -= output.value.into();
267 }
268
269 return fee;
270 }
271 }
272
273 async fn get_tx_block_height(&self, txid: &Txid) -> Option<u64> {
274 self.inner
275 .read()
276 .expect("RwLock poisoned")
277 .txid_to_block_height
278 .get(txid)
279 .map(|height| height.to_owned() as u64)
280 }
281
282 async fn get_block_count(&self) -> u64 {
283 self.inner.read().expect("RwLock poisoned").blocks.len() as u64
284 }
285
286 async fn get_mempool_tx(&self, txid: &Txid) -> Option<bitcoin::Transaction> {
287 let inner = self.inner.read().unwrap();
288 let mempool_transactions = inner.pending.clone();
289 mempool_transactions
290 .iter()
291 .find(|tx| tx.compute_txid() == *txid)
292 .map(std::borrow::ToOwned::to_owned)
293 }
294}
295
296#[async_trait]
297impl IBitcoindRpc for FakeBitcoinTest {
298 async fn get_tx_block_height(&self, txid: &bitcoin::Txid) -> Result<Option<u64>> {
299 for (height, block) in self.inner.read().unwrap().blocks.iter().enumerate() {
300 if block.txdata.iter().any(|tx| &tx.compute_txid() == txid) {
301 return Ok(Some(height as u64));
302 }
303 }
304 Ok(None)
305 }
306
307 async fn watch_script_history(&self, _: &bitcoin::ScriptBuf) -> Result<()> {
308 Ok(())
309 }
310
311 async fn get_script_history(
312 &self,
313 script: &bitcoin::ScriptBuf,
314 ) -> Result<Vec<bitcoin::Transaction>> {
315 let inner = self.inner.read().unwrap();
316 Ok(inner.scripts.get(script).cloned().unwrap_or_default())
317 }
318
319 async fn get_txout_proof(&self, txid: bitcoin::Txid) -> Result<TxOutProof> {
320 let inner = self.inner.read().unwrap();
321 let proof = inner.proofs.get(&txid);
322 Ok(proof.ok_or(format_err!("No proof stored"))?.clone())
323 }
324
325 async fn get_info(&self) -> Result<BlockchainInfo> {
326 let inner = self.inner.read().unwrap();
327 let count = inner.blocks.len() as u64;
328 let synced = inner.pending.is_empty();
329 Ok(BlockchainInfo {
330 block_height: count - 1,
331 synced,
332 })
333 }
334}
335
336fn output_sum(tx: &Transaction) -> u64 {
337 tx.output.iter().map(|output| output.value.to_sat()).sum()
338}
339
340fn inputs(tx: &Transaction) -> Vec<OutPoint> {
341 tx.input.iter().map(|input| input.previous_output).collect()
342}
343
344#[async_trait::async_trait]
345impl IServerBitcoinRpc for FakeBitcoinTest {
346 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
347 BitcoinRpcConfig {
348 kind: "mock_kind".to_string(),
349 url: "http://mock".parse().unwrap(),
350 }
351 }
352
353 fn get_url(&self) -> SafeUrl {
354 "http://mock".parse().unwrap()
355 }
356
357 async fn get_block_count(&self) -> Result<u64> {
358 Ok(self.inner.read().unwrap().blocks.len() as u64)
359 }
360
361 async fn get_block_hash(&self, height: u64) -> Result<bitcoin::BlockHash> {
362 self.inner
363 .read()
364 .unwrap()
365 .blocks
366 .get(height as usize)
367 .map(|block| block.header.block_hash())
368 .context("No block with that height found")
369 }
370
371 async fn get_block(&self, block_hash: &bitcoin::BlockHash) -> Result<bitcoin::Block> {
372 self.inner
373 .read()
374 .unwrap()
375 .blocks
376 .iter()
377 .find(|block| block.header.block_hash() == *block_hash)
378 .context("No block with that hash found")
379 .cloned()
380 }
381
382 async fn get_feerate(&self) -> Result<Option<Feerate>> {
383 Ok(Some(Feerate { sats_per_kvb: 2000 }))
384 }
385
386 async fn submit_transaction(&self, transaction: bitcoin::Transaction) {
387 let mut inner = self.inner.write().unwrap();
388 inner.pending.push(transaction);
389
390 let mut filtered = BTreeMap::<Vec<OutPoint>, bitcoin::Transaction>::new();
391
392 for tx in &inner.pending {
397 match filtered.get(&inputs(tx)) {
398 Some(found) if output_sum(tx) > output_sum(found) => {}
399 _ => {
400 filtered.insert(inputs(tx), tx.clone());
401 }
402 }
403 }
404
405 inner.pending = filtered.into_values().collect();
406 }
407
408 async fn get_sync_progress(&self) -> anyhow::Result<Option<f64>> {
409 Ok(None)
410 }
411
412 async fn get_chain_id(&self) -> anyhow::Result<ChainId> {
413 self.get_block_hash(1).await.map(ChainId::new)
414 }
415}