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, 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 raw_response = resp
169 .text()
170 .await
171 .map_err(|e| ClientError::Parse(e.to_string()))?;
172 trace!(%raw_response, "Raw response received");
173 let data: Response<T> = serde_json::from_str(&raw_response)
174 .map_err(|e| ClientError::Parse(e.to_string()))?;
175 if let Some(err) = data.error {
176 return Err(ClientError::Server(err.code, err.message));
177 }
178 return data
179 .result
180 .ok_or_else(|| ClientError::Other("Empty data received".to_string()));
181 }
182 Err(err) => {
183 warn!(err = %err, "Error calling bitcoin client");
184
185 if err.is_body() {
186 return Err(ClientError::Body(err.to_string()));
188 } else if err.is_status() {
189 let e = match err.status() {
191 Some(code) => ClientError::Status(code.to_string(), err.to_string()),
192 _ => ClientError::Other(err.to_string()),
193 };
194 return Err(e);
195 } else if err.is_decode() {
196 let e = ClientError::MalformedResponse(err.to_string());
198 warn!(%e, "decoding error, retrying...");
199 } else if err.is_connect() {
200 let e = ClientError::Connection(err.to_string());
202 warn!(%e, "connection error, retrying...");
203 } else if err.is_timeout() {
204 let e = ClientError::Timeout;
206 warn!(%e, "timeout error, retrying...");
207 } else if err.is_request() {
208 let e = ClientError::Request(err.to_string());
210 warn!(%e, "request error, retrying...");
211 } else if err.is_builder() {
212 return Err(ClientError::ReqBuilder(err.to_string()));
214 } else if err.is_redirect() {
215 return Err(ClientError::HttpRedirect(err.to_string()));
217 } else {
218 return Err(ClientError::Other("Unknown error".to_string()));
220 }
221 }
222 }
223 retries += 1;
224 if retries >= self.max_retries {
225 return Err(ClientError::MaxRetriesExceeded(self.max_retries));
226 }
227 sleep(Duration::from_millis(self.retry_interval)).await;
228 }
229 }
230}
231
232impl Reader for Client {
233 async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
234 let result = self
235 .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
236 .await?
237 .to_string();
238
239 let result_map: Value = result.parse::<Value>()?;
240
241 let btc_vkb = result_map
242 .get("feerate")
243 .unwrap_or(&"0.00001".parse::<Value>().unwrap())
244 .as_f64()
245 .unwrap();
246
247 Ok((btc_vkb * 100_000_000.0 / 1000.0) as u64)
249 }
250
251 async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
252 let get_block_header = self
253 .call::<GetBlockHeaderVerbosityZero>(
254 "getblockheader",
255 &[to_value(hash.to_string())?, to_value(false)?],
256 )
257 .await?;
258 let header = get_block_header
259 .header()
260 .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
261 Ok(header)
262 }
263
264 async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
265 let get_block = self
266 .call::<GetBlockVerbosityZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
267 .await?;
268 let block = get_block
269 .block()
270 .map_err(|err| ClientError::Other(format!("block decode: {err}")))?;
271 Ok(block)
272 }
273
274 async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
275 let block_verobose = self
276 .call::<GetBlockVerbosityOne>("getblock", &[to_value(hash.to_string())?])
277 .await?;
278
279 let block_height = block_verobose.height as u64;
280 Ok(block_height)
281 }
282
283 async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
284 let hash = self.get_block_hash(height).await?;
285 self.get_block_header(&hash).await
286 }
287
288 async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
289 let hash = self.get_block_hash(height).await?;
290 self.get_block(&hash).await
291 }
292
293 async fn get_block_count(&self) -> ClientResult<u64> {
294 self.call::<u64>("getblockcount", &[]).await
295 }
296
297 async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
298 self.call::<BlockHash>("getblockhash", &[to_value(height)?])
299 .await
300 }
301
302 async fn get_blockchain_info(&self) -> ClientResult<GetBlockchainInfo> {
303 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
304 .await
305 }
306
307 async fn get_current_timestamp(&self) -> ClientResult<u32> {
308 let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
309 let block = self.get_block(&best_block_hash).await?;
310 Ok(block.header.time)
311 }
312
313 async fn get_raw_mempool(&self) -> ClientResult<Vec<Txid>> {
314 self.call::<Vec<Txid>>("getrawmempool", &[]).await
315 }
316
317 async fn get_raw_transaction_verbosity_zero(
318 &self,
319 txid: &Txid,
320 ) -> ClientResult<GetRawTransactionVerbosityZero> {
321 self.call::<GetRawTransactionVerbosityZero>(
322 "getrawtransaction",
323 &[to_value(txid.to_string())?, to_value(0)?],
324 )
325 .await
326 }
327
328 async fn get_raw_transaction_verbosity_one(
329 &self,
330 txid: &Txid,
331 ) -> ClientResult<GetRawTransactionVerbosityOne> {
332 self.call::<GetRawTransactionVerbosityOne>(
333 "getrawtransaction",
334 &[to_value(txid.to_string())?, to_value(1)?],
335 )
336 .await
337 }
338
339 async fn get_tx_out(
340 &self,
341 txid: &Txid,
342 vout: u32,
343 include_mempool: bool,
344 ) -> ClientResult<GetTxOut> {
345 self.call::<GetTxOut>(
346 "gettxout",
347 &[
348 to_value(txid.to_string())?,
349 to_value(vout)?,
350 to_value(include_mempool)?,
351 ],
352 )
353 .await
354 }
355
356 async fn network(&self) -> ClientResult<Network> {
357 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
358 .await?
359 .chain
360 .parse::<Network>()
361 .map_err(|e| ClientError::Parse(e.to_string()))
362 }
363}
364
365impl Broadcaster for Client {
366 async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
367 let txstr = serialize_hex(tx);
368 trace!(txstr = %txstr, "Sending raw transaction");
369 match self
370 .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
371 .await
372 {
373 Ok(txid) => {
374 trace!(?txid, "Transaction sent");
375 Ok(txid)
376 }
377 Err(ClientError::Server(i, s)) => match i {
378 -27 => Ok(tx.compute_txid()), _ => Err(ClientError::Server(i, s)),
381 },
382 Err(e) => Err(ClientError::Other(e.to_string())),
383 }
384 }
385
386 async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
387 let txstr = serialize_hex(tx);
388 trace!(%txstr, "Testing mempool accept");
389 self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
390 .await
391 }
392
393 async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
394 let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
395 self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
396 .await
397 }
398}
399
400impl Wallet for Client {
401 async fn get_new_address(&self) -> ClientResult<Address> {
402 let address_unchecked = self
403 .call::<GetNewAddress>("getnewaddress", &[])
404 .await?
405 .0
406 .parse::<Address<_>>()
407 .map_err(|e| ClientError::Parse(e.to_string()))?
408 .assume_checked();
409 Ok(address_unchecked)
410 }
411 async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
412 self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
413 .await
414 }
415
416 async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
417 let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
418 trace!(?resp, "Got UTXOs");
419 Ok(resp)
420 }
421
422 async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
423 self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
424 .await
425 }
426
427 async fn list_wallets(&self) -> ClientResult<Vec<String>> {
428 self.call::<Vec<String>>("listwallets", &[]).await
429 }
430
431 async fn create_raw_transaction(
432 &self,
433 raw_tx: CreateRawTransaction,
434 ) -> ClientResult<Transaction> {
435 let raw_tx = self
436 .call::<String>(
437 "createrawtransaction",
438 &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
439 )
440 .await?;
441 trace!(%raw_tx, "Created raw transaction");
442 consensus::encode::deserialize_hex(&raw_tx)
443 .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
444 }
445}
446
447impl Signer for Client {
448 async fn sign_raw_transaction_with_wallet(
449 &self,
450 tx: &Transaction,
451 prev_outputs: Option<Vec<PreviousTransactionOutput>>,
452 ) -> ClientResult<SignRawTransactionWithWallet> {
453 let tx_hex = serialize_hex(tx);
454 trace!(tx_hex = %tx_hex, "Signing transaction");
455 trace!(?prev_outputs, "Signing transaction with previous outputs");
456 self.call::<SignRawTransactionWithWallet>(
457 "signrawtransactionwithwallet",
458 &[to_value(tx_hex)?, to_value(prev_outputs)?],
459 )
460 .await
461 }
462
463 async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
464 if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
466 return Ok(None);
467 }
468
469 let descriptors = self
470 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
472 .descriptors;
473 if descriptors.is_empty() {
474 return Err(ClientError::Other("No descriptors found".to_string()));
475 }
476
477 let descriptor = descriptors
479 .iter()
480 .find(|d| d.desc.contains("tr("))
481 .map(|d| d.desc.clone())
482 .ok_or(ClientError::Xpriv)?;
483
484 let xpriv_str = descriptor
486 .split("tr(")
487 .nth(1)
488 .ok_or(ClientError::Xpriv)?
489 .split("/")
490 .next()
491 .ok_or(ClientError::Xpriv)?;
492
493 let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
494 Ok(Some(xpriv))
495 }
496
497 async fn import_descriptors(
498 &self,
499 descriptors: Vec<ImportDescriptor>,
500 wallet_name: String,
501 ) -> ClientResult<Vec<ImportDescriptorResult>> {
502 let wallet_args = CreateWallet {
503 wallet_name,
504 load_on_startup: Some(true),
505 };
506
507 let _wallet_create = self
510 .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
511 .await;
512 let _wallet_load = self
514 .call::<Value>("loadwallet", &[to_value(wallet_args)?])
515 .await;
516
517 let result = self
518 .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
519 .await?;
520 Ok(result)
521 }
522}
523
524#[cfg(test)]
525mod test {
526
527 use std::sync::Once;
528
529 use bitcoin::{
530 consensus::{self, encode::deserialize_hex},
531 hashes::Hash,
532 transaction, Amount, NetworkKind,
533 };
534 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
535
536 use super::*;
537 use crate::{
538 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
539 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
540 };
541
542 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
544
545 fn init_tracing() {
547 static INIT: Once = Once::new();
548
549 INIT.call_once(|| {
550 tracing_subscriber::registry()
551 .with(fmt::layer())
552 .with(EnvFilter::from_default_env())
553 .try_init()
554 .ok();
555 });
556 }
557
558 #[tokio::test()]
559 async fn client_works() {
560 init_tracing();
561
562 let (bitcoind, client) = get_bitcoind_and_client();
563
564 let got = client.network().await.unwrap();
566 let expected = Network::Regtest;
567
568 assert_eq!(expected, got);
569 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
571 assert_eq!(get_blockchain_info.blocks, 0);
572
573 let _ = client
575 .get_current_timestamp()
576 .await
577 .expect("must be able to get current timestamp");
578
579 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
580
581 let expected = blocks.last().unwrap();
583 let got = client.get_block(expected).await.unwrap().block_hash();
584 assert_eq!(*expected, got);
585
586 let target_height = blocks.len() as u64;
588 let expected = blocks.last().unwrap();
589 let got = client
590 .get_block_at(target_height)
591 .await
592 .unwrap()
593 .block_hash();
594 assert_eq!(*expected, got);
595
596 let expected = blocks.len() as u64;
598 let got = client.get_block_count().await.unwrap();
599 assert_eq!(expected, got);
600
601 let target_height = blocks.len() as u64;
603 let expected = blocks.last().unwrap();
604 let got = client.get_block_hash(target_height).await.unwrap();
605 assert_eq!(*expected, got);
606
607 let address = client.get_new_address().await.unwrap();
609 let txid = client
610 .call::<String>(
611 "sendtoaddress",
612 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
613 )
614 .await
615 .unwrap()
616 .parse::<Txid>()
617 .unwrap();
618
619 let tx = client.get_transaction(&txid).await.unwrap().hex;
621 let got = client.send_raw_transaction(&tx).await.unwrap();
622 let expected = txid; assert_eq!(expected, got);
624
625 let got = client
627 .get_raw_transaction_verbosity_zero(&txid)
628 .await
629 .unwrap()
630 .0;
631 let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
632 assert_eq!(expected, got);
633
634 let got = client
636 .get_raw_transaction_verbosity_one(&txid)
637 .await
638 .unwrap()
639 .transaction
640 .compute_txid();
641 assert_eq!(expected, got);
642
643 let got = client.get_raw_mempool().await.unwrap();
645 let expected = vec![txid];
646 assert_eq!(expected, got);
647
648 let got = client.estimate_smart_fee(1).await.unwrap();
650 let expected = 1; assert_eq!(expected, got);
652
653 let got = client
655 .sign_raw_transaction_with_wallet(&tx, None)
656 .await
657 .unwrap();
658 assert!(got.complete);
659 assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
660
661 let txids = client
663 .test_mempool_accept(&tx)
664 .await
665 .expect("must be able to test mempool accept");
666 let got = txids.first().expect("there must be at least one txid");
667 assert_eq!(
668 got.txid,
669 tx.compute_txid(),
670 "txids must match in the mempool"
671 );
672
673 let got = client.send_raw_transaction(&tx).await.unwrap();
675 assert!(got.as_byte_array().len() == 32);
676
677 let got = client.list_transactions(None).await.unwrap();
679 assert_eq!(got.len(), 10);
680
681 mine_blocks(&bitcoind, 1, None).unwrap();
684 let got = client.get_utxos().await.unwrap();
685 assert_eq!(got.len(), 3);
686
687 let got = client.get_xpriv().await.unwrap().unwrap().network;
689 let expected = NetworkKind::Test;
690 assert_eq!(expected, got);
691
692 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
695 let timestamp = "now".to_owned();
696 let list_descriptors = vec![ImportDescriptor {
697 desc: descriptor_string,
698 active: Some(true),
699 timestamp,
700 }];
701 let got = client
702 .import_descriptors(list_descriptors, "strata".to_owned())
703 .await
704 .unwrap();
705 let expected = vec![ImportDescriptorResult { success: true }];
706 assert_eq!(expected, got);
707 }
708
709 #[tokio::test()]
710 async fn get_tx_out() {
711 init_tracing();
712
713 let (bitcoind, client) = get_bitcoind_and_client();
714
715 let got = client.network().await.unwrap();
717 let expected = Network::Regtest;
718 assert_eq!(expected, got);
719
720 let address = bitcoind.client.new_address().unwrap();
721 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
722 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
723 let coinbase_tx = last_block.coinbase().unwrap();
724
725 let got = client
727 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
728 .await
729 .unwrap();
730 assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
731
732 let new_address = bitcoind.client.new_address().unwrap();
734 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
736 .client
737 .send_to_address(&new_address, send_amount)
738 .unwrap()
739 .txid()
740 .unwrap();
741 let result = client
742 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
743 .await;
744 trace!(?result, "gettxout result");
745 assert!(result.is_err());
746 }
747
748 #[tokio::test()]
755 async fn submit_package() {
756 init_tracing();
757
758 let (bitcoind, client) = get_bitcoind_and_client();
759
760 let got = client.network().await.unwrap();
762 let expected = Network::Regtest;
763 assert_eq!(expected, got);
764
765 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
766 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
767 let coinbase_tx = last_block.coinbase().unwrap();
768
769 let destination = client.get_new_address().await.unwrap();
770 let change_address = client.get_new_address().await.unwrap();
771 let amount = Amount::from_btc(1.0).unwrap();
772 let fees = Amount::from_btc(0.0001).unwrap();
773 let change_amount = COINBASE_AMOUNT - amount - fees;
774 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
775
776 let send_back_address = client.get_new_address().await.unwrap();
777 let parent_raw_tx = CreateRawTransaction {
778 inputs: vec![CreateRawTransactionInput {
779 txid: coinbase_tx.compute_txid().to_string(),
780 vout: 0,
781 }],
782 outputs: vec![
783 CreateRawTransactionOutput::AddressAmount {
785 address: destination.to_string(),
786 amount: amount.to_btc(),
787 },
788 CreateRawTransactionOutput::AddressAmount {
790 address: change_address.to_string(),
791 amount: change_amount.to_btc(),
792 },
793 ],
794 };
795 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
796 let signed_parent: Transaction = consensus::encode::deserialize_hex(
797 client
798 .sign_raw_transaction_with_wallet(&parent, None)
799 .await
800 .unwrap()
801 .hex
802 .as_str(),
803 )
804 .unwrap();
805
806 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
808
809 let child_raw_tx = CreateRawTransaction {
810 inputs: vec![CreateRawTransactionInput {
811 txid: parent_submitted.to_string(),
812 vout: 0,
813 }],
814 outputs: vec![
815 CreateRawTransactionOutput::AddressAmount {
817 address: send_back_address.to_string(),
818 amount: amount_minus_fees.to_btc(),
819 },
820 ],
821 };
822 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
823 let signed_child: Transaction = consensus::encode::deserialize_hex(
824 client
825 .sign_raw_transaction_with_wallet(&child, None)
826 .await
827 .unwrap()
828 .hex
829 .as_str(),
830 )
831 .unwrap();
832
833 let result = client
835 .submit_package(&[signed_parent, signed_child])
836 .await
837 .unwrap();
838 assert_eq!(result.tx_results.len(), 2);
839 assert_eq!(result.package_msg, "success");
840 }
841
842 #[tokio::test]
849 async fn submit_package_1p1c() {
850 init_tracing();
851
852 let (bitcoind, client) = get_bitcoind_and_client();
853
854 let server_version = bitcoind.client.server_version().unwrap();
856 assert!(server_version > 28);
857
858 let destination = client.get_new_address().await.unwrap();
859
860 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
861 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
862 let coinbase_tx = last_block.coinbase().unwrap();
863
864 let parent_raw_tx = CreateRawTransaction {
865 inputs: vec![CreateRawTransactionInput {
866 txid: coinbase_tx.compute_txid().to_string(),
867 vout: 0,
868 }],
869 outputs: vec![CreateRawTransactionOutput::AddressAmount {
870 address: destination.to_string(),
871 amount: COINBASE_AMOUNT.to_btc(),
872 }],
873 };
874 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
875 parent.version = transaction::Version(3);
876 assert_eq!(parent.version, transaction::Version(3));
877 trace!(?parent, "parent:");
878 let signed_parent: Transaction = consensus::encode::deserialize_hex(
879 client
880 .sign_raw_transaction_with_wallet(&parent, None)
881 .await
882 .unwrap()
883 .hex
884 .as_str(),
885 )
886 .unwrap();
887 assert_eq!(signed_parent.version, transaction::Version(3));
888
889 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
891 assert!(parent_broadcasted.is_err());
892
893 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
895 let child_raw_tx = CreateRawTransaction {
896 inputs: vec![CreateRawTransactionInput {
897 txid: signed_parent.compute_txid().to_string(),
898 vout: 0,
899 }],
900 outputs: vec![CreateRawTransactionOutput::AddressAmount {
901 address: destination.to_string(),
902 amount: amount_minus_fees.to_btc(),
903 }],
904 };
905 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
906 child.version = transaction::Version(3);
907 assert_eq!(child.version, transaction::Version(3));
908 trace!(?child, "child:");
909 let prev_outputs = vec![PreviousTransactionOutput {
910 txid: parent.compute_txid(),
911 vout: 0,
912 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
913 redeem_script: None,
914 witness_script: None,
915 amount: Some(COINBASE_AMOUNT.to_btc()),
916 }];
917 let signed_child: Transaction = consensus::encode::deserialize_hex(
918 client
919 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
920 .await
921 .unwrap()
922 .hex
923 .as_str(),
924 )
925 .unwrap();
926 assert_eq!(signed_child.version, transaction::Version(3));
927
928 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
930 assert!(child_broadcasted.is_err());
931
932 let result = client
934 .submit_package(&[signed_parent, signed_child])
935 .await
936 .unwrap();
937 assert_eq!(result.tx_results.len(), 2);
938 assert_eq!(result.package_msg, "success");
939 }
940}