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, 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_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
338 self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
339 }
340
341 async fn get_raw_transaction_verbosity_zero(
342 &self,
343 txid: &Txid,
344 ) -> ClientResult<GetRawTransactionVerbosityZero> {
345 self.call::<GetRawTransactionVerbosityZero>(
346 "getrawtransaction",
347 &[to_value(txid.to_string())?, to_value(0)?],
348 )
349 .await
350 }
351
352 async fn get_raw_transaction_verbosity_one(
353 &self,
354 txid: &Txid,
355 ) -> ClientResult<GetRawTransactionVerbosityOne> {
356 self.call::<GetRawTransactionVerbosityOne>(
357 "getrawtransaction",
358 &[to_value(txid.to_string())?, to_value(1)?],
359 )
360 .await
361 }
362
363 async fn get_tx_out(
364 &self,
365 txid: &Txid,
366 vout: u32,
367 include_mempool: bool,
368 ) -> ClientResult<GetTxOut> {
369 self.call::<GetTxOut>(
370 "gettxout",
371 &[
372 to_value(txid.to_string())?,
373 to_value(vout)?,
374 to_value(include_mempool)?,
375 ],
376 )
377 .await
378 }
379
380 async fn network(&self) -> ClientResult<Network> {
381 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
382 .await?
383 .chain
384 .parse::<Network>()
385 .map_err(|e| ClientError::Parse(e.to_string()))
386 }
387}
388
389impl Broadcaster for Client {
390 async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
391 let txstr = serialize_hex(tx);
392 trace!(txstr = %txstr, "Sending raw transaction");
393 match self
394 .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
395 .await
396 {
397 Ok(txid) => {
398 trace!(?txid, "Transaction sent");
399 Ok(txid)
400 }
401 Err(ClientError::Server(i, s)) => match i {
402 -27 => Ok(tx.compute_txid()), _ => Err(ClientError::Server(i, s)),
405 },
406 Err(e) => Err(ClientError::Other(e.to_string())),
407 }
408 }
409
410 async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
411 let txstr = serialize_hex(tx);
412 trace!(%txstr, "Testing mempool accept");
413 self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
414 .await
415 }
416
417 async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
418 let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
419 self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
420 .await
421 }
422}
423
424impl Wallet for Client {
425 async fn get_new_address(&self) -> ClientResult<Address> {
426 let address_unchecked = self
427 .call::<GetNewAddress>("getnewaddress", &[])
428 .await?
429 .0
430 .parse::<Address<_>>()
431 .map_err(|e| ClientError::Parse(e.to_string()))?
432 .assume_checked();
433 Ok(address_unchecked)
434 }
435 async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
436 self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
437 .await
438 }
439
440 async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
441 let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
442 trace!(?resp, "Got UTXOs");
443 Ok(resp)
444 }
445
446 async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
447 self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
448 .await
449 }
450
451 async fn list_wallets(&self) -> ClientResult<Vec<String>> {
452 self.call::<Vec<String>>("listwallets", &[]).await
453 }
454
455 async fn create_raw_transaction(
456 &self,
457 raw_tx: CreateRawTransaction,
458 ) -> ClientResult<Transaction> {
459 let raw_tx = self
460 .call::<String>(
461 "createrawtransaction",
462 &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
463 )
464 .await?;
465 trace!(%raw_tx, "Created raw transaction");
466 consensus::encode::deserialize_hex(&raw_tx)
467 .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
468 }
469
470 async fn wallet_create_funded_psbt(
471 &self,
472 inputs: &[CreateRawTransactionInput],
473 outputs: &[CreateRawTransactionOutput],
474 locktime: Option<u32>,
475 options: Option<WalletCreateFundedPsbtOptions>,
476 bip32_derivs: Option<bool>,
477 ) -> ClientResult<WalletCreateFundedPsbt> {
478 self.call::<WalletCreateFundedPsbt>(
479 "walletcreatefundedpsbt",
480 &[
481 to_value(inputs)?,
482 to_value(outputs)?,
483 to_value(locktime.unwrap_or(0))?,
484 to_value(options.unwrap_or_default())?,
485 to_value(bip32_derivs)?,
486 ],
487 )
488 .await
489 }
490
491 async fn get_address_info(&self, address: &Address) -> ClientResult<GetAddressInfo> {
492 trace!(address = %address, "Getting address info");
493 self.call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
494 .await
495 }
496
497 async fn list_unspent(
498 &self,
499 min_conf: Option<u32>,
500 max_conf: Option<u32>,
501 addresses: Option<&[Address]>,
502 include_unsafe: Option<bool>,
503 query_options: Option<ListUnspentQueryOptions>,
504 ) -> ClientResult<Vec<ListUnspent>> {
505 let addr_strings: Vec<String> = addresses
506 .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
507 .unwrap_or_default();
508
509 let mut params = vec![
510 to_value(min_conf.unwrap_or(1))?,
511 to_value(max_conf.unwrap_or(9_999_999))?,
512 to_value(addr_strings)?,
513 to_value(include_unsafe.unwrap_or(true))?,
514 ];
515
516 if let Some(query_options) = query_options {
517 params.push(to_value(query_options)?);
518 }
519
520 self.call::<Vec<ListUnspent>>("listunspent", ¶ms).await
521 }
522}
523
524impl Signer for Client {
525 async fn sign_raw_transaction_with_wallet(
526 &self,
527 tx: &Transaction,
528 prev_outputs: Option<Vec<PreviousTransactionOutput>>,
529 ) -> ClientResult<SignRawTransactionWithWallet> {
530 let tx_hex = serialize_hex(tx);
531 trace!(tx_hex = %tx_hex, "Signing transaction");
532 trace!(?prev_outputs, "Signing transaction with previous outputs");
533 self.call::<SignRawTransactionWithWallet>(
534 "signrawtransactionwithwallet",
535 &[to_value(tx_hex)?, to_value(prev_outputs)?],
536 )
537 .await
538 }
539
540 async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
541 if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
543 return Ok(None);
544 }
545
546 let descriptors = self
547 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
549 .descriptors;
550 if descriptors.is_empty() {
551 return Err(ClientError::Other("No descriptors found".to_string()));
552 }
553
554 let descriptor = descriptors
556 .iter()
557 .find(|d| d.desc.contains("tr("))
558 .map(|d| d.desc.clone())
559 .ok_or(ClientError::Xpriv)?;
560
561 let xpriv_str = descriptor
563 .split("tr(")
564 .nth(1)
565 .ok_or(ClientError::Xpriv)?
566 .split("/")
567 .next()
568 .ok_or(ClientError::Xpriv)?;
569
570 let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
571 Ok(Some(xpriv))
572 }
573
574 async fn import_descriptors(
575 &self,
576 descriptors: Vec<ImportDescriptor>,
577 wallet_name: String,
578 ) -> ClientResult<Vec<ImportDescriptorResult>> {
579 let wallet_args = CreateWallet {
580 wallet_name,
581 load_on_startup: Some(true),
582 };
583
584 let _wallet_create = self
587 .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
588 .await;
589 let _wallet_load = self
591 .call::<Value>("loadwallet", &[to_value(wallet_args)?])
592 .await;
593
594 let result = self
595 .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
596 .await?;
597 Ok(result)
598 }
599
600 async fn wallet_process_psbt(
601 &self,
602 psbt: &str,
603 sign: Option<bool>,
604 sighashtype: Option<SighashType>,
605 bip32_derivs: Option<bool>,
606 ) -> ClientResult<WalletProcessPsbtResult> {
607 let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
608
609 if let Some(sighashtype) = sighashtype {
610 params.push(to_value(sighashtype)?);
611 }
612
613 if let Some(bip32_derivs) = bip32_derivs {
614 params.push(to_value(bip32_derivs)?);
615 }
616
617 self.call::<WalletProcessPsbtResult>("walletprocesspsbt", ¶ms)
618 .await
619 }
620
621 async fn psbt_bump_fee(
622 &self,
623 txid: &Txid,
624 options: Option<PsbtBumpFeeOptions>,
625 ) -> ClientResult<PsbtBumpFee> {
626 let mut params = vec![to_value(txid.to_string())?];
627
628 if let Some(options) = options {
629 params.push(to_value(options)?);
630 }
631
632 self.call::<PsbtBumpFee>("psbtbumpfee", ¶ms).await
633 }
634}
635
636#[cfg(test)]
637mod test {
638
639 use std::sync::Once;
640
641 use bitcoin::{
642 consensus::{self, encode::deserialize_hex},
643 hashes::Hash,
644 transaction, Amount, FeeRate, NetworkKind,
645 };
646 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
647
648 use super::*;
649 use crate::{
650 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
651 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
652 };
653
654 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
656
657 fn init_tracing() {
659 static INIT: Once = Once::new();
660
661 INIT.call_once(|| {
662 tracing_subscriber::registry()
663 .with(fmt::layer())
664 .with(EnvFilter::from_default_env())
665 .try_init()
666 .ok();
667 });
668 }
669
670 #[tokio::test()]
671 async fn client_works() {
672 init_tracing();
673
674 let (bitcoind, client) = get_bitcoind_and_client();
675
676 let got = client.network().await.unwrap();
678 let expected = Network::Regtest;
679
680 assert_eq!(expected, got);
681 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
683 assert_eq!(get_blockchain_info.blocks, 0);
684
685 let _ = client
687 .get_current_timestamp()
688 .await
689 .expect("must be able to get current timestamp");
690
691 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
692
693 let expected = blocks.last().unwrap();
695 let got = client.get_block(expected).await.unwrap().block_hash();
696 assert_eq!(*expected, got);
697
698 let target_height = blocks.len() as u64;
700 let expected = blocks.last().unwrap();
701 let got = client
702 .get_block_at(target_height)
703 .await
704 .unwrap()
705 .block_hash();
706 assert_eq!(*expected, got);
707
708 let expected = blocks.len() as u64;
710 let got = client.get_block_count().await.unwrap();
711 assert_eq!(expected, got);
712
713 let target_height = blocks.len() as u64;
715 let expected = blocks.last().unwrap();
716 let got = client.get_block_hash(target_height).await.unwrap();
717 assert_eq!(*expected, got);
718
719 let address = client.get_new_address().await.unwrap();
721 let txid = client
722 .call::<String>(
723 "sendtoaddress",
724 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
725 )
726 .await
727 .unwrap()
728 .parse::<Txid>()
729 .unwrap();
730
731 let tx = client.get_transaction(&txid).await.unwrap().hex;
733 let got = client.send_raw_transaction(&tx).await.unwrap();
734 let expected = txid; assert_eq!(expected, got);
736
737 let got = client
739 .get_raw_transaction_verbosity_zero(&txid)
740 .await
741 .unwrap()
742 .0;
743 let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
744 assert_eq!(expected, got);
745
746 let got = client
748 .get_raw_transaction_verbosity_one(&txid)
749 .await
750 .unwrap()
751 .transaction
752 .compute_txid();
753 assert_eq!(expected, got);
754
755 let got = client.get_raw_mempool().await.unwrap();
757 let expected = vec![txid];
758 assert_eq!(expected, got);
759
760 let got = client.get_mempool_info().await.unwrap();
762 assert!(got.loaded);
763 assert_eq!(got.size, 1);
764 assert_eq!(got.unbroadcastcount, 1);
765
766 let got = client.estimate_smart_fee(1).await.unwrap();
768 let expected = 1; assert_eq!(expected, got);
770
771 let got = client
773 .sign_raw_transaction_with_wallet(&tx, None)
774 .await
775 .unwrap();
776 assert!(got.complete);
777 assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
778
779 let txids = client
781 .test_mempool_accept(&tx)
782 .await
783 .expect("must be able to test mempool accept");
784 let got = txids.first().expect("there must be at least one txid");
785 assert_eq!(
786 got.txid,
787 tx.compute_txid(),
788 "txids must match in the mempool"
789 );
790
791 let got = client.send_raw_transaction(&tx).await.unwrap();
793 assert!(got.as_byte_array().len() == 32);
794
795 let got = client.list_transactions(None).await.unwrap();
797 assert_eq!(got.len(), 10);
798
799 mine_blocks(&bitcoind, 1, None).unwrap();
802 let got = client
803 .list_unspent(None, None, None, None, None)
804 .await
805 .unwrap();
806 assert_eq!(got.len(), 3);
807
808 let got = client.get_xpriv().await.unwrap().unwrap().network;
810 let expected = NetworkKind::Test;
811 assert_eq!(expected, got);
812
813 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
816 let timestamp = "now".to_owned();
817 let list_descriptors = vec![ImportDescriptor {
818 desc: descriptor_string,
819 active: Some(true),
820 timestamp,
821 }];
822 let got = client
823 .import_descriptors(list_descriptors, "strata".to_owned())
824 .await
825 .unwrap();
826 let expected = vec![ImportDescriptorResult { success: true }];
827 assert_eq!(expected, got);
828
829 let psbt_address = client.get_new_address().await.unwrap();
830 let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
831 address: psbt_address.to_string(),
832 amount: 1.0,
833 }];
834
835 let funded_psbt = client
836 .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
837 .await
838 .unwrap();
839 assert!(!funded_psbt.psbt.inputs.is_empty());
840 assert!(funded_psbt.fee.to_sat() > 0);
841
842 let processed_psbt = client
843 .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
844 .await
845 .unwrap();
846 assert!(!processed_psbt.psbt.as_ref().unwrap().inputs.is_empty());
847 assert!(processed_psbt.complete);
848
849 let finalized_psbt = client
850 .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
851 .await
852 .unwrap();
853 assert!(finalized_psbt.complete);
854 assert!(finalized_psbt.hex.is_some());
855 let signed_tx = finalized_psbt.hex.as_ref().unwrap();
856 let signed_txid = signed_tx.compute_txid();
857 let got = client
858 .test_mempool_accept(signed_tx)
859 .await
860 .unwrap()
861 .first()
862 .unwrap()
863 .txid;
864 assert_eq!(signed_txid, got);
865
866 let info_address = client.get_new_address().await.unwrap();
867 let address_info = client.get_address_info(&info_address).await.unwrap();
868 assert_eq!(address_info.address, info_address.as_unchecked().clone());
869 assert!(address_info.is_mine.unwrap_or(false));
870 assert!(address_info.solvable.unwrap_or(false));
871
872 let unspent_address = client.get_new_address().await.unwrap();
873 let unspent_txid = client
874 .call::<String>(
875 "sendtoaddress",
876 &[
877 to_value(unspent_address.to_string()).unwrap(),
878 to_value(1.0).unwrap(),
879 ],
880 )
881 .await
882 .unwrap();
883 mine_blocks(&bitcoind, 1, None).unwrap();
884
885 let utxos = client
886 .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
887 .await
888 .unwrap();
889 assert!(!utxos.is_empty());
890
891 let utxos_filtered = client
892 .list_unspent(
893 Some(1),
894 Some(9_999_999),
895 Some(std::slice::from_ref(&unspent_address)),
896 Some(true),
897 None,
898 )
899 .await
900 .unwrap();
901 assert!(!utxos_filtered.is_empty());
902 let found_utxo = utxos_filtered.iter().any(|utxo| {
903 utxo.txid.to_string() == unspent_txid
904 && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
905 });
906 assert!(found_utxo);
907
908 let query_options = ListUnspentQueryOptions {
909 minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
910 maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
911 maximum_count: Some(10),
912 };
913 let utxos_with_query = client
914 .list_unspent(
915 Some(1),
916 Some(9_999_999),
917 None,
918 Some(true),
919 Some(query_options),
920 )
921 .await
922 .unwrap();
923 assert!(!utxos_with_query.is_empty());
924 for utxo in &utxos_with_query {
925 let amount_btc = utxo.amount.to_btc();
926 assert!((0.5..=2.0).contains(&amount_btc));
927 }
928
929 let tx = finalized_psbt.hex.unwrap();
930 assert!(!tx.input.is_empty());
931 assert!(!tx.output.is_empty());
932 }
933
934 #[tokio::test()]
935 async fn get_tx_out() {
936 init_tracing();
937
938 let (bitcoind, client) = get_bitcoind_and_client();
939
940 let got = client.network().await.unwrap();
942 let expected = Network::Regtest;
943 assert_eq!(expected, got);
944
945 let address = bitcoind.client.new_address().unwrap();
946 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
947 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
948 let coinbase_tx = last_block.coinbase().unwrap();
949
950 let got = client
952 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
953 .await
954 .unwrap();
955 assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
956
957 let new_address = bitcoind.client.new_address().unwrap();
959 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
961 .client
962 .send_to_address(&new_address, send_amount)
963 .unwrap()
964 .txid()
965 .unwrap();
966 let result = client
967 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
968 .await;
969 trace!(?result, "gettxout result");
970 assert!(result.is_err());
971 }
972
973 #[tokio::test()]
980 async fn submit_package() {
981 init_tracing();
982
983 let (bitcoind, client) = get_bitcoind_and_client();
984
985 let got = client.network().await.unwrap();
987 let expected = Network::Regtest;
988 assert_eq!(expected, got);
989
990 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
991 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
992 let coinbase_tx = last_block.coinbase().unwrap();
993
994 let destination = client.get_new_address().await.unwrap();
995 let change_address = client.get_new_address().await.unwrap();
996 let amount = Amount::from_btc(1.0).unwrap();
997 let fees = Amount::from_btc(0.0001).unwrap();
998 let change_amount = COINBASE_AMOUNT - amount - fees;
999 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
1000
1001 let send_back_address = client.get_new_address().await.unwrap();
1002 let parent_raw_tx = CreateRawTransaction {
1003 inputs: vec![CreateRawTransactionInput {
1004 txid: coinbase_tx.compute_txid().to_string(),
1005 vout: 0,
1006 }],
1007 outputs: vec![
1008 CreateRawTransactionOutput::AddressAmount {
1010 address: destination.to_string(),
1011 amount: amount.to_btc(),
1012 },
1013 CreateRawTransactionOutput::AddressAmount {
1015 address: change_address.to_string(),
1016 amount: change_amount.to_btc(),
1017 },
1018 ],
1019 };
1020 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1021 let signed_parent: Transaction = consensus::encode::deserialize_hex(
1022 client
1023 .sign_raw_transaction_with_wallet(&parent, None)
1024 .await
1025 .unwrap()
1026 .hex
1027 .as_str(),
1028 )
1029 .unwrap();
1030
1031 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
1033
1034 let child_raw_tx = CreateRawTransaction {
1035 inputs: vec![CreateRawTransactionInput {
1036 txid: parent_submitted.to_string(),
1037 vout: 0,
1038 }],
1039 outputs: vec![
1040 CreateRawTransactionOutput::AddressAmount {
1042 address: send_back_address.to_string(),
1043 amount: amount_minus_fees.to_btc(),
1044 },
1045 ],
1046 };
1047 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1048 let signed_child: Transaction = consensus::encode::deserialize_hex(
1049 client
1050 .sign_raw_transaction_with_wallet(&child, None)
1051 .await
1052 .unwrap()
1053 .hex
1054 .as_str(),
1055 )
1056 .unwrap();
1057
1058 let result = client
1060 .submit_package(&[signed_parent, signed_child])
1061 .await
1062 .unwrap();
1063 assert_eq!(result.tx_results.len(), 2);
1064 assert_eq!(result.package_msg, "success");
1065 }
1066
1067 #[tokio::test]
1074 async fn submit_package_1p1c() {
1075 init_tracing();
1076
1077 let (bitcoind, client) = get_bitcoind_and_client();
1078
1079 let server_version = bitcoind.client.server_version().unwrap();
1081 assert!(server_version > 28);
1082
1083 let destination = client.get_new_address().await.unwrap();
1084
1085 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
1086 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
1087 let coinbase_tx = last_block.coinbase().unwrap();
1088
1089 let parent_raw_tx = CreateRawTransaction {
1090 inputs: vec![CreateRawTransactionInput {
1091 txid: coinbase_tx.compute_txid().to_string(),
1092 vout: 0,
1093 }],
1094 outputs: vec![CreateRawTransactionOutput::AddressAmount {
1095 address: destination.to_string(),
1096 amount: COINBASE_AMOUNT.to_btc(),
1097 }],
1098 };
1099 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
1100 parent.version = transaction::Version(3);
1101 assert_eq!(parent.version, transaction::Version(3));
1102 trace!(?parent, "parent:");
1103 let signed_parent: Transaction = consensus::encode::deserialize_hex(
1104 client
1105 .sign_raw_transaction_with_wallet(&parent, None)
1106 .await
1107 .unwrap()
1108 .hex
1109 .as_str(),
1110 )
1111 .unwrap();
1112 assert_eq!(signed_parent.version, transaction::Version(3));
1113
1114 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
1116 assert!(parent_broadcasted.is_err());
1117
1118 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
1120 let child_raw_tx = CreateRawTransaction {
1121 inputs: vec![CreateRawTransactionInput {
1122 txid: signed_parent.compute_txid().to_string(),
1123 vout: 0,
1124 }],
1125 outputs: vec![CreateRawTransactionOutput::AddressAmount {
1126 address: destination.to_string(),
1127 amount: amount_minus_fees.to_btc(),
1128 }],
1129 };
1130 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
1131 child.version = transaction::Version(3);
1132 assert_eq!(child.version, transaction::Version(3));
1133 trace!(?child, "child:");
1134 let prev_outputs = vec![PreviousTransactionOutput {
1135 txid: parent.compute_txid(),
1136 vout: 0,
1137 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
1138 redeem_script: None,
1139 witness_script: None,
1140 amount: Some(COINBASE_AMOUNT.to_btc()),
1141 }];
1142 let signed_child: Transaction = consensus::encode::deserialize_hex(
1143 client
1144 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
1145 .await
1146 .unwrap()
1147 .hex
1148 .as_str(),
1149 )
1150 .unwrap();
1151 assert_eq!(signed_child.version, transaction::Version(3));
1152
1153 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1155 assert!(child_broadcasted.is_err());
1156
1157 let result = client
1159 .submit_package(&[signed_parent, signed_child])
1160 .await
1161 .unwrap();
1162 assert_eq!(result.tx_results.len(), 2);
1163 assert_eq!(result.package_msg, "success");
1164 }
1165
1166 #[tokio::test]
1167 async fn test_invalid_credentials_return_401_error() {
1168 init_tracing();
1169
1170 let (bitcoind, _) = get_bitcoind_and_client();
1171 let url = bitcoind.rpc_url();
1172
1173 let invalid_client = Client::new(
1175 url,
1176 "wrong_user".to_string(),
1177 "wrong_password".to_string(),
1178 None,
1179 None,
1180 )
1181 .unwrap();
1182
1183 let result = invalid_client.get_blockchain_info().await;
1185
1186 assert!(result.is_err());
1188 let error = result.unwrap_err();
1189
1190 match error {
1191 ClientError::Status(status_code, message) => {
1192 assert_eq!(status_code, 401);
1193 assert!(message.contains("Unauthorized"));
1194 }
1195 _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1196 }
1197 }
1198
1199 #[tokio::test]
1200 async fn psbt_bump_fee() {
1201 init_tracing();
1202
1203 let (bitcoind, client) = get_bitcoind_and_client();
1204
1205 mine_blocks(&bitcoind, 101, None).unwrap();
1207
1208 let destination = client.get_new_address().await.unwrap();
1210 let amount = Amount::from_btc(0.001).unwrap(); let txid = bitcoind
1214 .client
1215 .send_to_address_rbf(&destination, amount)
1216 .unwrap()
1217 .txid()
1218 .unwrap();
1219
1220 let mempool = client.get_raw_mempool().await.unwrap();
1222 assert!(
1223 mempool.contains(&txid),
1224 "Transaction should be in mempool for RBF"
1225 );
1226
1227 let signed_tx = client
1229 .psbt_bump_fee(&txid, None)
1230 .await
1231 .unwrap()
1232 .psbt
1233 .extract_tx()
1234 .unwrap();
1235 let signed_txid = signed_tx.compute_txid();
1236 let got = client
1237 .test_mempool_accept(&signed_tx)
1238 .await
1239 .unwrap()
1240 .first()
1241 .unwrap()
1242 .txid;
1243 assert_eq!(
1244 got, signed_txid,
1245 "Bumped transaction should be accepted in mempool"
1246 );
1247
1248 let options = PsbtBumpFeeOptions {
1250 fee_rate: Some(FeeRate::from_sat_per_kwu(20)), ..Default::default()
1252 };
1253 trace!(?options, "Calling psbt_bump_fee");
1254 let signed_tx = client
1255 .psbt_bump_fee(&txid, Some(options))
1256 .await
1257 .unwrap()
1258 .psbt
1259 .extract_tx()
1260 .unwrap();
1261 let signed_txid = signed_tx.compute_txid();
1262 let got = client
1263 .test_mempool_accept(&signed_tx)
1264 .await
1265 .unwrap()
1266 .first()
1267 .unwrap()
1268 .txid;
1269 assert_eq!(
1270 got, signed_txid,
1271 "Bumped transaction should be accepted in mempool"
1272 );
1273 }
1274}