bitcoind_async_client/
client.rs

1use std::{
2    env::var,
3    fmt,
4    sync::{
5        atomic::{AtomicUsize, Ordering},
6        Arc,
7    },
8    time::Duration,
9};
10
11use base64::{engine::general_purpose, Engine};
12use bitcoin::{
13    bip32::Xpriv,
14    block::Header,
15    consensus::{self, encode::serialize_hex},
16    Address, Block, BlockHash, Network, Transaction, Txid,
17};
18use reqwest::{
19    header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE},
20    Client as ReqwestClient,
21};
22use serde::{de, Deserialize, Serialize};
23use serde_json::{
24    json,
25    value::{RawValue, Value},
26};
27use tokio::time::sleep;
28use tracing::*;
29
30use super::types::GetBlockHeaderVerbosityZero;
31use crate::{
32    error::{BitcoinRpcError, ClientError},
33    traits::{Broadcaster, Reader, Signer, Wallet},
34    types::{
35        CreateRawTransaction, CreateWallet, GetBlockVerbosityOne, GetBlockVerbosityZero,
36        GetBlockchainInfo, GetMempoolInfo, GetNewAddress, GetRawTransactionVerbosityOne,
37        GetRawTransactionVerbosityZero, GetTransaction, GetTxOut, ImportDescriptor,
38        ImportDescriptorResult, ListDescriptors, ListTransactions, ListUnspent,
39        PreviousTransactionOutput, SignRawTransactionWithWallet, SubmitPackage, TestMempoolAccept,
40    },
41};
42
43/// This is an alias for the result type returned by the [`Client`].
44pub type ClientResult<T> = Result<T, ClientError>;
45
46/// The maximum number of retries for a request.
47const DEFAULT_MAX_RETRIES: u8 = 3;
48
49/// The maximum number of retries for a request.
50const DEFAULT_RETRY_INTERVAL_MS: u64 = 1_000;
51
52/// Custom implementation to convert a value to a `Value` type.
53pub fn to_value<T>(value: T) -> ClientResult<Value>
54where
55    T: Serialize,
56{
57    serde_json::to_value(value)
58        .map_err(|e| ClientError::Param(format!("Error creating value: {e}")))
59}
60
61/// An `async` client for interacting with a `bitcoind` instance.
62#[derive(Debug, Clone)]
63pub struct Client {
64    /// The URL of the `bitcoind` instance.
65    url: String,
66
67    /// The underlying `async` HTTP client.
68    client: ReqwestClient,
69
70    /// The ID of the current request.
71    ///
72    /// # Implementation Details
73    ///
74    /// Using an [`Arc`] so that [`Client`] is [`Clone`].
75    id: Arc<AtomicUsize>,
76
77    /// The maximum number of retries for a request.
78    max_retries: u8,
79
80    /// Interval between retries for a request in ms.
81    retry_interval: u64,
82}
83
84/// Response returned by the `bitcoind` RPC server.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86struct Response<R> {
87    pub result: Option<R>,
88    pub error: Option<BitcoinRpcError>,
89    pub id: u64,
90}
91
92impl Client {
93    /// Creates a new [`Client`] with the given URL, username, and password.
94    pub fn new(
95        url: String,
96        username: String,
97        password: String,
98        max_retries: Option<u8>,
99        retry_interval: Option<u64>,
100    ) -> ClientResult<Self> {
101        if username.is_empty() || password.is_empty() {
102            return Err(ClientError::MissingUserPassword);
103        }
104
105        let user_pw = general_purpose::STANDARD.encode(format!("{username}:{password}"));
106        let authorization = format!("Basic {user_pw}")
107            .parse()
108            .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
109
110        let content_type = "application/json"
111            .parse()
112            .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
113        let headers =
114            HeaderMap::from_iter([(AUTHORIZATION, authorization), (CONTENT_TYPE, content_type)]);
115
116        trace!(headers = ?headers);
117
118        let client = ReqwestClient::builder()
119            .default_headers(headers)
120            .build()
121            .map_err(|e| ClientError::Other(format!("Could not create client: {e}")))?;
122
123        let id = Arc::new(AtomicUsize::new(0));
124
125        let max_retries = max_retries.unwrap_or(DEFAULT_MAX_RETRIES);
126        let retry_interval = retry_interval.unwrap_or(DEFAULT_RETRY_INTERVAL_MS);
127
128        trace!(url = %url, "Created bitcoin client");
129
130        Ok(Self {
131            url,
132            client,
133            id,
134            max_retries,
135            retry_interval,
136        })
137    }
138
139    fn next_id(&self) -> usize {
140        self.id.fetch_add(1, Ordering::AcqRel)
141    }
142
143    async fn call<T: de::DeserializeOwned + fmt::Debug>(
144        &self,
145        method: &str,
146        params: &[Value],
147    ) -> ClientResult<T> {
148        let mut retries = 0;
149        loop {
150            trace!(%method, ?params, %retries, "Calling bitcoin client");
151
152            let id = self.next_id();
153
154            let response = self
155                .client
156                .post(&self.url)
157                .json(&json!({
158                    "jsonrpc": "1.0",
159                    "id": id,
160                    "method": method,
161                    "params": params
162                }))
163                .send()
164                .await;
165            trace!(?response, "Response received");
166            match response {
167                Ok(resp) => {
168                    let raw_response = resp
169                        .text()
170                        .await
171                        .map_err(|e| ClientError::Parse(e.to_string()))?;
172                    trace!(%raw_response, "Raw response received");
173                    let data: Response<T> = serde_json::from_str(&raw_response)
174                        .map_err(|e| ClientError::Parse(e.to_string()))?;
175                    if let Some(err) = data.error {
176                        return Err(ClientError::Server(err.code, err.message));
177                    }
178                    return data
179                        .result
180                        .ok_or_else(|| ClientError::Other("Empty data received".to_string()));
181                }
182                Err(err) => {
183                    warn!(err = %err, "Error calling bitcoin client");
184
185                    if err.is_body() {
186                        // Body error is unrecoverable
187                        return Err(ClientError::Body(err.to_string()));
188                    } else if err.is_status() {
189                        // Status error is unrecoverable
190                        let e = match err.status() {
191                            Some(code) => ClientError::Status(code.to_string(), err.to_string()),
192                            _ => ClientError::Other(err.to_string()),
193                        };
194                        return Err(e);
195                    } else if err.is_decode() {
196                        // Error decoding response, might be recoverable
197                        let e = ClientError::MalformedResponse(err.to_string());
198                        warn!(%e, "decoding error, retrying...");
199                    } else if err.is_connect() {
200                        // Connection error, might be recoverable
201                        let e = ClientError::Connection(err.to_string());
202                        warn!(%e, "connection error, retrying...");
203                    } else if err.is_timeout() {
204                        // Timeout error, might be recoverable
205                        let e = ClientError::Timeout;
206                        warn!(%e, "timeout error, retrying...");
207                    } else if err.is_request() {
208                        // General request error, might be recoverable
209                        let e = ClientError::Request(err.to_string());
210                        warn!(%e, "request error, retrying...");
211                    } else if err.is_builder() {
212                        // Request builder error is unrecoverable
213                        return Err(ClientError::ReqBuilder(err.to_string()));
214                    } else if err.is_redirect() {
215                        // Redirect error is unrecoverable
216                        return Err(ClientError::HttpRedirect(err.to_string()));
217                    } else {
218                        // Unknown error is unrecoverable
219                        return Err(ClientError::Other("Unknown error".to_string()));
220                    }
221                }
222            }
223            retries += 1;
224            if retries >= self.max_retries {
225                return Err(ClientError::MaxRetriesExceeded(self.max_retries));
226            }
227            sleep(Duration::from_millis(self.retry_interval)).await;
228        }
229    }
230}
231
232impl Reader for Client {
233    async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
234        let result = self
235            .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
236            .await?
237            .to_string();
238
239        let result_map: Value = result.parse::<Value>()?;
240
241        let btc_vkb = result_map
242            .get("feerate")
243            .unwrap_or(&"0.00001".parse::<Value>().unwrap())
244            .as_f64()
245            .unwrap();
246
247        // convert to sat/vB and round up
248        Ok((btc_vkb * 100_000_000.0 / 1000.0) as u64)
249    }
250
251    async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
252        let get_block_header = self
253            .call::<GetBlockHeaderVerbosityZero>(
254                "getblockheader",
255                &[to_value(hash.to_string())?, to_value(false)?],
256            )
257            .await?;
258        let header = get_block_header
259            .header()
260            .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
261        Ok(header)
262    }
263
264    async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
265        let get_block = self
266            .call::<GetBlockVerbosityZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
267            .await?;
268        let block = get_block
269            .block()
270            .map_err(|err| ClientError::Other(format!("block decode: {err}")))?;
271        Ok(block)
272    }
273
274    async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
275        let block_verobose = self
276            .call::<GetBlockVerbosityOne>("getblock", &[to_value(hash.to_string())?])
277            .await?;
278
279        let block_height = block_verobose.height as u64;
280        Ok(block_height)
281    }
282
283    async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
284        let hash = self.get_block_hash(height).await?;
285        self.get_block_header(&hash).await
286    }
287
288    async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
289        let hash = self.get_block_hash(height).await?;
290        self.get_block(&hash).await
291    }
292
293    async fn get_block_count(&self) -> ClientResult<u64> {
294        self.call::<u64>("getblockcount", &[]).await
295    }
296
297    async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
298        self.call::<BlockHash>("getblockhash", &[to_value(height)?])
299            .await
300    }
301
302    async fn get_blockchain_info(&self) -> ClientResult<GetBlockchainInfo> {
303        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
304            .await
305    }
306
307    async fn get_current_timestamp(&self) -> ClientResult<u32> {
308        let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
309        let block = self.get_block(&best_block_hash).await?;
310        Ok(block.header.time)
311    }
312
313    async fn get_raw_mempool(&self) -> ClientResult<Vec<Txid>> {
314        self.call::<Vec<Txid>>("getrawmempool", &[]).await
315    }
316
317    async fn get_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
318        self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
319    }
320
321    async fn get_raw_transaction_verbosity_zero(
322        &self,
323        txid: &Txid,
324    ) -> ClientResult<GetRawTransactionVerbosityZero> {
325        self.call::<GetRawTransactionVerbosityZero>(
326            "getrawtransaction",
327            &[to_value(txid.to_string())?, to_value(0)?],
328        )
329        .await
330    }
331
332    async fn get_raw_transaction_verbosity_one(
333        &self,
334        txid: &Txid,
335    ) -> ClientResult<GetRawTransactionVerbosityOne> {
336        self.call::<GetRawTransactionVerbosityOne>(
337            "getrawtransaction",
338            &[to_value(txid.to_string())?, to_value(1)?],
339        )
340        .await
341    }
342
343    async fn get_tx_out(
344        &self,
345        txid: &Txid,
346        vout: u32,
347        include_mempool: bool,
348    ) -> ClientResult<GetTxOut> {
349        self.call::<GetTxOut>(
350            "gettxout",
351            &[
352                to_value(txid.to_string())?,
353                to_value(vout)?,
354                to_value(include_mempool)?,
355            ],
356        )
357        .await
358    }
359
360    async fn network(&self) -> ClientResult<Network> {
361        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
362            .await?
363            .chain
364            .parse::<Network>()
365            .map_err(|e| ClientError::Parse(e.to_string()))
366    }
367}
368
369impl Broadcaster for Client {
370    async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
371        let txstr = serialize_hex(tx);
372        trace!(txstr = %txstr, "Sending raw transaction");
373        match self
374            .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
375            .await
376        {
377            Ok(txid) => {
378                trace!(?txid, "Transaction sent");
379                Ok(txid)
380            }
381            Err(ClientError::Server(i, s)) => match i {
382                // Dealing with known and common errors
383                -27 => Ok(tx.compute_txid()), // Tx already in chain
384                _ => Err(ClientError::Server(i, s)),
385            },
386            Err(e) => Err(ClientError::Other(e.to_string())),
387        }
388    }
389
390    async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
391        let txstr = serialize_hex(tx);
392        trace!(%txstr, "Testing mempool accept");
393        self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
394            .await
395    }
396
397    async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
398        let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
399        self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
400            .await
401    }
402}
403
404impl Wallet for Client {
405    async fn get_new_address(&self) -> ClientResult<Address> {
406        let address_unchecked = self
407            .call::<GetNewAddress>("getnewaddress", &[])
408            .await?
409            .0
410            .parse::<Address<_>>()
411            .map_err(|e| ClientError::Parse(e.to_string()))?
412            .assume_checked();
413        Ok(address_unchecked)
414    }
415    async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
416        self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
417            .await
418    }
419
420    async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
421        let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
422        trace!(?resp, "Got UTXOs");
423        Ok(resp)
424    }
425
426    async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
427        self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
428            .await
429    }
430
431    async fn list_wallets(&self) -> ClientResult<Vec<String>> {
432        self.call::<Vec<String>>("listwallets", &[]).await
433    }
434
435    async fn create_raw_transaction(
436        &self,
437        raw_tx: CreateRawTransaction,
438    ) -> ClientResult<Transaction> {
439        let raw_tx = self
440            .call::<String>(
441                "createrawtransaction",
442                &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
443            )
444            .await?;
445        trace!(%raw_tx, "Created raw transaction");
446        consensus::encode::deserialize_hex(&raw_tx)
447            .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
448    }
449}
450
451impl Signer for Client {
452    async fn sign_raw_transaction_with_wallet(
453        &self,
454        tx: &Transaction,
455        prev_outputs: Option<Vec<PreviousTransactionOutput>>,
456    ) -> ClientResult<SignRawTransactionWithWallet> {
457        let tx_hex = serialize_hex(tx);
458        trace!(tx_hex = %tx_hex, "Signing transaction");
459        trace!(?prev_outputs, "Signing transaction with previous outputs");
460        self.call::<SignRawTransactionWithWallet>(
461            "signrawtransactionwithwallet",
462            &[to_value(tx_hex)?, to_value(prev_outputs)?],
463        )
464        .await
465    }
466
467    async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
468        // If the ENV variable `BITCOIN_XPRIV_RETRIEVABLE` is not set, we return `None`
469        if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
470            return Ok(None);
471        }
472
473        let descriptors = self
474            .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) // true is the xpriv, false is the xpub
475            .await?
476            .descriptors;
477        if descriptors.is_empty() {
478            return Err(ClientError::Other("No descriptors found".to_string()));
479        }
480
481        // We are only interested in the one that contains `tr(`
482        let descriptor = descriptors
483            .iter()
484            .find(|d| d.desc.contains("tr("))
485            .map(|d| d.desc.clone())
486            .ok_or(ClientError::Xpriv)?;
487
488        // Now we extract the xpriv from the `tr()` up to the first `/`
489        let xpriv_str = descriptor
490            .split("tr(")
491            .nth(1)
492            .ok_or(ClientError::Xpriv)?
493            .split("/")
494            .next()
495            .ok_or(ClientError::Xpriv)?;
496
497        let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
498        Ok(Some(xpriv))
499    }
500
501    async fn import_descriptors(
502        &self,
503        descriptors: Vec<ImportDescriptor>,
504        wallet_name: String,
505    ) -> ClientResult<Vec<ImportDescriptorResult>> {
506        let wallet_args = CreateWallet {
507            wallet_name,
508            load_on_startup: Some(true),
509        };
510
511        // TODO: this should check for -35 error code which is good,
512        //       means that is already created
513        let _wallet_create = self
514            .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
515            .await;
516        // TODO: this should check for -35 error code which is good, -18 is bad.
517        let _wallet_load = self
518            .call::<Value>("loadwallet", &[to_value(wallet_args)?])
519            .await;
520
521        let result = self
522            .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
523            .await?;
524        Ok(result)
525    }
526}
527
528#[cfg(test)]
529mod test {
530
531    use std::sync::Once;
532
533    use bitcoin::{
534        consensus::{self, encode::deserialize_hex},
535        hashes::Hash,
536        transaction, Amount, NetworkKind,
537    };
538    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
539
540    use super::*;
541    use crate::{
542        test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
543        types::{CreateRawTransactionInput, CreateRawTransactionOutput},
544    };
545
546    /// 50 BTC in [`Network::Regtest`].
547    const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
548
549    /// Only attempts to start tracing once.
550    fn init_tracing() {
551        static INIT: Once = Once::new();
552
553        INIT.call_once(|| {
554            tracing_subscriber::registry()
555                .with(fmt::layer())
556                .with(EnvFilter::from_default_env())
557                .try_init()
558                .ok();
559        });
560    }
561
562    #[tokio::test()]
563    async fn client_works() {
564        init_tracing();
565
566        let (bitcoind, client) = get_bitcoind_and_client();
567
568        // network
569        let got = client.network().await.unwrap();
570        let expected = Network::Regtest;
571
572        assert_eq!(expected, got);
573        // get_blockchain_info
574        let get_blockchain_info = client.get_blockchain_info().await.unwrap();
575        assert_eq!(get_blockchain_info.blocks, 0);
576
577        // get_current_timestamp
578        let _ = client
579            .get_current_timestamp()
580            .await
581            .expect("must be able to get current timestamp");
582
583        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
584
585        // get_block
586        let expected = blocks.last().unwrap();
587        let got = client.get_block(expected).await.unwrap().block_hash();
588        assert_eq!(*expected, got);
589
590        // get_block_at
591        let target_height = blocks.len() as u64;
592        let expected = blocks.last().unwrap();
593        let got = client
594            .get_block_at(target_height)
595            .await
596            .unwrap()
597            .block_hash();
598        assert_eq!(*expected, got);
599
600        // get_block_count
601        let expected = blocks.len() as u64;
602        let got = client.get_block_count().await.unwrap();
603        assert_eq!(expected, got);
604
605        // get_block_hash
606        let target_height = blocks.len() as u64;
607        let expected = blocks.last().unwrap();
608        let got = client.get_block_hash(target_height).await.unwrap();
609        assert_eq!(*expected, got);
610
611        // get_new_address
612        let address = client.get_new_address().await.unwrap();
613        let txid = client
614            .call::<String>(
615                "sendtoaddress",
616                &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
617            )
618            .await
619            .unwrap()
620            .parse::<Txid>()
621            .unwrap();
622
623        // get_transaction
624        let tx = client.get_transaction(&txid).await.unwrap().hex;
625        let got = client.send_raw_transaction(&tx).await.unwrap();
626        let expected = txid; // Don't touch this!
627        assert_eq!(expected, got);
628
629        // get_raw_transaction_verbosity_zero
630        let got = client
631            .get_raw_transaction_verbosity_zero(&txid)
632            .await
633            .unwrap()
634            .0;
635        let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
636        assert_eq!(expected, got);
637
638        // get_raw_transaction_verbosity_one
639        let got = client
640            .get_raw_transaction_verbosity_one(&txid)
641            .await
642            .unwrap()
643            .transaction
644            .compute_txid();
645        assert_eq!(expected, got);
646
647        // get_raw_mempool
648        let got = client.get_raw_mempool().await.unwrap();
649        let expected = vec![txid];
650        assert_eq!(expected, got);
651
652        // get_mempool_info
653        let got = client.get_mempool_info().await.unwrap();
654        assert!(got.loaded);
655        assert_eq!(got.size, 1);
656        assert_eq!(got.unbroadcastcount, 1);
657
658        // estimate_smart_fee
659        let got = client.estimate_smart_fee(1).await.unwrap();
660        let expected = 1; // 1 sat/vB
661        assert_eq!(expected, got);
662
663        // sign_raw_transaction_with_wallet
664        let got = client
665            .sign_raw_transaction_with_wallet(&tx, None)
666            .await
667            .unwrap();
668        assert!(got.complete);
669        assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
670
671        // test_mempool_accept
672        let txids = client
673            .test_mempool_accept(&tx)
674            .await
675            .expect("must be able to test mempool accept");
676        let got = txids.first().expect("there must be at least one txid");
677        assert_eq!(
678            got.txid,
679            tx.compute_txid(),
680            "txids must match in the mempool"
681        );
682
683        // send_raw_transaction
684        let got = client.send_raw_transaction(&tx).await.unwrap();
685        assert!(got.as_byte_array().len() == 32);
686
687        // list_transactions
688        let got = client.list_transactions(None).await.unwrap();
689        assert_eq!(got.len(), 10);
690
691        // get_utxos
692        // let's mine one more block
693        mine_blocks(&bitcoind, 1, None).unwrap();
694        let got = client.get_utxos().await.unwrap();
695        assert_eq!(got.len(), 3);
696
697        // listdescriptors
698        let got = client.get_xpriv().await.unwrap().unwrap().network;
699        let expected = NetworkKind::Test;
700        assert_eq!(expected, got);
701
702        // importdescriptors
703        // taken from https://github.com/rust-bitcoin/rust-bitcoin/blob/bb38aeb786f408247d5bbc88b9fa13616c74c009/bitcoin/examples/taproot-psbt.rs#L18C38-L18C149
704        let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
705        let timestamp = "now".to_owned();
706        let list_descriptors = vec![ImportDescriptor {
707            desc: descriptor_string,
708            active: Some(true),
709            timestamp,
710        }];
711        let got = client
712            .import_descriptors(list_descriptors, "strata".to_owned())
713            .await
714            .unwrap();
715        let expected = vec![ImportDescriptorResult { success: true }];
716        assert_eq!(expected, got);
717    }
718
719    #[tokio::test()]
720    async fn get_tx_out() {
721        init_tracing();
722
723        let (bitcoind, client) = get_bitcoind_and_client();
724
725        // network sanity check
726        let got = client.network().await.unwrap();
727        let expected = Network::Regtest;
728        assert_eq!(expected, got);
729
730        let address = bitcoind.client.new_address().unwrap();
731        let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
732        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
733        let coinbase_tx = last_block.coinbase().unwrap();
734
735        // gettxout should work with a non-spent UTXO.
736        let got = client
737            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
738            .await
739            .unwrap();
740        assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
741
742        // gettxout should fail with a spent UTXO.
743        let new_address = bitcoind.client.new_address().unwrap();
744        let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); // 2k sats as fees.
745        let _send_tx = bitcoind
746            .client
747            .send_to_address(&new_address, send_amount)
748            .unwrap()
749            .txid()
750            .unwrap();
751        let result = client
752            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
753            .await;
754        trace!(?result, "gettxout result");
755        assert!(result.is_err());
756    }
757
758    /// Create two transactions.
759    /// 1. Normal one: sends 1 BTC to an address that we control.
760    /// 2. CFFP: replaces the first transaction with a different one that we also control.
761    ///
762    /// This is needed because we must SIGN all these transactions, and we can't sign a transaction
763    /// that we don't control.
764    #[tokio::test()]
765    async fn submit_package() {
766        init_tracing();
767
768        let (bitcoind, client) = get_bitcoind_and_client();
769
770        // network sanity check
771        let got = client.network().await.unwrap();
772        let expected = Network::Regtest;
773        assert_eq!(expected, got);
774
775        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
776        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
777        let coinbase_tx = last_block.coinbase().unwrap();
778
779        let destination = client.get_new_address().await.unwrap();
780        let change_address = client.get_new_address().await.unwrap();
781        let amount = Amount::from_btc(1.0).unwrap();
782        let fees = Amount::from_btc(0.0001).unwrap();
783        let change_amount = COINBASE_AMOUNT - amount - fees;
784        let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
785
786        let send_back_address = client.get_new_address().await.unwrap();
787        let parent_raw_tx = CreateRawTransaction {
788            inputs: vec![CreateRawTransactionInput {
789                txid: coinbase_tx.compute_txid().to_string(),
790                vout: 0,
791            }],
792            outputs: vec![
793                // Destination
794                CreateRawTransactionOutput::AddressAmount {
795                    address: destination.to_string(),
796                    amount: amount.to_btc(),
797                },
798                // Change
799                CreateRawTransactionOutput::AddressAmount {
800                    address: change_address.to_string(),
801                    amount: change_amount.to_btc(),
802                },
803            ],
804        };
805        let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
806        let signed_parent: Transaction = consensus::encode::deserialize_hex(
807            client
808                .sign_raw_transaction_with_wallet(&parent, None)
809                .await
810                .unwrap()
811                .hex
812                .as_str(),
813        )
814        .unwrap();
815
816        // sanity check
817        let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
818
819        let child_raw_tx = CreateRawTransaction {
820            inputs: vec![CreateRawTransactionInput {
821                txid: parent_submitted.to_string(),
822                vout: 0,
823            }],
824            outputs: vec![
825                // Send back
826                CreateRawTransactionOutput::AddressAmount {
827                    address: send_back_address.to_string(),
828                    amount: amount_minus_fees.to_btc(),
829                },
830            ],
831        };
832        let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
833        let signed_child: Transaction = consensus::encode::deserialize_hex(
834            client
835                .sign_raw_transaction_with_wallet(&child, None)
836                .await
837                .unwrap()
838                .hex
839                .as_str(),
840        )
841        .unwrap();
842
843        // Ok now we have a parent and a child transaction.
844        let result = client
845            .submit_package(&[signed_parent, signed_child])
846            .await
847            .unwrap();
848        assert_eq!(result.tx_results.len(), 2);
849        assert_eq!(result.package_msg, "success");
850    }
851
852    /// Similar to [`submit_package`], but with where the parent does not pay fees,
853    /// and the child has to pay fees.
854    ///
855    /// This is called 1P1C because it has one parent and one child.
856    /// See <https://bitcoinops.org/en/bitcoin-core-28-wallet-integration-guide/>
857    /// for more information.
858    #[tokio::test]
859    async fn submit_package_1p1c() {
860        init_tracing();
861
862        let (bitcoind, client) = get_bitcoind_and_client();
863
864        // 1p1c sanity check
865        let server_version = bitcoind.client.server_version().unwrap();
866        assert!(server_version > 28);
867
868        let destination = client.get_new_address().await.unwrap();
869
870        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
871        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
872        let coinbase_tx = last_block.coinbase().unwrap();
873
874        let parent_raw_tx = CreateRawTransaction {
875            inputs: vec![CreateRawTransactionInput {
876                txid: coinbase_tx.compute_txid().to_string(),
877                vout: 0,
878            }],
879            outputs: vec![CreateRawTransactionOutput::AddressAmount {
880                address: destination.to_string(),
881                amount: COINBASE_AMOUNT.to_btc(),
882            }],
883        };
884        let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
885        parent.version = transaction::Version(3);
886        assert_eq!(parent.version, transaction::Version(3));
887        trace!(?parent, "parent:");
888        let signed_parent: Transaction = consensus::encode::deserialize_hex(
889            client
890                .sign_raw_transaction_with_wallet(&parent, None)
891                .await
892                .unwrap()
893                .hex
894                .as_str(),
895        )
896        .unwrap();
897        assert_eq!(signed_parent.version, transaction::Version(3));
898
899        // Assert that the parent tx cannot be broadcasted.
900        let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
901        assert!(parent_broadcasted.is_err());
902
903        // 5k sats as fees.
904        let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
905        let child_raw_tx = CreateRawTransaction {
906            inputs: vec![CreateRawTransactionInput {
907                txid: signed_parent.compute_txid().to_string(),
908                vout: 0,
909            }],
910            outputs: vec![CreateRawTransactionOutput::AddressAmount {
911                address: destination.to_string(),
912                amount: amount_minus_fees.to_btc(),
913            }],
914        };
915        let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
916        child.version = transaction::Version(3);
917        assert_eq!(child.version, transaction::Version(3));
918        trace!(?child, "child:");
919        let prev_outputs = vec![PreviousTransactionOutput {
920            txid: parent.compute_txid(),
921            vout: 0,
922            script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
923            redeem_script: None,
924            witness_script: None,
925            amount: Some(COINBASE_AMOUNT.to_btc()),
926        }];
927        let signed_child: Transaction = consensus::encode::deserialize_hex(
928            client
929                .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
930                .await
931                .unwrap()
932                .hex
933                .as_str(),
934        )
935        .unwrap();
936        assert_eq!(signed_child.version, transaction::Version(3));
937
938        // Assert that the child tx cannot be broadcasted.
939        let child_broadcasted = client.send_raw_transaction(&signed_child).await;
940        assert!(child_broadcasted.is_err());
941
942        // Let's send as a package 1C1P.
943        let result = client
944            .submit_package(&[signed_parent, signed_child])
945            .await
946            .unwrap();
947        assert_eq!(result.tx_results.len(), 2);
948        assert_eq!(result.package_msg, "success");
949    }
950}