testutils/
lib.rs

1// Bitcoin Dev Kit
2// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3//
4// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5//
6// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9// You may not use this file except in accordance with one or both of these
10// licenses.
11
12#[macro_use]
13extern crate serde_json;
14
15pub use serial_test::serial;
16
17use std::collections::HashMap;
18use std::env;
19use std::ops::Deref;
20use std::path::PathBuf;
21use std::str::FromStr;
22use std::time::Duration;
23
24#[allow(unused_imports)]
25use log::{debug, error, info, trace};
26
27use bitcoin::consensus::encode::{deserialize, serialize};
28use bitcoin::hashes::hex::{FromHex, ToHex};
29use bitcoin::hashes::sha256d;
30use bitcoin::secp256k1::{Secp256k1, Verification};
31use bitcoin::{Address, Amount, PublicKey, Script, Transaction, Txid};
32
33use miniscript::descriptor::DescriptorPublicKey;
34use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
35
36pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
37pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
38
39pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
40
41// TODO: we currently only support env vars, we could also parse a toml file
42fn get_auth() -> Auth {
43    match env::var("BDK_RPC_AUTH").as_ref().map(String::as_ref) {
44        Ok("USER_PASS") => Auth::UserPass(
45            env::var("BDK_RPC_USER").unwrap(),
46            env::var("BDK_RPC_PASS").unwrap(),
47        ),
48        _ => Auth::CookieFile(PathBuf::from(
49            env::var("BDK_RPC_COOKIEFILE")
50                .unwrap_or_else(|_| "/home/user/.bitcoin/regtest/.cookie".to_string()),
51        )),
52    }
53}
54
55pub fn get_electrum_url() -> String {
56    env::var("BDK_ELECTRUM_URL").unwrap_or_else(|_| "tcp://127.0.0.1:50001".to_string())
57}
58
59pub struct TestClient {
60    client: RpcClient,
61    electrum: ElectrumClient,
62}
63
64#[derive(Clone, Debug)]
65pub struct TestIncomingOutput {
66    pub value: u64,
67    pub to_address: String,
68}
69
70impl TestIncomingOutput {
71    pub fn new(value: u64, to_address: Address) -> Self {
72        Self {
73            value,
74            to_address: to_address.to_string(),
75        }
76    }
77}
78
79#[derive(Clone, Debug)]
80pub struct TestIncomingTx {
81    pub output: Vec<TestIncomingOutput>,
82    pub min_confirmations: Option<u64>,
83    pub locktime: Option<i64>,
84    pub replaceable: Option<bool>,
85}
86
87impl TestIncomingTx {
88    pub fn new(
89        output: Vec<TestIncomingOutput>,
90        min_confirmations: Option<u64>,
91        locktime: Option<i64>,
92        replaceable: Option<bool>,
93    ) -> Self {
94        Self {
95            output,
96            min_confirmations,
97            locktime,
98            replaceable,
99        }
100    }
101
102    pub fn add_output(&mut self, output: TestIncomingOutput) {
103        self.output.push(output);
104    }
105}
106
107#[doc(hidden)]
108pub trait TranslateDescriptor {
109    // derive and translate a `Descriptor<DescriptorPublicKey>` into a `Descriptor<PublicKey>`
110    fn derive_translated<C: Verification>(
111        &self,
112        secp: &Secp256k1<C>,
113        index: u32,
114    ) -> Descriptor<PublicKey>;
115}
116
117impl TranslateDescriptor for Descriptor<DescriptorPublicKey> {
118    fn derive_translated<C: Verification>(
119        &self,
120        secp: &Secp256k1<C>,
121        index: u32,
122    ) -> Descriptor<PublicKey> {
123        let translate = |key: &DescriptorPublicKey| -> PublicKey {
124            match key {
125                DescriptorPublicKey::XPub(xpub) => {
126                    xpub.xkey
127                        .derive_pub(secp, &xpub.derivation_path)
128                        .expect("hardened derivation steps")
129                        .public_key
130                }
131                DescriptorPublicKey::SinglePub(key) => key.key,
132            }
133        };
134
135        self.derive(index)
136            .translate_pk_infallible(|pk| translate(pk), |pkh| translate(pkh).to_pubkeyhash())
137    }
138}
139
140#[macro_export]
141macro_rules! testutils {
142    ( @external $descriptors:expr, $child:expr ) => ({
143        use bitcoin::secp256k1::Secp256k1;
144        use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
145
146        use $crate::TranslateDescriptor;
147
148        let secp = Secp256k1::new();
149
150        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
151        parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
152    });
153    ( @internal $descriptors:expr, $child:expr ) => ({
154        use bitcoin::secp256k1::Secp256k1;
155        use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
156
157        use $crate::TranslateDescriptor;
158
159        let secp = Secp256k1::new();
160
161        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
162        parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
163    });
164    ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
165    ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
166
167    ( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @locktime $locktime:expr ) )* $( ( @confirmations $confirmations:expr ) )* $( ( @replaceable $replaceable:expr ) )* ) => ({
168        let mut outs = Vec::new();
169        $( outs.push(testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))); )+
170
171        let mut locktime = None::<i64>;
172        $( locktime = Some($locktime); )*
173
174        let mut min_confirmations = None::<u64>;
175        $( min_confirmations = Some($confirmations); )*
176
177        let mut replaceable = None::<bool>;
178        $( replaceable = Some($replaceable); )*
179
180        testutils::TestIncomingTx::new(outs, min_confirmations, locktime, replaceable)
181    });
182
183    ( @literal $key:expr ) => ({
184        let key = $key.to_string();
185        (key, None::<String>, None::<String>)
186    });
187    ( @generate_xprv $( $external_path:expr )* $( ,$internal_path:expr )* ) => ({
188        use rand::Rng;
189
190        let mut seed = [0u8; 32];
191        rand::thread_rng().fill(&mut seed[..]);
192
193        let key = bitcoin::util::bip32::ExtendedPrivKey::new_master(
194            bitcoin::Network::Testnet,
195            &seed,
196        );
197
198        let mut external_path = None::<String>;
199        $( external_path = Some($external_path.to_string()); )*
200
201        let mut internal_path = None::<String>;
202        $( internal_path = Some($internal_path.to_string()); )*
203
204        (key.unwrap().to_string(), external_path, internal_path)
205    });
206    ( @generate_wif ) => ({
207        use rand::Rng;
208
209        let mut key = [0u8; bitcoin::secp256k1::constants::SECRET_KEY_SIZE];
210        rand::thread_rng().fill(&mut key[..]);
211
212        (bitcoin::PrivateKey {
213            compressed: true,
214            network: bitcoin::Network::Testnet,
215            key: bitcoin::secp256k1::SecretKey::from_slice(&key).unwrap(),
216        }.to_string(), None::<String>, None::<String>)
217    });
218
219    ( @keys ( $( $alias:expr => ( $( $key_type:tt )* ) ),+ ) ) => ({
220        let mut map = std::collections::HashMap::new();
221        $(
222            let alias: &str = $alias;
223            map.insert(alias, testutils!( $($key_type)* ));
224        )+
225
226        map
227    });
228
229    ( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )* $( ( @keys $( $keys:tt )* ) )* ) => ({
230        use std::str::FromStr;
231        use std::collections::HashMap;
232        use std::convert::TryInto;
233
234        use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
235        use miniscript::TranslatePk;
236
237        let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
238        $(
239            keys = testutils!{ @keys $( $keys )* };
240        )*
241
242        let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
243        let external: Descriptor<String> = external.translate_pk_infallible::<_, _>(|k| {
244            if let Some((key, ext_path, _)) = keys.get(&k.as_str()) {
245                format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
246            } else {
247                k.clone()
248            }
249        }, |kh| {
250            if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) {
251                format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
252            } else {
253                kh.clone()
254            }
255
256        });
257        let external = external.to_string();
258
259        let mut internal = None::<String>;
260        $(
261            let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
262
263            let string_internal: Descriptor<String> = string_internal.translate_pk_infallible::<_, _>(|k| {
264                if let Some((key, _, int_path)) = keys.get(&k.as_str()) {
265                    format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
266                } else {
267                    k.clone()
268                }
269            }, |kh| {
270                if let Some((key, _, int_path)) = keys.get(&kh.as_str()) {
271                    format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
272                } else {
273                    kh.clone()
274                }
275            });
276            internal = Some(string_internal.to_string());
277        )*
278
279        (external, internal)
280    })
281}
282
283fn exponential_backoff_poll<T, F>(mut poll: F) -> T
284where
285    F: FnMut() -> Option<T>,
286{
287    let mut delay = Duration::from_millis(64);
288    loop {
289        match poll() {
290            Some(data) => break data,
291            None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
292            None => {}
293        }
294
295        std::thread::sleep(delay);
296    }
297}
298
299impl TestClient {
300    pub fn new() -> Self {
301        let url = env::var("BDK_RPC_URL").unwrap_or_else(|_| "127.0.0.1:18443".to_string());
302        let wallet = env::var("BDK_RPC_WALLET").unwrap_or_else(|_| "bdk-test".to_string());
303        let client =
304            RpcClient::new(format!("http://{}/wallet/{}", url, wallet), get_auth()).unwrap();
305        let electrum = ElectrumClient::new(&get_electrum_url()).unwrap();
306
307        TestClient { client, electrum }
308    }
309
310    fn wait_for_tx(&mut self, txid: Txid, monitor_script: &Script) {
311        // wait for electrs to index the tx
312        exponential_backoff_poll(|| {
313            trace!("wait_for_tx {}", txid);
314
315            self.electrum
316                .script_get_history(monitor_script)
317                .unwrap()
318                .iter()
319                .position(|entry| entry.tx_hash == txid)
320        });
321    }
322
323    fn wait_for_block(&mut self, min_height: usize) {
324        self.electrum.block_headers_subscribe().unwrap();
325
326        loop {
327            let header = exponential_backoff_poll(|| {
328                self.electrum.ping().unwrap();
329                self.electrum.block_headers_pop().unwrap()
330            });
331            if header.height >= min_height {
332                break;
333            }
334        }
335    }
336
337    pub fn receive(&mut self, meta_tx: TestIncomingTx) -> Txid {
338        assert!(
339            !meta_tx.output.is_empty(),
340            "can't create a transaction with no outputs"
341        );
342
343        let mut map = HashMap::new();
344
345        let mut required_balance = 0;
346        for out in &meta_tx.output {
347            required_balance += out.value;
348            map.insert(out.to_address.clone(), Amount::from_sat(out.value));
349        }
350
351        if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
352            panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
353        }
354
355        // FIXME: core can't create a tx with two outputs to the same address
356        let tx = self
357            .create_raw_transaction_hex(&[], &map, meta_tx.locktime, meta_tx.replaceable)
358            .unwrap();
359        let tx = self.fund_raw_transaction(tx, None, None).unwrap();
360        let mut tx: Transaction = deserialize(&tx.hex).unwrap();
361
362        if let Some(true) = meta_tx.replaceable {
363            // for some reason core doesn't set this field right
364            for input in &mut tx.input {
365                input.sequence = 0xFFFFFFFD;
366            }
367        }
368
369        let tx = self
370            .sign_raw_transaction_with_wallet(&serialize(&tx), None, None)
371            .unwrap();
372
373        // broadcast through electrum so that it caches the tx immediately
374        let txid = self
375            .electrum
376            .transaction_broadcast(&deserialize(&tx.hex).unwrap())
377            .unwrap();
378
379        if let Some(num) = meta_tx.min_confirmations {
380            self.generate(num, None);
381        }
382
383        let monitor_script = Address::from_str(&meta_tx.output[0].to_address)
384            .unwrap()
385            .script_pubkey();
386        self.wait_for_tx(txid, &monitor_script);
387
388        debug!("Sent tx: {}", txid);
389
390        txid
391    }
392
393    pub fn bump_fee(&mut self, txid: &Txid) -> Txid {
394        let tx = self.get_raw_transaction_info(txid, None).unwrap();
395        assert!(
396            tx.confirmations.is_none(),
397            "Can't bump tx {} because it's already confirmed",
398            txid
399        );
400
401        let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
402        let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
403
404        let monitor_script =
405            tx.vout[0].script_pub_key.addresses.as_ref().unwrap()[0].script_pubkey();
406        self.wait_for_tx(new_txid, &monitor_script);
407
408        debug!("Bumped {}, new txid {}", txid, new_txid);
409
410        new_txid
411    }
412
413    pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String {
414        use bitcoin::blockdata::block::{Block, BlockHeader};
415        use bitcoin::blockdata::script::Builder;
416        use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
417        use bitcoin::hash_types::{BlockHash, TxMerkleNode};
418
419        let block_template: serde_json::Value = self
420            .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
421            .unwrap();
422        trace!("getblocktemplate: {:#?}", block_template);
423
424        let header = BlockHeader {
425            version: block_template["version"].as_i64().unwrap() as i32,
426            prev_blockhash: BlockHash::from_hex(
427                block_template["previousblockhash"].as_str().unwrap(),
428            )
429            .unwrap(),
430            merkle_root: TxMerkleNode::default(),
431            time: block_template["curtime"].as_u64().unwrap() as u32,
432            bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
433            nonce: 0,
434        };
435        debug!("header: {:#?}", header);
436
437        let height = block_template["height"].as_u64().unwrap() as i64;
438        let witness_reserved_value: Vec<u8> = sha256d::Hash::default().as_ref().into();
439        // burn block subsidy and fees, not a big deal
440        let mut coinbase_tx = Transaction {
441            version: 1,
442            lock_time: 0,
443            input: vec![TxIn {
444                previous_output: OutPoint::null(),
445                script_sig: Builder::new().push_int(height).into_script(),
446                sequence: 0xFFFFFFFF,
447                witness: vec![witness_reserved_value],
448            }],
449            output: vec![],
450        };
451
452        let mut txdata = vec![coinbase_tx.clone()];
453        txdata.extend_from_slice(&txs);
454
455        let mut block = Block { header, txdata };
456
457        let witness_root = block.witness_root();
458        let witness_commitment =
459            Block::compute_witness_commitment(&witness_root, &coinbase_tx.input[0].witness[0]);
460
461        // now update and replace the coinbase tx
462        let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
463        coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
464
465        coinbase_tx.output.push(TxOut {
466            value: 0,
467            script_pubkey: coinbase_witness_commitment_script.into(),
468        });
469        block.txdata[0] = coinbase_tx;
470
471        // set merkle root
472        let merkle_root = block.merkle_root();
473        block.header.merkle_root = merkle_root;
474
475        assert!(block.check_merkle_root());
476        assert!(block.check_witness_commitment());
477
478        // now do PoW :)
479        let target = block.header.target();
480        while block.header.validate_pow(&target).is_err() {
481            block.header.nonce = block.header.nonce.checked_add(1).unwrap(); // panic if we run out of nonces
482        }
483
484        let block_hex: String = serialize(&block).to_hex();
485        debug!("generated block hex: {}", block_hex);
486
487        self.electrum.block_headers_subscribe().unwrap();
488
489        let submit_result: serde_json::Value =
490            self.call("submitblock", &[block_hex.into()]).unwrap();
491        debug!("submitblock: {:?}", submit_result);
492        assert!(
493            submit_result.is_null(),
494            "submitblock error: {:?}",
495            submit_result.as_str()
496        );
497
498        self.wait_for_block(height as usize);
499
500        block.header.block_hash().to_hex()
501    }
502
503    pub fn generate(&mut self, num_blocks: u64, address: Option<Address>) {
504        let address = address.unwrap_or_else(|| self.get_new_address(None, None).unwrap());
505        let hashes = self.generate_to_address(num_blocks, &address).unwrap();
506        let best_hash = hashes.last().unwrap();
507        let height = self.get_block_info(best_hash).unwrap().height;
508
509        self.wait_for_block(height);
510
511        debug!("Generated blocks to new height {}", height);
512    }
513
514    pub fn invalidate(&mut self, num_blocks: u64) {
515        self.electrum.block_headers_subscribe().unwrap();
516
517        let best_hash = self.get_best_block_hash().unwrap();
518        let initial_height = self.get_block_info(&best_hash).unwrap().height;
519
520        let mut to_invalidate = best_hash;
521        for i in 1..=num_blocks {
522            trace!(
523                "Invalidating block {}/{} ({})",
524                i,
525                num_blocks,
526                to_invalidate
527            );
528
529            self.invalidate_block(&to_invalidate).unwrap();
530            to_invalidate = self.get_best_block_hash().unwrap();
531        }
532
533        self.wait_for_block(initial_height - num_blocks as usize);
534
535        debug!(
536            "Invalidated {} blocks to new height of {}",
537            num_blocks,
538            initial_height - num_blocks as usize
539        );
540    }
541
542    pub fn reorg(&mut self, num_blocks: u64) {
543        self.invalidate(num_blocks);
544        self.generate(num_blocks, None);
545    }
546
547    pub fn get_node_address(&self, address_type: Option<AddressType>) -> Address {
548        Address::from_str(
549            &self
550                .get_new_address(None, address_type)
551                .unwrap()
552                .to_string(),
553        )
554        .unwrap()
555    }
556}
557
558impl Deref for TestClient {
559    type Target = RpcClient;
560
561    fn deref(&self) -> &Self::Target {
562        &self.client
563    }
564}