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, GetRawMempoolVerbose, 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_raw_mempool_verbose(&self) -> ClientResult<GetRawMempoolVerbose> {
338        self.call::<GetRawMempoolVerbose>("getrawmempool", &[to_value(true)?])
339            .await
340    }
341
342    async fn get_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
343        self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
344    }
345
346    async fn get_raw_transaction_verbosity_zero(
347        &self,
348        txid: &Txid,
349    ) -> ClientResult<GetRawTransactionVerbosityZero> {
350        self.call::<GetRawTransactionVerbosityZero>(
351            "getrawtransaction",
352            &[to_value(txid.to_string())?, to_value(0)?],
353        )
354        .await
355    }
356
357    async fn get_raw_transaction_verbosity_one(
358        &self,
359        txid: &Txid,
360    ) -> ClientResult<GetRawTransactionVerbosityOne> {
361        self.call::<GetRawTransactionVerbosityOne>(
362            "getrawtransaction",
363            &[to_value(txid.to_string())?, to_value(1)?],
364        )
365        .await
366    }
367
368    async fn get_tx_out(
369        &self,
370        txid: &Txid,
371        vout: u32,
372        include_mempool: bool,
373    ) -> ClientResult<GetTxOut> {
374        self.call::<GetTxOut>(
375            "gettxout",
376            &[
377                to_value(txid.to_string())?,
378                to_value(vout)?,
379                to_value(include_mempool)?,
380            ],
381        )
382        .await
383    }
384
385    async fn network(&self) -> ClientResult<Network> {
386        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
387            .await?
388            .chain
389            .parse::<Network>()
390            .map_err(|e| ClientError::Parse(e.to_string()))
391    }
392}
393
394impl Broadcaster for Client {
395    async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
396        let txstr = serialize_hex(tx);
397        trace!(txstr = %txstr, "Sending raw transaction");
398        match self
399            .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
400            .await
401        {
402            Ok(txid) => {
403                trace!(?txid, "Transaction sent");
404                Ok(txid)
405            }
406            Err(ClientError::Server(i, s)) => match i {
407                // Dealing with known and common errors
408                -27 => Ok(tx.compute_txid()), // Tx already in chain
409                _ => Err(ClientError::Server(i, s)),
410            },
411            Err(e) => Err(ClientError::Other(e.to_string())),
412        }
413    }
414
415    async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
416        let txstr = serialize_hex(tx);
417        trace!(%txstr, "Testing mempool accept");
418        self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
419            .await
420    }
421
422    async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
423        let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
424        self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
425            .await
426    }
427}
428
429impl Wallet for Client {
430    async fn get_new_address(&self) -> ClientResult<Address> {
431        let address_unchecked = self
432            .call::<GetNewAddress>("getnewaddress", &[])
433            .await?
434            .0
435            .parse::<Address<_>>()
436            .map_err(|e| ClientError::Parse(e.to_string()))?
437            .assume_checked();
438        Ok(address_unchecked)
439    }
440    async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
441        self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
442            .await
443    }
444
445    async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
446        let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
447        trace!(?resp, "Got UTXOs");
448        Ok(resp)
449    }
450
451    async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
452        self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
453            .await
454    }
455
456    async fn list_wallets(&self) -> ClientResult<Vec<String>> {
457        self.call::<Vec<String>>("listwallets", &[]).await
458    }
459
460    async fn create_raw_transaction(
461        &self,
462        raw_tx: CreateRawTransaction,
463    ) -> ClientResult<Transaction> {
464        let raw_tx = self
465            .call::<String>(
466                "createrawtransaction",
467                &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
468            )
469            .await?;
470        trace!(%raw_tx, "Created raw transaction");
471        consensus::encode::deserialize_hex(&raw_tx)
472            .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
473    }
474
475    async fn wallet_create_funded_psbt(
476        &self,
477        inputs: &[CreateRawTransactionInput],
478        outputs: &[CreateRawTransactionOutput],
479        locktime: Option<u32>,
480        options: Option<WalletCreateFundedPsbtOptions>,
481        bip32_derivs: Option<bool>,
482    ) -> ClientResult<WalletCreateFundedPsbt> {
483        self.call::<WalletCreateFundedPsbt>(
484            "walletcreatefundedpsbt",
485            &[
486                to_value(inputs)?,
487                to_value(outputs)?,
488                to_value(locktime.unwrap_or(0))?,
489                to_value(options.unwrap_or_default())?,
490                to_value(bip32_derivs)?,
491            ],
492        )
493        .await
494    }
495
496    async fn get_address_info(&self, address: &Address) -> ClientResult<GetAddressInfo> {
497        trace!(address = %address, "Getting address info");
498        self.call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
499            .await
500    }
501
502    async fn list_unspent(
503        &self,
504        min_conf: Option<u32>,
505        max_conf: Option<u32>,
506        addresses: Option<&[Address]>,
507        include_unsafe: Option<bool>,
508        query_options: Option<ListUnspentQueryOptions>,
509    ) -> ClientResult<Vec<ListUnspent>> {
510        let addr_strings: Vec<String> = addresses
511            .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
512            .unwrap_or_default();
513
514        let mut params = vec![
515            to_value(min_conf.unwrap_or(1))?,
516            to_value(max_conf.unwrap_or(9_999_999))?,
517            to_value(addr_strings)?,
518            to_value(include_unsafe.unwrap_or(true))?,
519        ];
520
521        if let Some(query_options) = query_options {
522            params.push(to_value(query_options)?);
523        }
524
525        self.call::<Vec<ListUnspent>>("listunspent", &params).await
526    }
527}
528
529impl Signer for Client {
530    async fn sign_raw_transaction_with_wallet(
531        &self,
532        tx: &Transaction,
533        prev_outputs: Option<Vec<PreviousTransactionOutput>>,
534    ) -> ClientResult<SignRawTransactionWithWallet> {
535        let tx_hex = serialize_hex(tx);
536        trace!(tx_hex = %tx_hex, "Signing transaction");
537        trace!(?prev_outputs, "Signing transaction with previous outputs");
538        self.call::<SignRawTransactionWithWallet>(
539            "signrawtransactionwithwallet",
540            &[to_value(tx_hex)?, to_value(prev_outputs)?],
541        )
542        .await
543    }
544
545    async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
546        // If the ENV variable `BITCOIN_XPRIV_RETRIEVABLE` is not set, we return `None`
547        if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
548            return Ok(None);
549        }
550
551        let descriptors = self
552            .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) // true is the xpriv, false is the xpub
553            .await?
554            .descriptors;
555        if descriptors.is_empty() {
556            return Err(ClientError::Other("No descriptors found".to_string()));
557        }
558
559        // We are only interested in the one that contains `tr(`
560        let descriptor = descriptors
561            .iter()
562            .find(|d| d.desc.contains("tr("))
563            .map(|d| d.desc.clone())
564            .ok_or(ClientError::Xpriv)?;
565
566        // Now we extract the xpriv from the `tr()` up to the first `/`
567        let xpriv_str = descriptor
568            .split("tr(")
569            .nth(1)
570            .ok_or(ClientError::Xpriv)?
571            .split("/")
572            .next()
573            .ok_or(ClientError::Xpriv)?;
574
575        let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
576        Ok(Some(xpriv))
577    }
578
579    async fn import_descriptors(
580        &self,
581        descriptors: Vec<ImportDescriptor>,
582        wallet_name: String,
583    ) -> ClientResult<Vec<ImportDescriptorResult>> {
584        let wallet_args = CreateWallet {
585            wallet_name,
586            load_on_startup: Some(true),
587        };
588
589        // TODO: this should check for -35 error code which is good,
590        //       means that is already created
591        let _wallet_create = self
592            .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
593            .await;
594        // TODO: this should check for -35 error code which is good, -18 is bad.
595        let _wallet_load = self
596            .call::<Value>("loadwallet", &[to_value(wallet_args)?])
597            .await;
598
599        let result = self
600            .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
601            .await?;
602        Ok(result)
603    }
604
605    async fn wallet_process_psbt(
606        &self,
607        psbt: &str,
608        sign: Option<bool>,
609        sighashtype: Option<SighashType>,
610        bip32_derivs: Option<bool>,
611    ) -> ClientResult<WalletProcessPsbtResult> {
612        let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
613
614        if let Some(sighashtype) = sighashtype {
615            params.push(to_value(sighashtype)?);
616        }
617
618        if let Some(bip32_derivs) = bip32_derivs {
619            params.push(to_value(bip32_derivs)?);
620        }
621
622        self.call::<WalletProcessPsbtResult>("walletprocesspsbt", &params)
623            .await
624    }
625
626    async fn psbt_bump_fee(
627        &self,
628        txid: &Txid,
629        options: Option<PsbtBumpFeeOptions>,
630    ) -> ClientResult<PsbtBumpFee> {
631        let mut params = vec![to_value(txid.to_string())?];
632
633        if let Some(options) = options {
634            params.push(to_value(options)?);
635        }
636
637        self.call::<PsbtBumpFee>("psbtbumpfee", &params).await
638    }
639}
640
641#[cfg(test)]
642mod test {
643
644    use std::sync::Once;
645
646    use bitcoin::{
647        consensus::{self, encode::deserialize_hex},
648        hashes::Hash,
649        transaction, Amount, FeeRate, NetworkKind,
650    };
651    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
652
653    use super::*;
654    use crate::{
655        test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
656        types::{CreateRawTransactionInput, CreateRawTransactionOutput},
657    };
658
659    /// 50 BTC in [`Network::Regtest`].
660    const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
661
662    /// Only attempts to start tracing once.
663    fn init_tracing() {
664        static INIT: Once = Once::new();
665
666        INIT.call_once(|| {
667            tracing_subscriber::registry()
668                .with(fmt::layer())
669                .with(EnvFilter::from_default_env())
670                .try_init()
671                .ok();
672        });
673    }
674
675    #[tokio::test()]
676    async fn client_works() {
677        init_tracing();
678
679        let (bitcoind, client) = get_bitcoind_and_client();
680
681        // network
682        let got = client.network().await.unwrap();
683        let expected = Network::Regtest;
684
685        assert_eq!(expected, got);
686        // get_blockchain_info
687        let get_blockchain_info = client.get_blockchain_info().await.unwrap();
688        assert_eq!(get_blockchain_info.blocks, 0);
689
690        // get_current_timestamp
691        let _ = client
692            .get_current_timestamp()
693            .await
694            .expect("must be able to get current timestamp");
695
696        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
697
698        // get_block
699        let expected = blocks.last().unwrap();
700        let got = client.get_block(expected).await.unwrap().block_hash();
701        assert_eq!(*expected, got);
702
703        // get_block_at
704        let target_height = blocks.len() as u64;
705        let expected = blocks.last().unwrap();
706        let got = client
707            .get_block_at(target_height)
708            .await
709            .unwrap()
710            .block_hash();
711        assert_eq!(*expected, got);
712
713        // get_block_count
714        let expected = blocks.len() as u64;
715        let got = client.get_block_count().await.unwrap();
716        assert_eq!(expected, got);
717
718        // get_block_hash
719        let target_height = blocks.len() as u64;
720        let expected = blocks.last().unwrap();
721        let got = client.get_block_hash(target_height).await.unwrap();
722        assert_eq!(*expected, got);
723
724        // get_new_address
725        let address = client.get_new_address().await.unwrap();
726        let txid = client
727            .call::<String>(
728                "sendtoaddress",
729                &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
730            )
731            .await
732            .unwrap()
733            .parse::<Txid>()
734            .unwrap();
735
736        // get_transaction
737        let tx = client.get_transaction(&txid).await.unwrap().hex;
738        let got = client.send_raw_transaction(&tx).await.unwrap();
739        let expected = txid; // Don't touch this!
740        assert_eq!(expected, got);
741
742        // get_raw_transaction_verbosity_zero
743        let got = client
744            .get_raw_transaction_verbosity_zero(&txid)
745            .await
746            .unwrap()
747            .0;
748        let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
749        assert_eq!(expected, got);
750
751        // get_raw_transaction_verbosity_one
752        let got = client
753            .get_raw_transaction_verbosity_one(&txid)
754            .await
755            .unwrap()
756            .transaction
757            .compute_txid();
758        assert_eq!(expected, got);
759
760        // get_raw_mempool
761        let got = client.get_raw_mempool().await.unwrap();
762        let expected = vec![txid];
763        assert_eq!(expected, got);
764
765        // get_raw_mempool_verbose
766        let got = client.get_raw_mempool_verbose().await.unwrap();
767        assert_eq!(got.len(), 1);
768        assert_eq!(got.get(&txid).unwrap().height, 101);
769
770        // get_mempool_info
771        let got = client.get_mempool_info().await.unwrap();
772        assert!(got.loaded);
773        assert_eq!(got.size, 1);
774        assert_eq!(got.unbroadcastcount, 1);
775
776        // estimate_smart_fee
777        let got = client.estimate_smart_fee(1).await.unwrap();
778        let expected = 1; // 1 sat/vB
779        assert_eq!(expected, got);
780
781        // sign_raw_transaction_with_wallet
782        let got = client
783            .sign_raw_transaction_with_wallet(&tx, None)
784            .await
785            .unwrap();
786        assert!(got.complete);
787        assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
788
789        // test_mempool_accept
790        let txids = client
791            .test_mempool_accept(&tx)
792            .await
793            .expect("must be able to test mempool accept");
794        let got = txids.first().expect("there must be at least one txid");
795        assert_eq!(
796            got.txid,
797            tx.compute_txid(),
798            "txids must match in the mempool"
799        );
800
801        // send_raw_transaction
802        let got = client.send_raw_transaction(&tx).await.unwrap();
803        assert!(got.as_byte_array().len() == 32);
804
805        // list_transactions
806        let got = client.list_transactions(None).await.unwrap();
807        assert_eq!(got.len(), 10);
808
809        // list_unspent
810        // let's mine one more block
811        mine_blocks(&bitcoind, 1, None).unwrap();
812        let got = client
813            .list_unspent(None, None, None, None, None)
814            .await
815            .unwrap();
816        assert_eq!(got.len(), 3);
817
818        // listdescriptors
819        let got = client.get_xpriv().await.unwrap().unwrap().network;
820        let expected = NetworkKind::Test;
821        assert_eq!(expected, got);
822
823        // importdescriptors
824        // taken from https://github.com/rust-bitcoin/rust-bitcoin/blob/bb38aeb786f408247d5bbc88b9fa13616c74c009/bitcoin/examples/taproot-psbt.rs#L18C38-L18C149
825        let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
826        let timestamp = "now".to_owned();
827        let list_descriptors = vec![ImportDescriptor {
828            desc: descriptor_string,
829            active: Some(true),
830            timestamp,
831        }];
832        let got = client
833            .import_descriptors(list_descriptors, "strata".to_owned())
834            .await
835            .unwrap();
836        let expected = vec![ImportDescriptorResult { success: true }];
837        assert_eq!(expected, got);
838
839        let psbt_address = client.get_new_address().await.unwrap();
840        let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
841            address: psbt_address.to_string(),
842            amount: 1.0,
843        }];
844
845        let funded_psbt = client
846            .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
847            .await
848            .unwrap();
849        assert!(!funded_psbt.psbt.inputs.is_empty());
850        assert!(funded_psbt.fee.to_sat() > 0);
851
852        let processed_psbt = client
853            .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
854            .await
855            .unwrap();
856        assert!(!processed_psbt.psbt.as_ref().unwrap().inputs.is_empty());
857        assert!(processed_psbt.complete);
858
859        let finalized_psbt = client
860            .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
861            .await
862            .unwrap();
863        assert!(finalized_psbt.complete);
864        assert!(finalized_psbt.hex.is_some());
865        let signed_tx = finalized_psbt.hex.as_ref().unwrap();
866        let signed_txid = signed_tx.compute_txid();
867        let got = client
868            .test_mempool_accept(signed_tx)
869            .await
870            .unwrap()
871            .first()
872            .unwrap()
873            .txid;
874        assert_eq!(signed_txid, got);
875
876        let info_address = client.get_new_address().await.unwrap();
877        let address_info = client.get_address_info(&info_address).await.unwrap();
878        assert_eq!(address_info.address, info_address.as_unchecked().clone());
879        assert!(address_info.is_mine.unwrap_or(false));
880        assert!(address_info.solvable.unwrap_or(false));
881
882        let unspent_address = client.get_new_address().await.unwrap();
883        let unspent_txid = client
884            .call::<String>(
885                "sendtoaddress",
886                &[
887                    to_value(unspent_address.to_string()).unwrap(),
888                    to_value(1.0).unwrap(),
889                ],
890            )
891            .await
892            .unwrap();
893        mine_blocks(&bitcoind, 1, None).unwrap();
894
895        let utxos = client
896            .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
897            .await
898            .unwrap();
899        assert!(!utxos.is_empty());
900
901        let utxos_filtered = client
902            .list_unspent(
903                Some(1),
904                Some(9_999_999),
905                Some(std::slice::from_ref(&unspent_address)),
906                Some(true),
907                None,
908            )
909            .await
910            .unwrap();
911        assert!(!utxos_filtered.is_empty());
912        let found_utxo = utxos_filtered.iter().any(|utxo| {
913            utxo.txid.to_string() == unspent_txid
914                && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
915        });
916        assert!(found_utxo);
917
918        let query_options = ListUnspentQueryOptions {
919            minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
920            maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
921            maximum_count: Some(10),
922        };
923        let utxos_with_query = client
924            .list_unspent(
925                Some(1),
926                Some(9_999_999),
927                None,
928                Some(true),
929                Some(query_options),
930            )
931            .await
932            .unwrap();
933        assert!(!utxos_with_query.is_empty());
934        for utxo in &utxos_with_query {
935            let amount_btc = utxo.amount.to_btc();
936            assert!((0.5..=2.0).contains(&amount_btc));
937        }
938
939        let tx = finalized_psbt.hex.unwrap();
940        assert!(!tx.input.is_empty());
941        assert!(!tx.output.is_empty());
942    }
943
944    #[tokio::test()]
945    async fn get_tx_out() {
946        init_tracing();
947
948        let (bitcoind, client) = get_bitcoind_and_client();
949
950        // network sanity check
951        let got = client.network().await.unwrap();
952        let expected = Network::Regtest;
953        assert_eq!(expected, got);
954
955        let address = bitcoind.client.new_address().unwrap();
956        let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
957        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
958        let coinbase_tx = last_block.coinbase().unwrap();
959
960        // gettxout should work with a non-spent UTXO.
961        let got = client
962            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
963            .await
964            .unwrap();
965        assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
966
967        // gettxout should fail with a spent UTXO.
968        let new_address = bitcoind.client.new_address().unwrap();
969        let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); // 2k sats as fees.
970        let _send_tx = bitcoind
971            .client
972            .send_to_address(&new_address, send_amount)
973            .unwrap()
974            .txid()
975            .unwrap();
976        let result = client
977            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
978            .await;
979        trace!(?result, "gettxout result");
980        assert!(result.is_err());
981    }
982
983    /// Create two transactions.
984    /// 1. Normal one: sends 1 BTC to an address that we control.
985    /// 2. CFFP: replaces the first transaction with a different one that we also control.
986    ///
987    /// This is needed because we must SIGN all these transactions, and we can't sign a transaction
988    /// that we don't control.
989    #[tokio::test()]
990    async fn submit_package() {
991        init_tracing();
992
993        let (bitcoind, client) = get_bitcoind_and_client();
994
995        // network sanity check
996        let got = client.network().await.unwrap();
997        let expected = Network::Regtest;
998        assert_eq!(expected, got);
999
1000        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1001        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1002        let coinbase_tx = last_block.coinbase().unwrap();
1003
1004        let destination = client.get_new_address().await.unwrap();
1005        let change_address = client.get_new_address().await.unwrap();
1006        let amount = Amount::from_btc(1.0).unwrap();
1007        let fees = Amount::from_btc(0.0001).unwrap();
1008        let change_amount = COINBASE_AMOUNT - amount - fees;
1009        let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
1010
1011        let send_back_address = client.get_new_address().await.unwrap();
1012        let parent_raw_tx = CreateRawTransaction {
1013            inputs: vec![CreateRawTransactionInput {
1014                txid: coinbase_tx.compute_txid().to_string(),
1015                vout: 0,
1016            }],
1017            outputs: vec![
1018                // Destination
1019                CreateRawTransactionOutput::AddressAmount {
1020                    address: destination.to_string(),
1021                    amount: amount.to_btc(),
1022                },
1023                // Change
1024                CreateRawTransactionOutput::AddressAmount {
1025                    address: change_address.to_string(),
1026                    amount: change_amount.to_btc(),
1027                },
1028            ],
1029        };
1030        let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1031        let signed_parent: Transaction = consensus::encode::deserialize_hex(
1032            client
1033                .sign_raw_transaction_with_wallet(&parent, None)
1034                .await
1035                .unwrap()
1036                .hex
1037                .as_str(),
1038        )
1039        .unwrap();
1040
1041        // sanity check
1042        let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
1043
1044        let child_raw_tx = CreateRawTransaction {
1045            inputs: vec![CreateRawTransactionInput {
1046                txid: parent_submitted.to_string(),
1047                vout: 0,
1048            }],
1049            outputs: vec![
1050                // Send back
1051                CreateRawTransactionOutput::AddressAmount {
1052                    address: send_back_address.to_string(),
1053                    amount: amount_minus_fees.to_btc(),
1054                },
1055            ],
1056        };
1057        let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1058        let signed_child: Transaction = consensus::encode::deserialize_hex(
1059            client
1060                .sign_raw_transaction_with_wallet(&child, None)
1061                .await
1062                .unwrap()
1063                .hex
1064                .as_str(),
1065        )
1066        .unwrap();
1067
1068        // Ok now we have a parent and a child transaction.
1069        let result = client
1070            .submit_package(&[signed_parent, signed_child])
1071            .await
1072            .unwrap();
1073        assert_eq!(result.tx_results.len(), 2);
1074        assert_eq!(result.package_msg, "success");
1075    }
1076
1077    /// Similar to [`submit_package`], but with where the parent does not pay fees,
1078    /// and the child has to pay fees.
1079    ///
1080    /// This is called 1P1C because it has one parent and one child.
1081    /// See <https://bitcoinops.org/en/bitcoin-core-28-wallet-integration-guide/>
1082    /// for more information.
1083    #[tokio::test]
1084    async fn submit_package_1p1c() {
1085        init_tracing();
1086
1087        let (bitcoind, client) = get_bitcoind_and_client();
1088
1089        // 1p1c sanity check
1090        let server_version = bitcoind.client.server_version().unwrap();
1091        assert!(server_version > 28);
1092
1093        let destination = client.get_new_address().await.unwrap();
1094
1095        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1096        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1097        let coinbase_tx = last_block.coinbase().unwrap();
1098
1099        let parent_raw_tx = CreateRawTransaction {
1100            inputs: vec![CreateRawTransactionInput {
1101                txid: coinbase_tx.compute_txid().to_string(),
1102                vout: 0,
1103            }],
1104            outputs: vec![CreateRawTransactionOutput::AddressAmount {
1105                address: destination.to_string(),
1106                amount: COINBASE_AMOUNT.to_btc(),
1107            }],
1108        };
1109        let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1110        parent.version = transaction::Version(3);
1111        assert_eq!(parent.version, transaction::Version(3));
1112        trace!(?parent, "parent:");
1113        let signed_parent: Transaction = consensus::encode::deserialize_hex(
1114            client
1115                .sign_raw_transaction_with_wallet(&parent, None)
1116                .await
1117                .unwrap()
1118                .hex
1119                .as_str(),
1120        )
1121        .unwrap();
1122        assert_eq!(signed_parent.version, transaction::Version(3));
1123
1124        // Assert that the parent tx cannot be broadcasted.
1125        let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
1126        assert!(parent_broadcasted.is_err());
1127
1128        // 5k sats as fees.
1129        let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
1130        let child_raw_tx = CreateRawTransaction {
1131            inputs: vec![CreateRawTransactionInput {
1132                txid: signed_parent.compute_txid().to_string(),
1133                vout: 0,
1134            }],
1135            outputs: vec![CreateRawTransactionOutput::AddressAmount {
1136                address: destination.to_string(),
1137                amount: amount_minus_fees.to_btc(),
1138            }],
1139        };
1140        let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1141        child.version = transaction::Version(3);
1142        assert_eq!(child.version, transaction::Version(3));
1143        trace!(?child, "child:");
1144        let prev_outputs = vec![PreviousTransactionOutput {
1145            txid: parent.compute_txid(),
1146            vout: 0,
1147            script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
1148            redeem_script: None,
1149            witness_script: None,
1150            amount: Some(COINBASE_AMOUNT.to_btc()),
1151        }];
1152        let signed_child: Transaction = consensus::encode::deserialize_hex(
1153            client
1154                .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
1155                .await
1156                .unwrap()
1157                .hex
1158                .as_str(),
1159        )
1160        .unwrap();
1161        assert_eq!(signed_child.version, transaction::Version(3));
1162
1163        // Assert that the child tx cannot be broadcasted.
1164        let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1165        assert!(child_broadcasted.is_err());
1166
1167        // Let's send as a package 1C1P.
1168        let result = client
1169            .submit_package(&[signed_parent, signed_child])
1170            .await
1171            .unwrap();
1172        assert_eq!(result.tx_results.len(), 2);
1173        assert_eq!(result.package_msg, "success");
1174    }
1175
1176    #[tokio::test]
1177    async fn test_invalid_credentials_return_401_error() {
1178        init_tracing();
1179
1180        let (bitcoind, _) = get_bitcoind_and_client();
1181        let url = bitcoind.rpc_url();
1182
1183        // Create client with invalid credentials
1184        let invalid_client = Client::new(
1185            url,
1186            "wrong_user".to_string(),
1187            "wrong_password".to_string(),
1188            None,
1189            None,
1190        )
1191        .unwrap();
1192
1193        // Try to make any RPC call
1194        let result = invalid_client.get_blockchain_info().await;
1195
1196        // Verify we get a 401 Status error, not a Parse error
1197        assert!(result.is_err());
1198        let error = result.unwrap_err();
1199
1200        match error {
1201            ClientError::Status(status_code, message) => {
1202                assert_eq!(status_code, 401);
1203                assert!(message.contains("Unauthorized"));
1204            }
1205            _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1206        }
1207    }
1208
1209    #[tokio::test]
1210    async fn psbt_bump_fee() {
1211        init_tracing();
1212
1213        let (bitcoind, client) = get_bitcoind_and_client();
1214
1215        // Mine blocks to have funds
1216        mine_blocks(&bitcoind, 101, None).unwrap();
1217
1218        // Send to the next address
1219        let destination = client.get_new_address().await.unwrap();
1220        let amount = Amount::from_btc(0.001).unwrap(); // 0.001 BTC
1221
1222        // Create transaction with RBF enabled
1223        let txid = bitcoind
1224            .client
1225            .send_to_address_rbf(&destination, amount)
1226            .unwrap()
1227            .txid()
1228            .unwrap();
1229
1230        // Verify transaction is in mempool (unconfirmed)
1231        let mempool = client.get_raw_mempool().await.unwrap();
1232        assert!(
1233            mempool.contains(&txid),
1234            "Transaction should be in mempool for RBF"
1235        );
1236
1237        // Test psbt_bump_fee with default options
1238        let signed_tx = client
1239            .psbt_bump_fee(&txid, None)
1240            .await
1241            .unwrap()
1242            .psbt
1243            .extract_tx()
1244            .unwrap();
1245        let signed_txid = signed_tx.compute_txid();
1246        let got = client
1247            .test_mempool_accept(&signed_tx)
1248            .await
1249            .unwrap()
1250            .first()
1251            .unwrap()
1252            .txid;
1253        assert_eq!(
1254            got, signed_txid,
1255            "Bumped transaction should be accepted in mempool"
1256        );
1257
1258        // Test psbt_bump_fee with custom fee rate
1259        let options = PsbtBumpFeeOptions {
1260            fee_rate: Some(FeeRate::from_sat_per_kwu(20)), // 20 sat/vB - higher than default
1261            ..Default::default()
1262        };
1263        trace!(?options, "Calling psbt_bump_fee");
1264        let signed_tx = client
1265            .psbt_bump_fee(&txid, Some(options))
1266            .await
1267            .unwrap()
1268            .psbt
1269            .extract_tx()
1270            .unwrap();
1271        let signed_txid = signed_tx.compute_txid();
1272        let got = client
1273            .test_mempool_accept(&signed_tx)
1274            .await
1275            .unwrap()
1276            .first()
1277            .unwrap()
1278            .txid;
1279        assert_eq!(
1280            got, signed_txid,
1281            "Bumped transaction should be accepted in mempool"
1282        );
1283    }
1284}