/ fedimint-testing / src / btc / mock.rs
mock.rs
  1  use std::collections::BTreeMap;
  2  use std::iter::repeat;
  3  use std::sync::Arc;
  4  use std::time::Duration;
  5  
  6  use anyhow::format_err;
  7  use async_trait::async_trait;
  8  use bitcoin::absolute::LockTime;
  9  use bitcoin::block::{Header as BlockHeader, Version};
 10  use bitcoin::blockdata::constants::genesis_block;
 11  use bitcoin::hash_types::Txid;
 12  use bitcoin::hashes::Hash;
 13  use bitcoin::merkle_tree::PartialMerkleTree;
 14  use bitcoin::{
 15      Address, Block, BlockHash, CompactTarget, Network, OutPoint, ScriptBuf, Transaction, TxOut,
 16  };
 17  use fedimint_bitcoind::{
 18      register_bitcoind, DynBitcoindRpc, IBitcoindRpc, IBitcoindRpcFactory,
 19      Result as BitcoinRpcResult,
 20  };
 21  use fedimint_core::envs::BitcoinRpcConfig;
 22  use fedimint_core::task::{sleep_in_test, TaskHandle};
 23  use fedimint_core::txoproof::TxOutProof;
 24  use fedimint_core::util::SafeUrl;
 25  use fedimint_core::{Amount, Feerate};
 26  use rand::rngs::OsRng;
 27  use tracing::debug;
 28  
 29  use super::BitcoinTest;
 30  
 31  #[derive(Debug, Clone)]
 32  pub struct FakeBitcoinFactory {
 33      pub bitcoin: FakeBitcoinTest,
 34      pub config: BitcoinRpcConfig,
 35  }
 36  
 37  impl FakeBitcoinFactory {
 38      /// Registers a fake bitcoin rpc factory for testing
 39      pub fn register_new() -> FakeBitcoinFactory {
 40          let kind = format!("test_btc-{}", rand::random::<u64>());
 41          let factory = FakeBitcoinFactory {
 42              bitcoin: FakeBitcoinTest::new(),
 43              config: BitcoinRpcConfig {
 44                  kind: kind.clone(),
 45                  url: "http://ignored".parse().unwrap(),
 46              },
 47          };
 48          register_bitcoind(kind, factory.clone().into());
 49          factory
 50      }
 51  }
 52  
 53  impl IBitcoindRpcFactory for FakeBitcoinFactory {
 54      fn create_connection(
 55          &self,
 56          _url: &SafeUrl,
 57          _handle: TaskHandle,
 58      ) -> anyhow::Result<DynBitcoindRpc> {
 59          Ok(self.bitcoin.clone().into())
 60      }
 61  }
 62  
 63  #[derive(Debug)]
 64  struct FakeBitcoinTestInner {
 65      /// Simulates mined bitcoin blocks
 66      blocks: Vec<Block>,
 67      /// Simulates pending transactions in the mempool
 68      pending: Vec<Transaction>,
 69      /// Tracks how much bitcoin was sent to an address (doesn't track sending
 70      /// out of it)
 71      addresses: BTreeMap<Txid, Amount>,
 72      /// Simulates the merkle tree proofs
 73      proofs: BTreeMap<Txid, TxOutProof>,
 74      /// Simulates the script history
 75      scripts: BTreeMap<ScriptBuf, Vec<Transaction>>,
 76  }
 77  
 78  #[derive(Clone, Debug)]
 79  pub struct FakeBitcoinTest {
 80      inner: Arc<std::sync::RwLock<FakeBitcoinTestInner>>,
 81  }
 82  
 83  impl Default for FakeBitcoinTest {
 84      fn default() -> Self {
 85          Self::new()
 86      }
 87  }
 88  
 89  impl FakeBitcoinTest {
 90      pub fn new() -> Self {
 91          let inner = FakeBitcoinTestInner {
 92              blocks: vec![genesis_block(Network::Regtest)],
 93              pending: vec![],
 94              addresses: Default::default(),
 95              proofs: Default::default(),
 96              scripts: Default::default(),
 97          };
 98          FakeBitcoinTest {
 99              inner: std::sync::RwLock::new(inner).into(),
100          }
101      }
102  
103      fn pending_merkle_tree(pending: &[Transaction]) -> PartialMerkleTree {
104          let txs = pending.iter().map(|tx| tx.txid()).collect::<Vec<Txid>>();
105          let matches = repeat(true).take(txs.len()).collect::<Vec<bool>>();
106          PartialMerkleTree::from_txids(txs.as_slice(), matches.as_slice())
107      }
108  
109      fn new_transaction(out: Vec<TxOut>) -> Transaction {
110          Transaction {
111              version: 0,
112              lock_time: LockTime::ZERO,
113              input: vec![],
114              output: out,
115          }
116      }
117  
118      fn mine_block(
119          addresses: &mut BTreeMap<Txid, Amount>,
120          blocks: &mut Vec<Block>,
121          pending: &mut Vec<Transaction>,
122      ) {
123          debug!(
124              "Mining block: {} transactions, {} blocks",
125              pending.len(),
126              blocks.len()
127          );
128          let root = BlockHash::hash(&[0]);
129          for tx in pending.iter() {
130              addresses.insert(tx.txid(), Amount::from_sats(output_sum(tx)));
131          }
132          // all blocks need at least one transaction
133          if pending.is_empty() {
134              pending.push(Self::new_transaction(vec![]));
135          }
136          let merkle_root = Self::pending_merkle_tree(pending)
137              .extract_matches(&mut vec![], &mut vec![])
138              .unwrap();
139          let block = Block {
140              header: BlockHeader {
141                  version: Version::from_consensus(0),
142                  prev_blockhash: blocks.last().map(|b| b.header.block_hash()).unwrap_or(root),
143                  merkle_root,
144                  time: 0,
145                  bits: CompactTarget::from_consensus(0),
146                  nonce: 0,
147              },
148              txdata: pending.clone(),
149          };
150          pending.clear();
151          blocks.push(block);
152      }
153  }
154  
155  #[async_trait]
156  impl BitcoinTest for FakeBitcoinTest {
157      async fn lock_exclusive(&self) -> Box<dyn BitcoinTest + Send + Sync> {
158          // With  FakeBitcoinTest, every test spawns their own instance,
159          // so not need to lock anything
160          Box::new(self.clone())
161      }
162  
163      async fn mine_blocks(&self, block_num: u64) {
164          let mut inner = self.inner.write().unwrap();
165  
166          let FakeBitcoinTestInner {
167              ref mut blocks,
168              ref mut pending,
169              ref mut addresses,
170              ..
171          } = *inner;
172  
173          for _ in 1..=block_num {
174              FakeBitcoinTest::mine_block(addresses, blocks, pending);
175          }
176      }
177  
178      async fn prepare_funding_wallet(&self) {
179          // In fake wallet this might not be technically necessary,
180          // but it makes it behave more like the `RealBitcoinTest`.
181          let block_count = self.inner.write().unwrap().blocks.len() as u64;
182          if block_count < 100 {
183              self.mine_blocks(100 - block_count).await;
184          }
185      }
186  
187      async fn send_and_mine_block(
188          &self,
189          address: &Address,
190          amount: bitcoin::Amount,
191      ) -> (TxOutProof, Transaction) {
192          let mut inner = self.inner.write().unwrap();
193  
194          let transaction = FakeBitcoinTest::new_transaction(vec![TxOut {
195              value: amount.to_sat(),
196              script_pubkey: address.payload.script_pubkey(),
197          }]);
198          inner.addresses.insert(transaction.txid(), amount.into());
199  
200          inner.pending.push(transaction.clone());
201          let merkle_proof = FakeBitcoinTest::pending_merkle_tree(&inner.pending);
202  
203          let FakeBitcoinTestInner {
204              ref mut blocks,
205              ref mut pending,
206              ref mut addresses,
207              ..
208          } = *inner;
209          FakeBitcoinTest::mine_block(addresses, blocks, pending);
210          let block_header = inner.blocks.last().unwrap().header;
211          let proof = TxOutProof {
212              block_header,
213              merkle_proof,
214          };
215          inner.proofs.insert(transaction.txid(), proof.clone());
216          inner
217              .scripts
218              .insert(address.payload.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::PublicKey::new(public_key), Network::Regtest).unwrap()
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              .clone()
238              .into_iter()
239              .flat_map(|block| block.txdata.into_iter().flat_map(|tx| tx.output))
240              .find(|out| out.script_pubkey == address.payload.script_pubkey())
241              .map(|tx| tx.value)
242              .unwrap_or(0);
243          Amount::from_sats(sats)
244      }
245  
246      async fn get_mempool_tx_fee(&self, txid: &Txid) -> Amount {
247          loop {
248              let (pending, addresses) = {
249                  let inner = self.inner.read().unwrap();
250                  (inner.pending.clone(), inner.addresses.clone())
251              };
252  
253              let mut fee = Amount::ZERO;
254              let maybe_tx = pending.iter().find(|tx| tx.txid() == *txid);
255  
256              let tx = match maybe_tx {
257                  None => {
258                      sleep_in_test("no transaction found", Duration::from_millis(100)).await;
259                      continue;
260                  }
261                  Some(tx) => tx,
262              };
263  
264              for input in tx.input.iter() {
265                  fee += *addresses
266                      .get(&input.previous_output.txid)
267                      .expect("previous transaction should be known");
268              }
269  
270              for output in tx.output.iter() {
271                  fee -= Amount::from_sats(output.value);
272              }
273  
274              return fee;
275          }
276      }
277  }
278  
279  #[async_trait]
280  impl IBitcoindRpc for FakeBitcoinTest {
281      async fn get_network(&self) -> BitcoinRpcResult<bitcoin::Network> {
282          Ok(bitcoin::Network::Regtest)
283      }
284  
285      async fn get_block_count(&self) -> BitcoinRpcResult<u64> {
286          Ok(self.inner.read().unwrap().blocks.len() as u64)
287      }
288  
289      async fn get_block_hash(&self, height: u64) -> BitcoinRpcResult<bitcoin::BlockHash> {
290          Ok(self.inner.read().unwrap().blocks[height as usize]
291              .header
292              .block_hash())
293      }
294  
295      async fn get_fee_rate(&self, _confirmation_target: u16) -> BitcoinRpcResult<Option<Feerate>> {
296          Ok(Some(Feerate { sats_per_kvb: 2000 }))
297      }
298  
299      async fn submit_transaction(&self, transaction: bitcoin::Transaction) {
300          let mut inner = self.inner.write().unwrap();
301          inner.pending.push(transaction);
302  
303          let mut filtered = BTreeMap::<Vec<OutPoint>, bitcoin::Transaction>::new();
304  
305          // Simulate the mempool keeping txs with higher fees (less output)
306          // TODO: This looks borked, should remove from `filtered` on higher fee or
307          // something, and check per-input anyway. Probably doesn't matter, and I
308          // don't want to touch it.
309          for tx in inner.pending.iter() {
310              match filtered.get(&inputs(tx)) {
311                  Some(found) if output_sum(tx) > output_sum(found) => {}
312                  _ => {
313                      filtered.insert(inputs(tx), tx.clone());
314                  }
315              }
316          }
317  
318          inner.pending = filtered.into_values().collect();
319      }
320  
321      async fn get_tx_block_height(&self, txid: &bitcoin::Txid) -> BitcoinRpcResult<Option<u64>> {
322          for (height, block) in self.inner.read().unwrap().blocks.iter().enumerate() {
323              if block.txdata.iter().any(|tx| tx.txid() == *txid) {
324                  return Ok(Some(height as u64));
325              }
326          }
327          Ok(None)
328      }
329  
330      async fn watch_script_history(&self, _: &ScriptBuf) -> BitcoinRpcResult<()> {
331          Ok(())
332      }
333  
334      async fn get_script_history(
335          &self,
336          script: &ScriptBuf,
337      ) -> BitcoinRpcResult<Vec<bitcoin::Transaction>> {
338          let inner = self.inner.read().unwrap();
339          let script = inner.scripts.get(script).cloned().unwrap_or_default();
340          Ok(script)
341      }
342  
343      async fn get_txout_proof(&self, txid: bitcoin::Txid) -> BitcoinRpcResult<TxOutProof> {
344          let inner = self.inner.read().unwrap();
345          let proof = inner.proofs.get(&txid);
346          Ok(proof.ok_or(format_err!("No proof stored"))?.clone())
347      }
348  }
349  
350  fn output_sum(tx: &Transaction) -> u64 {
351      tx.output.iter().map(|output| output.value).sum()
352  }
353  
354  fn inputs(tx: &Transaction) -> Vec<OutPoint> {
355      tx.input.iter().map(|input| input.previous_output).collect()
356  }