1use 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", ¶ms).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 var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
369 return Ok(None);
370 }
371
372 let descriptors = self
373 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
375 .descriptors;
376 if descriptors.is_empty() {
377 return Err(ClientError::Other("No descriptors found".to_string()));
378 }
379
380 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 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 let _wallet_create = self
413 .call::<CreateWallet>("createwallet", &[to_value(wallet_args.clone())?])
414 .await;
415 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", ¶ms)
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", ¶ms).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 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
488
489 const FEE_ESTIMATION_BLOCKS: usize = 5;
491
492 const FEE_ESTIMATION_TXS_PER_BLOCK: usize = 5;
494
495 const FEE_ESTIMATION_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(500);
497
498 const FEE_ESTIMATION_WAIT_ATTEMPTS: usize = 1_200;
500
501 const FEE_ESTIMATION_WAIT_INTERVAL: Duration = Duration::from_millis(50);
503
504 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 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 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 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 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 let got = client.network().await.unwrap();
615 let expected = Network::Regtest;
616
617 assert_eq!(expected, got);
618 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
620 assert_eq!(get_blockchain_info.blocks, 0);
621
622 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 let expected = blocks.last().unwrap();
632 let got = client.get_block(expected).await.unwrap().block_hash();
633 assert_eq!(*expected, got);
634
635 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 let expected = blocks.len() as u64;
647 let got = client.get_block_count().await.unwrap();
648 assert_eq!(expected, got);
649
650 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 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 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 let tx = client.get_transaction(&txid).await.unwrap().tx;
676 let got = client.send_raw_transaction(&tx).await.unwrap();
677 let expected = txid; assert_eq!(expected, got);
679
680 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 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 let got = client.get_raw_mempool().await.unwrap();
700 let expected = vec![txid];
701 assert_eq!(expected, got.0);
702
703 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 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 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 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 let got = client.send_raw_transaction(&tx).await.unwrap();
739 assert!(got.as_byte_array().len() == 32);
740
741 let got = client.list_transactions(None).await.unwrap();
743 assert_eq!(got.0.len(), 10);
744
745 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 let got = client.get_xpriv().await.unwrap().unwrap().network;
756 let expected = NetworkKind::Test;
757 assert_eq!(expected, got);
758
759 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 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 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 let new_address = bitcoind.client.new_address().unwrap();
926 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); 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 #[tokio::test()]
947 async fn submit_package() {
948 init_tracing();
949
950 let (bitcoind, client) = get_bitcoind_and_client();
951
952 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 CreateRawTransactionOutput::AddressAmount {
977 address: destination.to_string(),
978 amount: amount.to_btc(),
979 },
980 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 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 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 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 #[tokio::test]
1033 async fn submit_package_1p1c() {
1034 init_tracing();
1035
1036 let (bitcoind, client) = get_bitcoind_and_client();
1037
1038 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 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
1071 assert!(parent_broadcasted.is_err());
1072
1073 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 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1106 assert!(child_broadcasted.is_err());
1107
1108 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 let result = invalid_client.get_blockchain_info().await;
1129
1130 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(&bitcoind, 101, None).unwrap();
1195
1196 let destination = client.get_new_address().await.unwrap();
1198 let amount = Amount::from_btc(0.001).unwrap(); let txid = bitcoind
1202 .client
1203 .send_to_address_rbf(&destination, amount)
1204 .unwrap()
1205 .txid()
1206 .unwrap();
1207
1208 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 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 let options = PsbtBumpFeeOptions {
1239 fee_rate: Some(FeeRate::from_sat_per_vb(20).unwrap()), ..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}