bitcoind_async_client/
client.rs

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