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