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