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, CreateRawTransactionInput, CreateRawTransactionOutput, CreateWallet,
36        GetAddressInfo, GetBlockVerbosityOne, GetBlockVerbosityZero, GetBlockchainInfo,
37        GetMempoolInfo, GetNewAddress, GetRawTransactionVerbosityOne,
38        GetRawTransactionVerbosityZero, GetTransaction, GetTxOut, ImportDescriptor,
39        ImportDescriptorResult, ListDescriptors, ListTransactions, ListUnspent,
40        ListUnspentQueryOptions, PreviousTransactionOutput, PsbtBumpFee, PsbtBumpFeeOptions,
41        SighashType, SignRawTransactionWithWallet, SubmitPackage, TestMempoolAccept,
42        WalletCreateFundedPsbt, WalletCreateFundedPsbtOptions, WalletProcessPsbtResult,
43    },
44};
45
46/// This is an alias for the result type returned by the [`Client`].
47pub type ClientResult<T> = Result<T, ClientError>;
48
49/// The maximum number of retries for a request.
50const DEFAULT_MAX_RETRIES: u8 = 3;
51
52/// The maximum number of retries for a request.
53const DEFAULT_RETRY_INTERVAL_MS: u64 = 1_000;
54
55/// Custom implementation to convert a value to a `Value` type.
56pub fn to_value<T>(value: T) -> ClientResult<Value>
57where
58    T: Serialize,
59{
60    serde_json::to_value(value)
61        .map_err(|e| ClientError::Param(format!("Error creating value: {e}")))
62}
63
64/// An `async` client for interacting with a `bitcoind` instance.
65#[derive(Debug, Clone)]
66pub struct Client {
67    /// The URL of the `bitcoind` instance.
68    url: String,
69
70    /// The underlying `async` HTTP client.
71    client: ReqwestClient,
72
73    /// The ID of the current request.
74    ///
75    /// # Implementation Details
76    ///
77    /// Using an [`Arc`] so that [`Client`] is [`Clone`].
78    id: Arc<AtomicUsize>,
79
80    /// The maximum number of retries for a request.
81    max_retries: u8,
82
83    /// Interval between retries for a request in ms.
84    retry_interval: u64,
85}
86
87/// Response returned by the `bitcoind` RPC server.
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89struct Response<R> {
90    pub result: Option<R>,
91    pub error: Option<BitcoinRpcError>,
92    pub id: u64,
93}
94
95impl Client {
96    /// Creates a new [`Client`] with the given URL, username, and password.
97    pub fn new(
98        url: String,
99        username: String,
100        password: String,
101        max_retries: Option<u8>,
102        retry_interval: Option<u64>,
103    ) -> ClientResult<Self> {
104        if username.is_empty() || password.is_empty() {
105            return Err(ClientError::MissingUserPassword);
106        }
107
108        let user_pw = general_purpose::STANDARD.encode(format!("{username}:{password}"));
109        let authorization = format!("Basic {user_pw}")
110            .parse()
111            .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
112
113        let content_type = "application/json"
114            .parse()
115            .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
116        let headers =
117            HeaderMap::from_iter([(AUTHORIZATION, authorization), (CONTENT_TYPE, content_type)]);
118
119        trace!(headers = ?headers);
120
121        let client = ReqwestClient::builder()
122            .default_headers(headers)
123            .build()
124            .map_err(|e| ClientError::Other(format!("Could not create client: {e}")))?;
125
126        let id = Arc::new(AtomicUsize::new(0));
127
128        let max_retries = max_retries.unwrap_or(DEFAULT_MAX_RETRIES);
129        let retry_interval = retry_interval.unwrap_or(DEFAULT_RETRY_INTERVAL_MS);
130
131        trace!(url = %url, "Created bitcoin client");
132
133        Ok(Self {
134            url,
135            client,
136            id,
137            max_retries,
138            retry_interval,
139        })
140    }
141
142    fn next_id(&self) -> usize {
143        self.id.fetch_add(1, Ordering::AcqRel)
144    }
145
146    async fn call<T: de::DeserializeOwned + fmt::Debug>(
147        &self,
148        method: &str,
149        params: &[Value],
150    ) -> ClientResult<T> {
151        let mut retries = 0;
152        loop {
153            trace!(%method, ?params, %retries, "Calling bitcoin client");
154
155            let id = self.next_id();
156
157            let response = self
158                .client
159                .post(&self.url)
160                .json(&json!({
161                    "jsonrpc": "1.0",
162                    "id": id,
163                    "method": method,
164                    "params": params
165                }))
166                .send()
167                .await;
168            trace!(?response, "Response received");
169            match response {
170                Ok(resp) => {
171                    // Check HTTP status code first before parsing body
172                    let resp = match resp.error_for_status() {
173                        Err(e) if e.is_status() => {
174                            if let Some(status) = e.status() {
175                                let reason =
176                                    status.canonical_reason().unwrap_or("Unknown").to_string();
177                                return Err(ClientError::Status(status.as_u16(), reason));
178                            } else {
179                                return Err(ClientError::Other(e.to_string()));
180                            }
181                        }
182                        Err(e) => {
183                            return Err(ClientError::Other(e.to_string()));
184                        }
185                        Ok(resp) => resp,
186                    };
187
188                    let raw_response = resp
189                        .text()
190                        .await
191                        .map_err(|e| ClientError::Parse(e.to_string()))?;
192                    trace!(%raw_response, "Raw response received");
193                    let data: Response<T> = serde_json::from_str(&raw_response)
194                        .map_err(|e| ClientError::Parse(e.to_string()))?;
195                    if let Some(err) = data.error {
196                        return Err(ClientError::Server(err.code, err.message));
197                    }
198                    return data
199                        .result
200                        .ok_or_else(|| ClientError::Other("Empty data received".to_string()));
201                }
202                Err(err) => {
203                    warn!(err = %err, "Error calling bitcoin client");
204
205                    if err.is_body() {
206                        // Body error is unrecoverable
207                        return Err(ClientError::Body(err.to_string()));
208                    } else if err.is_status() {
209                        // Status error is unrecoverable
210                        let e = match err.status() {
211                            Some(code) => ClientError::Status(code.as_u16(), err.to_string()),
212                            _ => ClientError::Other(err.to_string()),
213                        };
214                        return Err(e);
215                    } else if err.is_decode() {
216                        // Error decoding response, might be recoverable
217                        let e = ClientError::MalformedResponse(err.to_string());
218                        warn!(%e, "decoding error, retrying...");
219                    } else if err.is_connect() {
220                        // Connection error, might be recoverable
221                        let e = ClientError::Connection(err.to_string());
222                        warn!(%e, "connection error, retrying...");
223                    } else if err.is_timeout() {
224                        // Timeout error, might be recoverable
225                        let e = ClientError::Timeout;
226                        warn!(%e, "timeout error, retrying...");
227                    } else if err.is_request() {
228                        // General request error, might be recoverable
229                        let e = ClientError::Request(err.to_string());
230                        warn!(%e, "request error, retrying...");
231                    } else if err.is_builder() {
232                        // Request builder error is unrecoverable
233                        return Err(ClientError::ReqBuilder(err.to_string()));
234                    } else if err.is_redirect() {
235                        // Redirect error is unrecoverable
236                        return Err(ClientError::HttpRedirect(err.to_string()));
237                    } else {
238                        // Unknown error is unrecoverable
239                        return Err(ClientError::Other("Unknown error".to_string()));
240                    }
241                }
242            }
243            retries += 1;
244            if retries >= self.max_retries {
245                return Err(ClientError::MaxRetriesExceeded(self.max_retries));
246            }
247            sleep(Duration::from_millis(self.retry_interval)).await;
248        }
249    }
250}
251
252impl Reader for Client {
253    async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
254        let result = self
255            .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
256            .await?
257            .to_string();
258
259        let result_map: Value = result.parse::<Value>()?;
260
261        let btc_vkb = result_map
262            .get("feerate")
263            .unwrap_or(&"0.00001".parse::<Value>().unwrap())
264            .as_f64()
265            .unwrap();
266
267        // convert to sat/vB and round up
268        Ok((btc_vkb * 100_000_000.0 / 1000.0) as u64)
269    }
270
271    async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
272        let get_block_header = self
273            .call::<GetBlockHeaderVerbosityZero>(
274                "getblockheader",
275                &[to_value(hash.to_string())?, to_value(false)?],
276            )
277            .await?;
278        let header = get_block_header
279            .header()
280            .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
281        Ok(header)
282    }
283
284    async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
285        let get_block = self
286            .call::<GetBlockVerbosityZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
287            .await?;
288        let block = get_block
289            .block()
290            .map_err(|err| ClientError::Other(format!("block decode: {err}")))?;
291        Ok(block)
292    }
293
294    async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
295        let block_verobose = self
296            .call::<GetBlockVerbosityOne>("getblock", &[to_value(hash.to_string())?])
297            .await?;
298
299        let block_height = block_verobose.height as u64;
300        Ok(block_height)
301    }
302
303    async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
304        let hash = self.get_block_hash(height).await?;
305        self.get_block_header(&hash).await
306    }
307
308    async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
309        let hash = self.get_block_hash(height).await?;
310        self.get_block(&hash).await
311    }
312
313    async fn get_block_count(&self) -> ClientResult<u64> {
314        self.call::<u64>("getblockcount", &[]).await
315    }
316
317    async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
318        self.call::<BlockHash>("getblockhash", &[to_value(height)?])
319            .await
320    }
321
322    async fn get_blockchain_info(&self) -> ClientResult<GetBlockchainInfo> {
323        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
324            .await
325    }
326
327    async fn get_current_timestamp(&self) -> ClientResult<u32> {
328        let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
329        let block = self.get_block(&best_block_hash).await?;
330        Ok(block.header.time)
331    }
332
333    async fn get_raw_mempool(&self) -> ClientResult<Vec<Txid>> {
334        self.call::<Vec<Txid>>("getrawmempool", &[]).await
335    }
336
337    async fn get_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
338        self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
339    }
340
341    async fn get_raw_transaction_verbosity_zero(
342        &self,
343        txid: &Txid,
344    ) -> ClientResult<GetRawTransactionVerbosityZero> {
345        self.call::<GetRawTransactionVerbosityZero>(
346            "getrawtransaction",
347            &[to_value(txid.to_string())?, to_value(0)?],
348        )
349        .await
350    }
351
352    async fn get_raw_transaction_verbosity_one(
353        &self,
354        txid: &Txid,
355    ) -> ClientResult<GetRawTransactionVerbosityOne> {
356        self.call::<GetRawTransactionVerbosityOne>(
357            "getrawtransaction",
358            &[to_value(txid.to_string())?, to_value(1)?],
359        )
360        .await
361    }
362
363    async fn get_tx_out(
364        &self,
365        txid: &Txid,
366        vout: u32,
367        include_mempool: bool,
368    ) -> ClientResult<GetTxOut> {
369        self.call::<GetTxOut>(
370            "gettxout",
371            &[
372                to_value(txid.to_string())?,
373                to_value(vout)?,
374                to_value(include_mempool)?,
375            ],
376        )
377        .await
378    }
379
380    async fn network(&self) -> ClientResult<Network> {
381        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
382            .await?
383            .chain
384            .parse::<Network>()
385            .map_err(|e| ClientError::Parse(e.to_string()))
386    }
387}
388
389impl Broadcaster for Client {
390    async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
391        let txstr = serialize_hex(tx);
392        trace!(txstr = %txstr, "Sending raw transaction");
393        match self
394            .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
395            .await
396        {
397            Ok(txid) => {
398                trace!(?txid, "Transaction sent");
399                Ok(txid)
400            }
401            Err(ClientError::Server(i, s)) => match i {
402                // Dealing with known and common errors
403                -27 => Ok(tx.compute_txid()), // Tx already in chain
404                _ => Err(ClientError::Server(i, s)),
405            },
406            Err(e) => Err(ClientError::Other(e.to_string())),
407        }
408    }
409
410    async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
411        let txstr = serialize_hex(tx);
412        trace!(%txstr, "Testing mempool accept");
413        self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
414            .await
415    }
416
417    async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
418        let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
419        self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
420            .await
421    }
422}
423
424impl Wallet for Client {
425    async fn get_new_address(&self) -> ClientResult<Address> {
426        let address_unchecked = self
427            .call::<GetNewAddress>("getnewaddress", &[])
428            .await?
429            .0
430            .parse::<Address<_>>()
431            .map_err(|e| ClientError::Parse(e.to_string()))?
432            .assume_checked();
433        Ok(address_unchecked)
434    }
435    async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
436        self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
437            .await
438    }
439
440    async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
441        let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
442        trace!(?resp, "Got UTXOs");
443        Ok(resp)
444    }
445
446    async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
447        self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
448            .await
449    }
450
451    async fn list_wallets(&self) -> ClientResult<Vec<String>> {
452        self.call::<Vec<String>>("listwallets", &[]).await
453    }
454
455    async fn create_raw_transaction(
456        &self,
457        raw_tx: CreateRawTransaction,
458    ) -> ClientResult<Transaction> {
459        let raw_tx = self
460            .call::<String>(
461                "createrawtransaction",
462                &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
463            )
464            .await?;
465        trace!(%raw_tx, "Created raw transaction");
466        consensus::encode::deserialize_hex(&raw_tx)
467            .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
468    }
469
470    async fn wallet_create_funded_psbt(
471        &self,
472        inputs: &[CreateRawTransactionInput],
473        outputs: &[CreateRawTransactionOutput],
474        locktime: Option<u32>,
475        options: Option<WalletCreateFundedPsbtOptions>,
476        bip32_derivs: Option<bool>,
477    ) -> ClientResult<WalletCreateFundedPsbt> {
478        self.call::<WalletCreateFundedPsbt>(
479            "walletcreatefundedpsbt",
480            &[
481                to_value(inputs)?,
482                to_value(outputs)?,
483                to_value(locktime.unwrap_or(0))?,
484                to_value(options.unwrap_or_default())?,
485                to_value(bip32_derivs)?,
486            ],
487        )
488        .await
489    }
490
491    async fn get_address_info(&self, address: &Address) -> ClientResult<GetAddressInfo> {
492        trace!(address = %address, "Getting address info");
493        self.call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
494            .await
495    }
496
497    async fn list_unspent(
498        &self,
499        min_conf: Option<u32>,
500        max_conf: Option<u32>,
501        addresses: Option<&[Address]>,
502        include_unsafe: Option<bool>,
503        query_options: Option<ListUnspentQueryOptions>,
504    ) -> ClientResult<Vec<ListUnspent>> {
505        let addr_strings: Vec<String> = addresses
506            .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
507            .unwrap_or_default();
508
509        let mut params = vec![
510            to_value(min_conf.unwrap_or(1))?,
511            to_value(max_conf.unwrap_or(9_999_999))?,
512            to_value(addr_strings)?,
513            to_value(include_unsafe.unwrap_or(true))?,
514        ];
515
516        if let Some(query_options) = query_options {
517            params.push(to_value(query_options)?);
518        }
519
520        self.call::<Vec<ListUnspent>>("listunspent", &params).await
521    }
522}
523
524impl Signer for Client {
525    async fn sign_raw_transaction_with_wallet(
526        &self,
527        tx: &Transaction,
528        prev_outputs: Option<Vec<PreviousTransactionOutput>>,
529    ) -> ClientResult<SignRawTransactionWithWallet> {
530        let tx_hex = serialize_hex(tx);
531        trace!(tx_hex = %tx_hex, "Signing transaction");
532        trace!(?prev_outputs, "Signing transaction with previous outputs");
533        self.call::<SignRawTransactionWithWallet>(
534            "signrawtransactionwithwallet",
535            &[to_value(tx_hex)?, to_value(prev_outputs)?],
536        )
537        .await
538    }
539
540    async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
541        // If the ENV variable `BITCOIN_XPRIV_RETRIEVABLE` is not set, we return `None`
542        if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
543            return Ok(None);
544        }
545
546        let descriptors = self
547            .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) // true is the xpriv, false is the xpub
548            .await?
549            .descriptors;
550        if descriptors.is_empty() {
551            return Err(ClientError::Other("No descriptors found".to_string()));
552        }
553
554        // We are only interested in the one that contains `tr(`
555        let descriptor = descriptors
556            .iter()
557            .find(|d| d.desc.contains("tr("))
558            .map(|d| d.desc.clone())
559            .ok_or(ClientError::Xpriv)?;
560
561        // Now we extract the xpriv from the `tr()` up to the first `/`
562        let xpriv_str = descriptor
563            .split("tr(")
564            .nth(1)
565            .ok_or(ClientError::Xpriv)?
566            .split("/")
567            .next()
568            .ok_or(ClientError::Xpriv)?;
569
570        let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
571        Ok(Some(xpriv))
572    }
573
574    async fn import_descriptors(
575        &self,
576        descriptors: Vec<ImportDescriptor>,
577        wallet_name: String,
578    ) -> ClientResult<Vec<ImportDescriptorResult>> {
579        let wallet_args = CreateWallet {
580            wallet_name,
581            load_on_startup: Some(true),
582        };
583
584        // TODO: this should check for -35 error code which is good,
585        //       means that is already created
586        let _wallet_create = self
587            .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
588            .await;
589        // TODO: this should check for -35 error code which is good, -18 is bad.
590        let _wallet_load = self
591            .call::<Value>("loadwallet", &[to_value(wallet_args)?])
592            .await;
593
594        let result = self
595            .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
596            .await?;
597        Ok(result)
598    }
599
600    async fn wallet_process_psbt(
601        &self,
602        psbt: &str,
603        sign: Option<bool>,
604        sighashtype: Option<SighashType>,
605        bip32_derivs: Option<bool>,
606    ) -> ClientResult<WalletProcessPsbtResult> {
607        let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
608
609        if let Some(sighashtype) = sighashtype {
610            params.push(to_value(sighashtype)?);
611        }
612
613        if let Some(bip32_derivs) = bip32_derivs {
614            params.push(to_value(bip32_derivs)?);
615        }
616
617        self.call::<WalletProcessPsbtResult>("walletprocesspsbt", &params)
618            .await
619    }
620
621    async fn psbt_bump_fee(
622        &self,
623        txid: &Txid,
624        options: Option<PsbtBumpFeeOptions>,
625    ) -> ClientResult<PsbtBumpFee> {
626        let mut params = vec![to_value(txid.to_string())?];
627
628        if let Some(options) = options {
629            params.push(to_value(options)?);
630        }
631
632        self.call::<PsbtBumpFee>("psbtbumpfee", &params).await
633    }
634}
635
636#[cfg(test)]
637mod test {
638
639    use std::sync::Once;
640
641    use bitcoin::{
642        consensus::{self, encode::deserialize_hex},
643        hashes::Hash,
644        transaction, Amount, FeeRate, NetworkKind,
645    };
646    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
647
648    use super::*;
649    use crate::{
650        test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
651        types::{CreateRawTransactionInput, CreateRawTransactionOutput},
652    };
653
654    /// 50 BTC in [`Network::Regtest`].
655    const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
656
657    /// Only attempts to start tracing once.
658    fn init_tracing() {
659        static INIT: Once = Once::new();
660
661        INIT.call_once(|| {
662            tracing_subscriber::registry()
663                .with(fmt::layer())
664                .with(EnvFilter::from_default_env())
665                .try_init()
666                .ok();
667        });
668    }
669
670    #[tokio::test()]
671    async fn client_works() {
672        init_tracing();
673
674        let (bitcoind, client) = get_bitcoind_and_client();
675
676        // network
677        let got = client.network().await.unwrap();
678        let expected = Network::Regtest;
679
680        assert_eq!(expected, got);
681        // get_blockchain_info
682        let get_blockchain_info = client.get_blockchain_info().await.unwrap();
683        assert_eq!(get_blockchain_info.blocks, 0);
684
685        // get_current_timestamp
686        let _ = client
687            .get_current_timestamp()
688            .await
689            .expect("must be able to get current timestamp");
690
691        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
692
693        // get_block
694        let expected = blocks.last().unwrap();
695        let got = client.get_block(expected).await.unwrap().block_hash();
696        assert_eq!(*expected, got);
697
698        // get_block_at
699        let target_height = blocks.len() as u64;
700        let expected = blocks.last().unwrap();
701        let got = client
702            .get_block_at(target_height)
703            .await
704            .unwrap()
705            .block_hash();
706        assert_eq!(*expected, got);
707
708        // get_block_count
709        let expected = blocks.len() as u64;
710        let got = client.get_block_count().await.unwrap();
711        assert_eq!(expected, got);
712
713        // get_block_hash
714        let target_height = blocks.len() as u64;
715        let expected = blocks.last().unwrap();
716        let got = client.get_block_hash(target_height).await.unwrap();
717        assert_eq!(*expected, got);
718
719        // get_new_address
720        let address = client.get_new_address().await.unwrap();
721        let txid = client
722            .call::<String>(
723                "sendtoaddress",
724                &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
725            )
726            .await
727            .unwrap()
728            .parse::<Txid>()
729            .unwrap();
730
731        // get_transaction
732        let tx = client.get_transaction(&txid).await.unwrap().hex;
733        let got = client.send_raw_transaction(&tx).await.unwrap();
734        let expected = txid; // Don't touch this!
735        assert_eq!(expected, got);
736
737        // get_raw_transaction_verbosity_zero
738        let got = client
739            .get_raw_transaction_verbosity_zero(&txid)
740            .await
741            .unwrap()
742            .0;
743        let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
744        assert_eq!(expected, got);
745
746        // get_raw_transaction_verbosity_one
747        let got = client
748            .get_raw_transaction_verbosity_one(&txid)
749            .await
750            .unwrap()
751            .transaction
752            .compute_txid();
753        assert_eq!(expected, got);
754
755        // get_raw_mempool
756        let got = client.get_raw_mempool().await.unwrap();
757        let expected = vec![txid];
758        assert_eq!(expected, got);
759
760        // get_mempool_info
761        let got = client.get_mempool_info().await.unwrap();
762        assert!(got.loaded);
763        assert_eq!(got.size, 1);
764        assert_eq!(got.unbroadcastcount, 1);
765
766        // estimate_smart_fee
767        let got = client.estimate_smart_fee(1).await.unwrap();
768        let expected = 1; // 1 sat/vB
769        assert_eq!(expected, got);
770
771        // sign_raw_transaction_with_wallet
772        let got = client
773            .sign_raw_transaction_with_wallet(&tx, None)
774            .await
775            .unwrap();
776        assert!(got.complete);
777        assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
778
779        // test_mempool_accept
780        let txids = client
781            .test_mempool_accept(&tx)
782            .await
783            .expect("must be able to test mempool accept");
784        let got = txids.first().expect("there must be at least one txid");
785        assert_eq!(
786            got.txid,
787            tx.compute_txid(),
788            "txids must match in the mempool"
789        );
790
791        // send_raw_transaction
792        let got = client.send_raw_transaction(&tx).await.unwrap();
793        assert!(got.as_byte_array().len() == 32);
794
795        // list_transactions
796        let got = client.list_transactions(None).await.unwrap();
797        assert_eq!(got.len(), 10);
798
799        // list_unspent
800        // let's mine one more block
801        mine_blocks(&bitcoind, 1, None).unwrap();
802        let got = client
803            .list_unspent(None, None, None, None, None)
804            .await
805            .unwrap();
806        assert_eq!(got.len(), 3);
807
808        // listdescriptors
809        let got = client.get_xpriv().await.unwrap().unwrap().network;
810        let expected = NetworkKind::Test;
811        assert_eq!(expected, got);
812
813        // importdescriptors
814        // taken from https://github.com/rust-bitcoin/rust-bitcoin/blob/bb38aeb786f408247d5bbc88b9fa13616c74c009/bitcoin/examples/taproot-psbt.rs#L18C38-L18C149
815        let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
816        let timestamp = "now".to_owned();
817        let list_descriptors = vec![ImportDescriptor {
818            desc: descriptor_string,
819            active: Some(true),
820            timestamp,
821        }];
822        let got = client
823            .import_descriptors(list_descriptors, "strata".to_owned())
824            .await
825            .unwrap();
826        let expected = vec![ImportDescriptorResult { success: true }];
827        assert_eq!(expected, got);
828
829        let psbt_address = client.get_new_address().await.unwrap();
830        let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
831            address: psbt_address.to_string(),
832            amount: 1.0,
833        }];
834
835        let funded_psbt = client
836            .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
837            .await
838            .unwrap();
839        assert!(!funded_psbt.psbt.inputs.is_empty());
840        assert!(funded_psbt.fee.to_sat() > 0);
841
842        let processed_psbt = client
843            .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
844            .await
845            .unwrap();
846        assert!(!processed_psbt.psbt.as_ref().unwrap().inputs.is_empty());
847        assert!(processed_psbt.complete);
848
849        let finalized_psbt = client
850            .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
851            .await
852            .unwrap();
853        assert!(finalized_psbt.complete);
854        assert!(finalized_psbt.hex.is_some());
855        let signed_tx = finalized_psbt.hex.as_ref().unwrap();
856        let signed_txid = signed_tx.compute_txid();
857        let got = client
858            .test_mempool_accept(signed_tx)
859            .await
860            .unwrap()
861            .first()
862            .unwrap()
863            .txid;
864        assert_eq!(signed_txid, got);
865
866        let info_address = client.get_new_address().await.unwrap();
867        let address_info = client.get_address_info(&info_address).await.unwrap();
868        assert_eq!(address_info.address, info_address.as_unchecked().clone());
869        assert!(address_info.is_mine.unwrap_or(false));
870        assert!(address_info.solvable.unwrap_or(false));
871
872        let unspent_address = client.get_new_address().await.unwrap();
873        let unspent_txid = client
874            .call::<String>(
875                "sendtoaddress",
876                &[
877                    to_value(unspent_address.to_string()).unwrap(),
878                    to_value(1.0).unwrap(),
879                ],
880            )
881            .await
882            .unwrap();
883        mine_blocks(&bitcoind, 1, None).unwrap();
884
885        let utxos = client
886            .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
887            .await
888            .unwrap();
889        assert!(!utxos.is_empty());
890
891        let utxos_filtered = client
892            .list_unspent(
893                Some(1),
894                Some(9_999_999),
895                Some(std::slice::from_ref(&unspent_address)),
896                Some(true),
897                None,
898            )
899            .await
900            .unwrap();
901        assert!(!utxos_filtered.is_empty());
902        let found_utxo = utxos_filtered.iter().any(|utxo| {
903            utxo.txid.to_string() == unspent_txid
904                && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
905        });
906        assert!(found_utxo);
907
908        let query_options = ListUnspentQueryOptions {
909            minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
910            maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
911            maximum_count: Some(10),
912        };
913        let utxos_with_query = client
914            .list_unspent(
915                Some(1),
916                Some(9_999_999),
917                None,
918                Some(true),
919                Some(query_options),
920            )
921            .await
922            .unwrap();
923        assert!(!utxos_with_query.is_empty());
924        for utxo in &utxos_with_query {
925            let amount_btc = utxo.amount.to_btc();
926            assert!((0.5..=2.0).contains(&amount_btc));
927        }
928
929        let tx = finalized_psbt.hex.unwrap();
930        assert!(!tx.input.is_empty());
931        assert!(!tx.output.is_empty());
932    }
933
934    #[tokio::test()]
935    async fn get_tx_out() {
936        init_tracing();
937
938        let (bitcoind, client) = get_bitcoind_and_client();
939
940        // network sanity check
941        let got = client.network().await.unwrap();
942        let expected = Network::Regtest;
943        assert_eq!(expected, got);
944
945        let address = bitcoind.client.new_address().unwrap();
946        let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
947        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
948        let coinbase_tx = last_block.coinbase().unwrap();
949
950        // gettxout should work with a non-spent UTXO.
951        let got = client
952            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
953            .await
954            .unwrap();
955        assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
956
957        // gettxout should fail with a spent UTXO.
958        let new_address = bitcoind.client.new_address().unwrap();
959        let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); // 2k sats as fees.
960        let _send_tx = bitcoind
961            .client
962            .send_to_address(&new_address, send_amount)
963            .unwrap()
964            .txid()
965            .unwrap();
966        let result = client
967            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
968            .await;
969        trace!(?result, "gettxout result");
970        assert!(result.is_err());
971    }
972
973    /// Create two transactions.
974    /// 1. Normal one: sends 1 BTC to an address that we control.
975    /// 2. CFFP: replaces the first transaction with a different one that we also control.
976    ///
977    /// This is needed because we must SIGN all these transactions, and we can't sign a transaction
978    /// that we don't control.
979    #[tokio::test()]
980    async fn submit_package() {
981        init_tracing();
982
983        let (bitcoind, client) = get_bitcoind_and_client();
984
985        // network sanity check
986        let got = client.network().await.unwrap();
987        let expected = Network::Regtest;
988        assert_eq!(expected, got);
989
990        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
991        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
992        let coinbase_tx = last_block.coinbase().unwrap();
993
994        let destination = client.get_new_address().await.unwrap();
995        let change_address = client.get_new_address().await.unwrap();
996        let amount = Amount::from_btc(1.0).unwrap();
997        let fees = Amount::from_btc(0.0001).unwrap();
998        let change_amount = COINBASE_AMOUNT - amount - fees;
999        let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
1000
1001        let send_back_address = client.get_new_address().await.unwrap();
1002        let parent_raw_tx = CreateRawTransaction {
1003            inputs: vec![CreateRawTransactionInput {
1004                txid: coinbase_tx.compute_txid().to_string(),
1005                vout: 0,
1006            }],
1007            outputs: vec![
1008                // Destination
1009                CreateRawTransactionOutput::AddressAmount {
1010                    address: destination.to_string(),
1011                    amount: amount.to_btc(),
1012                },
1013                // Change
1014                CreateRawTransactionOutput::AddressAmount {
1015                    address: change_address.to_string(),
1016                    amount: change_amount.to_btc(),
1017                },
1018            ],
1019        };
1020        let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1021        let signed_parent: Transaction = consensus::encode::deserialize_hex(
1022            client
1023                .sign_raw_transaction_with_wallet(&parent, None)
1024                .await
1025                .unwrap()
1026                .hex
1027                .as_str(),
1028        )
1029        .unwrap();
1030
1031        // sanity check
1032        let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
1033
1034        let child_raw_tx = CreateRawTransaction {
1035            inputs: vec![CreateRawTransactionInput {
1036                txid: parent_submitted.to_string(),
1037                vout: 0,
1038            }],
1039            outputs: vec![
1040                // Send back
1041                CreateRawTransactionOutput::AddressAmount {
1042                    address: send_back_address.to_string(),
1043                    amount: amount_minus_fees.to_btc(),
1044                },
1045            ],
1046        };
1047        let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1048        let signed_child: Transaction = consensus::encode::deserialize_hex(
1049            client
1050                .sign_raw_transaction_with_wallet(&child, None)
1051                .await
1052                .unwrap()
1053                .hex
1054                .as_str(),
1055        )
1056        .unwrap();
1057
1058        // Ok now we have a parent and a child transaction.
1059        let result = client
1060            .submit_package(&[signed_parent, signed_child])
1061            .await
1062            .unwrap();
1063        assert_eq!(result.tx_results.len(), 2);
1064        assert_eq!(result.package_msg, "success");
1065    }
1066
1067    /// Similar to [`submit_package`], but with where the parent does not pay fees,
1068    /// and the child has to pay fees.
1069    ///
1070    /// This is called 1P1C because it has one parent and one child.
1071    /// See <https://bitcoinops.org/en/bitcoin-core-28-wallet-integration-guide/>
1072    /// for more information.
1073    #[tokio::test]
1074    async fn submit_package_1p1c() {
1075        init_tracing();
1076
1077        let (bitcoind, client) = get_bitcoind_and_client();
1078
1079        // 1p1c sanity check
1080        let server_version = bitcoind.client.server_version().unwrap();
1081        assert!(server_version > 28);
1082
1083        let destination = client.get_new_address().await.unwrap();
1084
1085        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1086        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1087        let coinbase_tx = last_block.coinbase().unwrap();
1088
1089        let parent_raw_tx = CreateRawTransaction {
1090            inputs: vec![CreateRawTransactionInput {
1091                txid: coinbase_tx.compute_txid().to_string(),
1092                vout: 0,
1093            }],
1094            outputs: vec![CreateRawTransactionOutput::AddressAmount {
1095                address: destination.to_string(),
1096                amount: COINBASE_AMOUNT.to_btc(),
1097            }],
1098        };
1099        let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1100        parent.version = transaction::Version(3);
1101        assert_eq!(parent.version, transaction::Version(3));
1102        trace!(?parent, "parent:");
1103        let signed_parent: Transaction = consensus::encode::deserialize_hex(
1104            client
1105                .sign_raw_transaction_with_wallet(&parent, None)
1106                .await
1107                .unwrap()
1108                .hex
1109                .as_str(),
1110        )
1111        .unwrap();
1112        assert_eq!(signed_parent.version, transaction::Version(3));
1113
1114        // Assert that the parent tx cannot be broadcasted.
1115        let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
1116        assert!(parent_broadcasted.is_err());
1117
1118        // 5k sats as fees.
1119        let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
1120        let child_raw_tx = CreateRawTransaction {
1121            inputs: vec![CreateRawTransactionInput {
1122                txid: signed_parent.compute_txid().to_string(),
1123                vout: 0,
1124            }],
1125            outputs: vec![CreateRawTransactionOutput::AddressAmount {
1126                address: destination.to_string(),
1127                amount: amount_minus_fees.to_btc(),
1128            }],
1129        };
1130        let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1131        child.version = transaction::Version(3);
1132        assert_eq!(child.version, transaction::Version(3));
1133        trace!(?child, "child:");
1134        let prev_outputs = vec![PreviousTransactionOutput {
1135            txid: parent.compute_txid(),
1136            vout: 0,
1137            script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
1138            redeem_script: None,
1139            witness_script: None,
1140            amount: Some(COINBASE_AMOUNT.to_btc()),
1141        }];
1142        let signed_child: Transaction = consensus::encode::deserialize_hex(
1143            client
1144                .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
1145                .await
1146                .unwrap()
1147                .hex
1148                .as_str(),
1149        )
1150        .unwrap();
1151        assert_eq!(signed_child.version, transaction::Version(3));
1152
1153        // Assert that the child tx cannot be broadcasted.
1154        let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1155        assert!(child_broadcasted.is_err());
1156
1157        // Let's send as a package 1C1P.
1158        let result = client
1159            .submit_package(&[signed_parent, signed_child])
1160            .await
1161            .unwrap();
1162        assert_eq!(result.tx_results.len(), 2);
1163        assert_eq!(result.package_msg, "success");
1164    }
1165
1166    #[tokio::test]
1167    async fn test_invalid_credentials_return_401_error() {
1168        init_tracing();
1169
1170        let (bitcoind, _) = get_bitcoind_and_client();
1171        let url = bitcoind.rpc_url();
1172
1173        // Create client with invalid credentials
1174        let invalid_client = Client::new(
1175            url,
1176            "wrong_user".to_string(),
1177            "wrong_password".to_string(),
1178            None,
1179            None,
1180        )
1181        .unwrap();
1182
1183        // Try to make any RPC call
1184        let result = invalid_client.get_blockchain_info().await;
1185
1186        // Verify we get a 401 Status error, not a Parse error
1187        assert!(result.is_err());
1188        let error = result.unwrap_err();
1189
1190        match error {
1191            ClientError::Status(status_code, message) => {
1192                assert_eq!(status_code, 401);
1193                assert!(message.contains("Unauthorized"));
1194            }
1195            _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1196        }
1197    }
1198
1199    #[tokio::test]
1200    async fn psbt_bump_fee() {
1201        init_tracing();
1202
1203        let (bitcoind, client) = get_bitcoind_and_client();
1204
1205        // Mine blocks to have funds
1206        mine_blocks(&bitcoind, 101, None).unwrap();
1207
1208        // Send to the next address
1209        let destination = client.get_new_address().await.unwrap();
1210        let amount = Amount::from_btc(0.001).unwrap(); // 0.001 BTC
1211
1212        // Create transaction with RBF enabled
1213        let txid = bitcoind
1214            .client
1215            .send_to_address_rbf(&destination, amount)
1216            .unwrap()
1217            .txid()
1218            .unwrap();
1219
1220        // Verify transaction is in mempool (unconfirmed)
1221        let mempool = client.get_raw_mempool().await.unwrap();
1222        assert!(
1223            mempool.contains(&txid),
1224            "Transaction should be in mempool for RBF"
1225        );
1226
1227        // Test psbt_bump_fee with default options
1228        let signed_tx = client
1229            .psbt_bump_fee(&txid, None)
1230            .await
1231            .unwrap()
1232            .psbt
1233            .extract_tx()
1234            .unwrap();
1235        let signed_txid = signed_tx.compute_txid();
1236        let got = client
1237            .test_mempool_accept(&signed_tx)
1238            .await
1239            .unwrap()
1240            .first()
1241            .unwrap()
1242            .txid;
1243        assert_eq!(
1244            got, signed_txid,
1245            "Bumped transaction should be accepted in mempool"
1246        );
1247
1248        // Test psbt_bump_fee with custom fee rate
1249        let options = PsbtBumpFeeOptions {
1250            fee_rate: Some(FeeRate::from_sat_per_kwu(20)), // 20 sat/vB - higher than default
1251            ..Default::default()
1252        };
1253        trace!(?options, "Calling psbt_bump_fee");
1254        let signed_tx = client
1255            .psbt_bump_fee(&txid, Some(options))
1256            .await
1257            .unwrap()
1258            .psbt
1259            .extract_tx()
1260            .unwrap();
1261        let signed_txid = signed_tx.compute_txid();
1262        let got = client
1263            .test_mempool_accept(&signed_tx)
1264            .await
1265            .unwrap()
1266            .first()
1267            .unwrap()
1268            .txid;
1269        assert_eq!(
1270            got, signed_txid,
1271            "Bumped transaction should be accepted in mempool"
1272        );
1273    }
1274}