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, CreateRawTransactionInput, CreateRawTransactionOutput, CreateWallet,
36 GetAddressInfo, GetBlockVerbosityOne, GetBlockVerbosityZero, GetBlockchainInfo,
37 GetMempoolInfo, GetNewAddress, GetRawMempoolVerbose, GetRawTransactionVerbosityOne,
38 GetRawTransactionVerbosityZero, GetTransaction, GetTxOut, ImportDescriptor,
39 ImportDescriptorResult, ListDescriptors, ListTransactions, ListUnspent,
40 ListUnspentQueryOptions, PreviousTransactionOutput, PsbtBumpFee, PsbtBumpFeeOptions,
41 SighashType, SignRawTransactionWithWallet, SubmitPackage, TestMempoolAccept,
42 WalletCreateFundedPsbt, WalletCreateFundedPsbtOptions, WalletProcessPsbtResult,
43 },
44};
45
46pub type ClientResult<T> = Result<T, ClientError>;
48
49const DEFAULT_MAX_RETRIES: u8 = 3;
51
52const DEFAULT_RETRY_INTERVAL_MS: u64 = 1_000;
54
55pub fn to_value<T>(value: T) -> ClientResult<Value>
57where
58 T: Serialize,
59{
60 serde_json::to_value(value)
61 .map_err(|e| ClientError::Param(format!("Error creating value: {e}")))
62}
63
64#[derive(Debug, Clone)]
66pub struct Client {
67 url: String,
69
70 client: ReqwestClient,
72
73 id: Arc<AtomicUsize>,
79
80 max_retries: u8,
82
83 retry_interval: u64,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89struct Response<R> {
90 pub result: Option<R>,
91 pub error: Option<BitcoinRpcError>,
92 pub id: u64,
93}
94
95impl Client {
96 pub fn new(
98 url: String,
99 username: String,
100 password: String,
101 max_retries: Option<u8>,
102 retry_interval: Option<u64>,
103 ) -> ClientResult<Self> {
104 if username.is_empty() || password.is_empty() {
105 return Err(ClientError::MissingUserPassword);
106 }
107
108 let user_pw = general_purpose::STANDARD.encode(format!("{username}:{password}"));
109 let authorization = format!("Basic {user_pw}")
110 .parse()
111 .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
112
113 let content_type = "application/json"
114 .parse()
115 .map_err(|_| ClientError::Other("Error parsing header".to_string()))?;
116 let headers =
117 HeaderMap::from_iter([(AUTHORIZATION, authorization), (CONTENT_TYPE, content_type)]);
118
119 trace!(headers = ?headers);
120
121 let client = ReqwestClient::builder()
122 .default_headers(headers)
123 .build()
124 .map_err(|e| ClientError::Other(format!("Could not create client: {e}")))?;
125
126 let id = Arc::new(AtomicUsize::new(0));
127
128 let max_retries = max_retries.unwrap_or(DEFAULT_MAX_RETRIES);
129 let retry_interval = retry_interval.unwrap_or(DEFAULT_RETRY_INTERVAL_MS);
130
131 trace!(url = %url, "Created bitcoin client");
132
133 Ok(Self {
134 url,
135 client,
136 id,
137 max_retries,
138 retry_interval,
139 })
140 }
141
142 fn next_id(&self) -> usize {
143 self.id.fetch_add(1, Ordering::AcqRel)
144 }
145
146 async fn call<T: de::DeserializeOwned + fmt::Debug>(
147 &self,
148 method: &str,
149 params: &[Value],
150 ) -> ClientResult<T> {
151 let mut retries = 0;
152 loop {
153 trace!(%method, ?params, %retries, "Calling bitcoin client");
154
155 let id = self.next_id();
156
157 let response = self
158 .client
159 .post(&self.url)
160 .json(&json!({
161 "jsonrpc": "1.0",
162 "id": id,
163 "method": method,
164 "params": params
165 }))
166 .send()
167 .await;
168 trace!(?response, "Response received");
169 match response {
170 Ok(resp) => {
171 let resp = match resp.error_for_status() {
173 Err(e) if e.is_status() => {
174 if let Some(status) = e.status() {
175 let reason =
176 status.canonical_reason().unwrap_or("Unknown").to_string();
177 return Err(ClientError::Status(status.as_u16(), reason));
178 } else {
179 return Err(ClientError::Other(e.to_string()));
180 }
181 }
182 Err(e) => {
183 return Err(ClientError::Other(e.to_string()));
184 }
185 Ok(resp) => resp,
186 };
187
188 let raw_response = resp
189 .text()
190 .await
191 .map_err(|e| ClientError::Parse(e.to_string()))?;
192 trace!(%raw_response, "Raw response received");
193 let data: Response<T> = serde_json::from_str(&raw_response)
194 .map_err(|e| ClientError::Parse(e.to_string()))?;
195 if let Some(err) = data.error {
196 return Err(ClientError::Server(err.code, err.message));
197 }
198 return data
199 .result
200 .ok_or_else(|| ClientError::Other("Empty data received".to_string()));
201 }
202 Err(err) => {
203 warn!(err = %err, "Error calling bitcoin client");
204
205 if err.is_body() {
206 return Err(ClientError::Body(err.to_string()));
208 } else if err.is_status() {
209 let e = match err.status() {
211 Some(code) => ClientError::Status(code.as_u16(), err.to_string()),
212 _ => ClientError::Other(err.to_string()),
213 };
214 return Err(e);
215 } else if err.is_decode() {
216 let e = ClientError::MalformedResponse(err.to_string());
218 warn!(%e, "decoding error, retrying...");
219 } else if err.is_connect() {
220 let e = ClientError::Connection(err.to_string());
222 warn!(%e, "connection error, retrying...");
223 } else if err.is_timeout() {
224 let e = ClientError::Timeout;
226 warn!(%e, "timeout error, retrying...");
227 } else if err.is_request() {
228 let e = ClientError::Request(err.to_string());
230 warn!(%e, "request error, retrying...");
231 } else if err.is_builder() {
232 return Err(ClientError::ReqBuilder(err.to_string()));
234 } else if err.is_redirect() {
235 return Err(ClientError::HttpRedirect(err.to_string()));
237 } else {
238 return Err(ClientError::Other("Unknown error".to_string()));
240 }
241 }
242 }
243 retries += 1;
244 if retries >= self.max_retries {
245 return Err(ClientError::MaxRetriesExceeded(self.max_retries));
246 }
247 sleep(Duration::from_millis(self.retry_interval)).await;
248 }
249 }
250}
251
252impl Reader for Client {
253 async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
254 let result = self
255 .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
256 .await?
257 .to_string();
258
259 let result_map: Value = result.parse::<Value>()?;
260
261 let btc_vkb = result_map
262 .get("feerate")
263 .unwrap_or(&"0.00001".parse::<Value>().unwrap())
264 .as_f64()
265 .unwrap();
266
267 Ok((btc_vkb * 100_000_000.0 / 1000.0) as u64)
269 }
270
271 async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
272 let get_block_header = self
273 .call::<GetBlockHeaderVerbosityZero>(
274 "getblockheader",
275 &[to_value(hash.to_string())?, to_value(false)?],
276 )
277 .await?;
278 let header = get_block_header
279 .header()
280 .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
281 Ok(header)
282 }
283
284 async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
285 let get_block = self
286 .call::<GetBlockVerbosityZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
287 .await?;
288 let block = get_block
289 .block()
290 .map_err(|err| ClientError::Other(format!("block decode: {err}")))?;
291 Ok(block)
292 }
293
294 async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
295 let block_verobose = self
296 .call::<GetBlockVerbosityOne>("getblock", &[to_value(hash.to_string())?])
297 .await?;
298
299 let block_height = block_verobose.height as u64;
300 Ok(block_height)
301 }
302
303 async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
304 let hash = self.get_block_hash(height).await?;
305 self.get_block_header(&hash).await
306 }
307
308 async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
309 let hash = self.get_block_hash(height).await?;
310 self.get_block(&hash).await
311 }
312
313 async fn get_block_count(&self) -> ClientResult<u64> {
314 self.call::<u64>("getblockcount", &[]).await
315 }
316
317 async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
318 self.call::<BlockHash>("getblockhash", &[to_value(height)?])
319 .await
320 }
321
322 async fn get_blockchain_info(&self) -> ClientResult<GetBlockchainInfo> {
323 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
324 .await
325 }
326
327 async fn get_current_timestamp(&self) -> ClientResult<u32> {
328 let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
329 let block = self.get_block(&best_block_hash).await?;
330 Ok(block.header.time)
331 }
332
333 async fn get_raw_mempool(&self) -> ClientResult<Vec<Txid>> {
334 self.call::<Vec<Txid>>("getrawmempool", &[]).await
335 }
336
337 async fn get_raw_mempool_verbose(&self) -> ClientResult<GetRawMempoolVerbose> {
338 self.call::<GetRawMempoolVerbose>("getrawmempool", &[to_value(true)?])
339 .await
340 }
341
342 async fn get_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
343 self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
344 }
345
346 async fn get_raw_transaction_verbosity_zero(
347 &self,
348 txid: &Txid,
349 ) -> ClientResult<GetRawTransactionVerbosityZero> {
350 self.call::<GetRawTransactionVerbosityZero>(
351 "getrawtransaction",
352 &[to_value(txid.to_string())?, to_value(0)?],
353 )
354 .await
355 }
356
357 async fn get_raw_transaction_verbosity_one(
358 &self,
359 txid: &Txid,
360 ) -> ClientResult<GetRawTransactionVerbosityOne> {
361 self.call::<GetRawTransactionVerbosityOne>(
362 "getrawtransaction",
363 &[to_value(txid.to_string())?, to_value(1)?],
364 )
365 .await
366 }
367
368 async fn get_tx_out(
369 &self,
370 txid: &Txid,
371 vout: u32,
372 include_mempool: bool,
373 ) -> ClientResult<GetTxOut> {
374 self.call::<GetTxOut>(
375 "gettxout",
376 &[
377 to_value(txid.to_string())?,
378 to_value(vout)?,
379 to_value(include_mempool)?,
380 ],
381 )
382 .await
383 }
384
385 async fn network(&self) -> ClientResult<Network> {
386 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
387 .await?
388 .chain
389 .parse::<Network>()
390 .map_err(|e| ClientError::Parse(e.to_string()))
391 }
392}
393
394impl Broadcaster for Client {
395 async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
396 let txstr = serialize_hex(tx);
397 trace!(txstr = %txstr, "Sending raw transaction");
398 match self
399 .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
400 .await
401 {
402 Ok(txid) => {
403 trace!(?txid, "Transaction sent");
404 Ok(txid)
405 }
406 Err(ClientError::Server(i, s)) => match i {
407 -27 => Ok(tx.compute_txid()), _ => Err(ClientError::Server(i, s)),
410 },
411 Err(e) => Err(ClientError::Other(e.to_string())),
412 }
413 }
414
415 async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
416 let txstr = serialize_hex(tx);
417 trace!(%txstr, "Testing mempool accept");
418 self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
419 .await
420 }
421
422 async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
423 let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
424 self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
425 .await
426 }
427}
428
429impl Wallet for Client {
430 async fn get_new_address(&self) -> ClientResult<Address> {
431 let address_unchecked = self
432 .call::<GetNewAddress>("getnewaddress", &[])
433 .await?
434 .0
435 .parse::<Address<_>>()
436 .map_err(|e| ClientError::Parse(e.to_string()))?
437 .assume_checked();
438 Ok(address_unchecked)
439 }
440 async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
441 self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
442 .await
443 }
444
445 async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
446 let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
447 trace!(?resp, "Got UTXOs");
448 Ok(resp)
449 }
450
451 async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
452 self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
453 .await
454 }
455
456 async fn list_wallets(&self) -> ClientResult<Vec<String>> {
457 self.call::<Vec<String>>("listwallets", &[]).await
458 }
459
460 async fn create_raw_transaction(
461 &self,
462 raw_tx: CreateRawTransaction,
463 ) -> ClientResult<Transaction> {
464 let raw_tx = self
465 .call::<String>(
466 "createrawtransaction",
467 &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
468 )
469 .await?;
470 trace!(%raw_tx, "Created raw transaction");
471 consensus::encode::deserialize_hex(&raw_tx)
472 .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
473 }
474
475 async fn wallet_create_funded_psbt(
476 &self,
477 inputs: &[CreateRawTransactionInput],
478 outputs: &[CreateRawTransactionOutput],
479 locktime: Option<u32>,
480 options: Option<WalletCreateFundedPsbtOptions>,
481 bip32_derivs: Option<bool>,
482 ) -> ClientResult<WalletCreateFundedPsbt> {
483 self.call::<WalletCreateFundedPsbt>(
484 "walletcreatefundedpsbt",
485 &[
486 to_value(inputs)?,
487 to_value(outputs)?,
488 to_value(locktime.unwrap_or(0))?,
489 to_value(options.unwrap_or_default())?,
490 to_value(bip32_derivs)?,
491 ],
492 )
493 .await
494 }
495
496 async fn get_address_info(&self, address: &Address) -> ClientResult<GetAddressInfo> {
497 trace!(address = %address, "Getting address info");
498 self.call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
499 .await
500 }
501
502 async fn list_unspent(
503 &self,
504 min_conf: Option<u32>,
505 max_conf: Option<u32>,
506 addresses: Option<&[Address]>,
507 include_unsafe: Option<bool>,
508 query_options: Option<ListUnspentQueryOptions>,
509 ) -> ClientResult<Vec<ListUnspent>> {
510 let addr_strings: Vec<String> = addresses
511 .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
512 .unwrap_or_default();
513
514 let mut params = vec![
515 to_value(min_conf.unwrap_or(1))?,
516 to_value(max_conf.unwrap_or(9_999_999))?,
517 to_value(addr_strings)?,
518 to_value(include_unsafe.unwrap_or(true))?,
519 ];
520
521 if let Some(query_options) = query_options {
522 params.push(to_value(query_options)?);
523 }
524
525 self.call::<Vec<ListUnspent>>("listunspent", ¶ms).await
526 }
527}
528
529impl Signer for Client {
530 async fn sign_raw_transaction_with_wallet(
531 &self,
532 tx: &Transaction,
533 prev_outputs: Option<Vec<PreviousTransactionOutput>>,
534 ) -> ClientResult<SignRawTransactionWithWallet> {
535 let tx_hex = serialize_hex(tx);
536 trace!(tx_hex = %tx_hex, "Signing transaction");
537 trace!(?prev_outputs, "Signing transaction with previous outputs");
538 self.call::<SignRawTransactionWithWallet>(
539 "signrawtransactionwithwallet",
540 &[to_value(tx_hex)?, to_value(prev_outputs)?],
541 )
542 .await
543 }
544
545 async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
546 if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
548 return Ok(None);
549 }
550
551 let descriptors = self
552 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
554 .descriptors;
555 if descriptors.is_empty() {
556 return Err(ClientError::Other("No descriptors found".to_string()));
557 }
558
559 let descriptor = descriptors
561 .iter()
562 .find(|d| d.desc.contains("tr("))
563 .map(|d| d.desc.clone())
564 .ok_or(ClientError::Xpriv)?;
565
566 let xpriv_str = descriptor
568 .split("tr(")
569 .nth(1)
570 .ok_or(ClientError::Xpriv)?
571 .split("/")
572 .next()
573 .ok_or(ClientError::Xpriv)?;
574
575 let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
576 Ok(Some(xpriv))
577 }
578
579 async fn import_descriptors(
580 &self,
581 descriptors: Vec<ImportDescriptor>,
582 wallet_name: String,
583 ) -> ClientResult<Vec<ImportDescriptorResult>> {
584 let wallet_args = CreateWallet {
585 wallet_name,
586 load_on_startup: Some(true),
587 };
588
589 let _wallet_create = self
592 .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
593 .await;
594 let _wallet_load = self
596 .call::<Value>("loadwallet", &[to_value(wallet_args)?])
597 .await;
598
599 let result = self
600 .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
601 .await?;
602 Ok(result)
603 }
604
605 async fn wallet_process_psbt(
606 &self,
607 psbt: &str,
608 sign: Option<bool>,
609 sighashtype: Option<SighashType>,
610 bip32_derivs: Option<bool>,
611 ) -> ClientResult<WalletProcessPsbtResult> {
612 let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
613
614 if let Some(sighashtype) = sighashtype {
615 params.push(to_value(sighashtype)?);
616 }
617
618 if let Some(bip32_derivs) = bip32_derivs {
619 params.push(to_value(bip32_derivs)?);
620 }
621
622 self.call::<WalletProcessPsbtResult>("walletprocesspsbt", ¶ms)
623 .await
624 }
625
626 async fn psbt_bump_fee(
627 &self,
628 txid: &Txid,
629 options: Option<PsbtBumpFeeOptions>,
630 ) -> ClientResult<PsbtBumpFee> {
631 let mut params = vec![to_value(txid.to_string())?];
632
633 if let Some(options) = options {
634 params.push(to_value(options)?);
635 }
636
637 self.call::<PsbtBumpFee>("psbtbumpfee", ¶ms).await
638 }
639}
640
641#[cfg(test)]
642mod test {
643
644 use std::sync::Once;
645
646 use bitcoin::{
647 consensus::{self, encode::deserialize_hex},
648 hashes::Hash,
649 transaction, Amount, FeeRate, NetworkKind,
650 };
651 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
652
653 use super::*;
654 use crate::{
655 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
656 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
657 };
658
659 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
661
662 fn init_tracing() {
664 static INIT: Once = Once::new();
665
666 INIT.call_once(|| {
667 tracing_subscriber::registry()
668 .with(fmt::layer())
669 .with(EnvFilter::from_default_env())
670 .try_init()
671 .ok();
672 });
673 }
674
675 #[tokio::test()]
676 async fn client_works() {
677 init_tracing();
678
679 let (bitcoind, client) = get_bitcoind_and_client();
680
681 let got = client.network().await.unwrap();
683 let expected = Network::Regtest;
684
685 assert_eq!(expected, got);
686 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
688 assert_eq!(get_blockchain_info.blocks, 0);
689
690 let _ = client
692 .get_current_timestamp()
693 .await
694 .expect("must be able to get current timestamp");
695
696 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
697
698 let expected = blocks.last().unwrap();
700 let got = client.get_block(expected).await.unwrap().block_hash();
701 assert_eq!(*expected, got);
702
703 let target_height = blocks.len() as u64;
705 let expected = blocks.last().unwrap();
706 let got = client
707 .get_block_at(target_height)
708 .await
709 .unwrap()
710 .block_hash();
711 assert_eq!(*expected, got);
712
713 let expected = blocks.len() as u64;
715 let got = client.get_block_count().await.unwrap();
716 assert_eq!(expected, got);
717
718 let target_height = blocks.len() as u64;
720 let expected = blocks.last().unwrap();
721 let got = client.get_block_hash(target_height).await.unwrap();
722 assert_eq!(*expected, got);
723
724 let address = client.get_new_address().await.unwrap();
726 let txid = client
727 .call::<String>(
728 "sendtoaddress",
729 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
730 )
731 .await
732 .unwrap()
733 .parse::<Txid>()
734 .unwrap();
735
736 let tx = client.get_transaction(&txid).await.unwrap().hex;
738 let got = client.send_raw_transaction(&tx).await.unwrap();
739 let expected = txid; assert_eq!(expected, got);
741
742 let got = client
744 .get_raw_transaction_verbosity_zero(&txid)
745 .await
746 .unwrap()
747 .0;
748 let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
749 assert_eq!(expected, got);
750
751 let got = client
753 .get_raw_transaction_verbosity_one(&txid)
754 .await
755 .unwrap()
756 .transaction
757 .compute_txid();
758 assert_eq!(expected, got);
759
760 let got = client.get_raw_mempool().await.unwrap();
762 let expected = vec![txid];
763 assert_eq!(expected, got);
764
765 let got = client.get_raw_mempool_verbose().await.unwrap();
767 assert_eq!(got.len(), 1);
768 assert_eq!(got.get(&txid).unwrap().height, 101);
769
770 let got = client.get_mempool_info().await.unwrap();
772 assert!(got.loaded);
773 assert_eq!(got.size, 1);
774 assert_eq!(got.unbroadcastcount, 1);
775
776 let got = client.estimate_smart_fee(1).await.unwrap();
778 let expected = 1; assert_eq!(expected, got);
780
781 let got = client
783 .sign_raw_transaction_with_wallet(&tx, None)
784 .await
785 .unwrap();
786 assert!(got.complete);
787 assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
788
789 let txids = client
791 .test_mempool_accept(&tx)
792 .await
793 .expect("must be able to test mempool accept");
794 let got = txids.first().expect("there must be at least one txid");
795 assert_eq!(
796 got.txid,
797 tx.compute_txid(),
798 "txids must match in the mempool"
799 );
800
801 let got = client.send_raw_transaction(&tx).await.unwrap();
803 assert!(got.as_byte_array().len() == 32);
804
805 let got = client.list_transactions(None).await.unwrap();
807 assert_eq!(got.len(), 10);
808
809 mine_blocks(&bitcoind, 1, None).unwrap();
812 let got = client
813 .list_unspent(None, None, None, None, None)
814 .await
815 .unwrap();
816 assert_eq!(got.len(), 3);
817
818 let got = client.get_xpriv().await.unwrap().unwrap().network;
820 let expected = NetworkKind::Test;
821 assert_eq!(expected, got);
822
823 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
826 let timestamp = "now".to_owned();
827 let list_descriptors = vec![ImportDescriptor {
828 desc: descriptor_string,
829 active: Some(true),
830 timestamp,
831 }];
832 let got = client
833 .import_descriptors(list_descriptors, "strata".to_owned())
834 .await
835 .unwrap();
836 let expected = vec![ImportDescriptorResult { success: true }];
837 assert_eq!(expected, got);
838
839 let psbt_address = client.get_new_address().await.unwrap();
840 let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
841 address: psbt_address.to_string(),
842 amount: 1.0,
843 }];
844
845 let funded_psbt = client
846 .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
847 .await
848 .unwrap();
849 assert!(!funded_psbt.psbt.inputs.is_empty());
850 assert!(funded_psbt.fee.to_sat() > 0);
851
852 let processed_psbt = client
853 .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
854 .await
855 .unwrap();
856 assert!(!processed_psbt.psbt.as_ref().unwrap().inputs.is_empty());
857 assert!(processed_psbt.complete);
858
859 let finalized_psbt = client
860 .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
861 .await
862 .unwrap();
863 assert!(finalized_psbt.complete);
864 assert!(finalized_psbt.hex.is_some());
865 let signed_tx = finalized_psbt.hex.as_ref().unwrap();
866 let signed_txid = signed_tx.compute_txid();
867 let got = client
868 .test_mempool_accept(signed_tx)
869 .await
870 .unwrap()
871 .first()
872 .unwrap()
873 .txid;
874 assert_eq!(signed_txid, got);
875
876 let info_address = client.get_new_address().await.unwrap();
877 let address_info = client.get_address_info(&info_address).await.unwrap();
878 assert_eq!(address_info.address, info_address.as_unchecked().clone());
879 assert!(address_info.is_mine.unwrap_or(false));
880 assert!(address_info.solvable.unwrap_or(false));
881
882 let unspent_address = client.get_new_address().await.unwrap();
883 let unspent_txid = client
884 .call::<String>(
885 "sendtoaddress",
886 &[
887 to_value(unspent_address.to_string()).unwrap(),
888 to_value(1.0).unwrap(),
889 ],
890 )
891 .await
892 .unwrap();
893 mine_blocks(&bitcoind, 1, None).unwrap();
894
895 let utxos = client
896 .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
897 .await
898 .unwrap();
899 assert!(!utxos.is_empty());
900
901 let utxos_filtered = client
902 .list_unspent(
903 Some(1),
904 Some(9_999_999),
905 Some(std::slice::from_ref(&unspent_address)),
906 Some(true),
907 None,
908 )
909 .await
910 .unwrap();
911 assert!(!utxos_filtered.is_empty());
912 let found_utxo = utxos_filtered.iter().any(|utxo| {
913 utxo.txid.to_string() == unspent_txid
914 && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
915 });
916 assert!(found_utxo);
917
918 let query_options = ListUnspentQueryOptions {
919 minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
920 maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
921 maximum_count: Some(10),
922 };
923 let utxos_with_query = client
924 .list_unspent(
925 Some(1),
926 Some(9_999_999),
927 None,
928 Some(true),
929 Some(query_options),
930 )
931 .await
932 .unwrap();
933 assert!(!utxos_with_query.is_empty());
934 for utxo in &utxos_with_query {
935 let amount_btc = utxo.amount.to_btc();
936 assert!((0.5..=2.0).contains(&amount_btc));
937 }
938
939 let tx = finalized_psbt.hex.unwrap();
940 assert!(!tx.input.is_empty());
941 assert!(!tx.output.is_empty());
942 }
943
944 #[tokio::test()]
945 async fn get_tx_out() {
946 init_tracing();
947
948 let (bitcoind, client) = get_bitcoind_and_client();
949
950 let got = client.network().await.unwrap();
952 let expected = Network::Regtest;
953 assert_eq!(expected, got);
954
955 let address = bitcoind.client.new_address().unwrap();
956 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
957 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
958 let coinbase_tx = last_block.coinbase().unwrap();
959
960 let got = client
962 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
963 .await
964 .unwrap();
965 assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
966
967 let new_address = bitcoind.client.new_address().unwrap();
969 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
971 .client
972 .send_to_address(&new_address, send_amount)
973 .unwrap()
974 .txid()
975 .unwrap();
976 let result = client
977 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
978 .await;
979 trace!(?result, "gettxout result");
980 assert!(result.is_err());
981 }
982
983 #[tokio::test()]
990 async fn submit_package() {
991 init_tracing();
992
993 let (bitcoind, client) = get_bitcoind_and_client();
994
995 let got = client.network().await.unwrap();
997 let expected = Network::Regtest;
998 assert_eq!(expected, got);
999
1000 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1001 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1002 let coinbase_tx = last_block.coinbase().unwrap();
1003
1004 let destination = client.get_new_address().await.unwrap();
1005 let change_address = client.get_new_address().await.unwrap();
1006 let amount = Amount::from_btc(1.0).unwrap();
1007 let fees = Amount::from_btc(0.0001).unwrap();
1008 let change_amount = COINBASE_AMOUNT - amount - fees;
1009 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
1010
1011 let send_back_address = client.get_new_address().await.unwrap();
1012 let parent_raw_tx = CreateRawTransaction {
1013 inputs: vec![CreateRawTransactionInput {
1014 txid: coinbase_tx.compute_txid().to_string(),
1015 vout: 0,
1016 }],
1017 outputs: vec![
1018 CreateRawTransactionOutput::AddressAmount {
1020 address: destination.to_string(),
1021 amount: amount.to_btc(),
1022 },
1023 CreateRawTransactionOutput::AddressAmount {
1025 address: change_address.to_string(),
1026 amount: change_amount.to_btc(),
1027 },
1028 ],
1029 };
1030 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1031 let signed_parent: Transaction = consensus::encode::deserialize_hex(
1032 client
1033 .sign_raw_transaction_with_wallet(&parent, None)
1034 .await
1035 .unwrap()
1036 .hex
1037 .as_str(),
1038 )
1039 .unwrap();
1040
1041 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
1043
1044 let child_raw_tx = CreateRawTransaction {
1045 inputs: vec![CreateRawTransactionInput {
1046 txid: parent_submitted.to_string(),
1047 vout: 0,
1048 }],
1049 outputs: vec![
1050 CreateRawTransactionOutput::AddressAmount {
1052 address: send_back_address.to_string(),
1053 amount: amount_minus_fees.to_btc(),
1054 },
1055 ],
1056 };
1057 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1058 let signed_child: Transaction = consensus::encode::deserialize_hex(
1059 client
1060 .sign_raw_transaction_with_wallet(&child, None)
1061 .await
1062 .unwrap()
1063 .hex
1064 .as_str(),
1065 )
1066 .unwrap();
1067
1068 let result = client
1070 .submit_package(&[signed_parent, signed_child])
1071 .await
1072 .unwrap();
1073 assert_eq!(result.tx_results.len(), 2);
1074 assert_eq!(result.package_msg, "success");
1075 }
1076
1077 #[tokio::test]
1084 async fn submit_package_1p1c() {
1085 init_tracing();
1086
1087 let (bitcoind, client) = get_bitcoind_and_client();
1088
1089 let server_version = bitcoind.client.server_version().unwrap();
1091 assert!(server_version > 28);
1092
1093 let destination = client.get_new_address().await.unwrap();
1094
1095 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1096 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1097 let coinbase_tx = last_block.coinbase().unwrap();
1098
1099 let parent_raw_tx = CreateRawTransaction {
1100 inputs: vec![CreateRawTransactionInput {
1101 txid: coinbase_tx.compute_txid().to_string(),
1102 vout: 0,
1103 }],
1104 outputs: vec![CreateRawTransactionOutput::AddressAmount {
1105 address: destination.to_string(),
1106 amount: COINBASE_AMOUNT.to_btc(),
1107 }],
1108 };
1109 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1110 parent.version = transaction::Version(3);
1111 assert_eq!(parent.version, transaction::Version(3));
1112 trace!(?parent, "parent:");
1113 let signed_parent: Transaction = consensus::encode::deserialize_hex(
1114 client
1115 .sign_raw_transaction_with_wallet(&parent, None)
1116 .await
1117 .unwrap()
1118 .hex
1119 .as_str(),
1120 )
1121 .unwrap();
1122 assert_eq!(signed_parent.version, transaction::Version(3));
1123
1124 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
1126 assert!(parent_broadcasted.is_err());
1127
1128 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
1130 let child_raw_tx = CreateRawTransaction {
1131 inputs: vec![CreateRawTransactionInput {
1132 txid: signed_parent.compute_txid().to_string(),
1133 vout: 0,
1134 }],
1135 outputs: vec![CreateRawTransactionOutput::AddressAmount {
1136 address: destination.to_string(),
1137 amount: amount_minus_fees.to_btc(),
1138 }],
1139 };
1140 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1141 child.version = transaction::Version(3);
1142 assert_eq!(child.version, transaction::Version(3));
1143 trace!(?child, "child:");
1144 let prev_outputs = vec![PreviousTransactionOutput {
1145 txid: parent.compute_txid(),
1146 vout: 0,
1147 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
1148 redeem_script: None,
1149 witness_script: None,
1150 amount: Some(COINBASE_AMOUNT.to_btc()),
1151 }];
1152 let signed_child: Transaction = consensus::encode::deserialize_hex(
1153 client
1154 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
1155 .await
1156 .unwrap()
1157 .hex
1158 .as_str(),
1159 )
1160 .unwrap();
1161 assert_eq!(signed_child.version, transaction::Version(3));
1162
1163 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1165 assert!(child_broadcasted.is_err());
1166
1167 let result = client
1169 .submit_package(&[signed_parent, signed_child])
1170 .await
1171 .unwrap();
1172 assert_eq!(result.tx_results.len(), 2);
1173 assert_eq!(result.package_msg, "success");
1174 }
1175
1176 #[tokio::test]
1177 async fn test_invalid_credentials_return_401_error() {
1178 init_tracing();
1179
1180 let (bitcoind, _) = get_bitcoind_and_client();
1181 let url = bitcoind.rpc_url();
1182
1183 let invalid_client = Client::new(
1185 url,
1186 "wrong_user".to_string(),
1187 "wrong_password".to_string(),
1188 None,
1189 None,
1190 )
1191 .unwrap();
1192
1193 let result = invalid_client.get_blockchain_info().await;
1195
1196 assert!(result.is_err());
1198 let error = result.unwrap_err();
1199
1200 match error {
1201 ClientError::Status(status_code, message) => {
1202 assert_eq!(status_code, 401);
1203 assert!(message.contains("Unauthorized"));
1204 }
1205 _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1206 }
1207 }
1208
1209 #[tokio::test]
1210 async fn psbt_bump_fee() {
1211 init_tracing();
1212
1213 let (bitcoind, client) = get_bitcoind_and_client();
1214
1215 mine_blocks(&bitcoind, 101, None).unwrap();
1217
1218 let destination = client.get_new_address().await.unwrap();
1220 let amount = Amount::from_btc(0.001).unwrap(); let txid = bitcoind
1224 .client
1225 .send_to_address_rbf(&destination, amount)
1226 .unwrap()
1227 .txid()
1228 .unwrap();
1229
1230 let mempool = client.get_raw_mempool().await.unwrap();
1232 assert!(
1233 mempool.contains(&txid),
1234 "Transaction should be in mempool for RBF"
1235 );
1236
1237 let signed_tx = client
1239 .psbt_bump_fee(&txid, None)
1240 .await
1241 .unwrap()
1242 .psbt
1243 .extract_tx()
1244 .unwrap();
1245 let signed_txid = signed_tx.compute_txid();
1246 let got = client
1247 .test_mempool_accept(&signed_tx)
1248 .await
1249 .unwrap()
1250 .first()
1251 .unwrap()
1252 .txid;
1253 assert_eq!(
1254 got, signed_txid,
1255 "Bumped transaction should be accepted in mempool"
1256 );
1257
1258 let options = PsbtBumpFeeOptions {
1260 fee_rate: Some(FeeRate::from_sat_per_kwu(20)), ..Default::default()
1262 };
1263 trace!(?options, "Calling psbt_bump_fee");
1264 let signed_tx = client
1265 .psbt_bump_fee(&txid, Some(options))
1266 .await
1267 .unwrap()
1268 .psbt
1269 .extract_tx()
1270 .unwrap();
1271 let signed_txid = signed_tx.compute_txid();
1272 let got = client
1273 .test_mempool_accept(&signed_tx)
1274 .await
1275 .unwrap()
1276 .first()
1277 .unwrap()
1278 .txid;
1279 assert_eq!(
1280 got, signed_txid,
1281 "Bumped transaction should be accepted in mempool"
1282 );
1283 }
1284}