Skip to main content

bitcoind_async_client/client/
v30.rs

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