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