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