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 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_mempool_info(&self) -> ClientResult<GetMempoolInfo> {
318 self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await
319 }
320
321 async fn get_raw_transaction_verbosity_zero(
322 &self,
323 txid: &Txid,
324 ) -> ClientResult<GetRawTransactionVerbosityZero> {
325 self.call::<GetRawTransactionVerbosityZero>(
326 "getrawtransaction",
327 &[to_value(txid.to_string())?, to_value(0)?],
328 )
329 .await
330 }
331
332 async fn get_raw_transaction_verbosity_one(
333 &self,
334 txid: &Txid,
335 ) -> ClientResult<GetRawTransactionVerbosityOne> {
336 self.call::<GetRawTransactionVerbosityOne>(
337 "getrawtransaction",
338 &[to_value(txid.to_string())?, to_value(1)?],
339 )
340 .await
341 }
342
343 async fn get_tx_out(
344 &self,
345 txid: &Txid,
346 vout: u32,
347 include_mempool: bool,
348 ) -> ClientResult<GetTxOut> {
349 self.call::<GetTxOut>(
350 "gettxout",
351 &[
352 to_value(txid.to_string())?,
353 to_value(vout)?,
354 to_value(include_mempool)?,
355 ],
356 )
357 .await
358 }
359
360 async fn network(&self) -> ClientResult<Network> {
361 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
362 .await?
363 .chain
364 .parse::<Network>()
365 .map_err(|e| ClientError::Parse(e.to_string()))
366 }
367}
368
369impl Broadcaster for Client {
370 async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
371 let txstr = serialize_hex(tx);
372 trace!(txstr = %txstr, "Sending raw transaction");
373 match self
374 .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
375 .await
376 {
377 Ok(txid) => {
378 trace!(?txid, "Transaction sent");
379 Ok(txid)
380 }
381 Err(ClientError::Server(i, s)) => match i {
382 -27 => Ok(tx.compute_txid()), _ => Err(ClientError::Server(i, s)),
385 },
386 Err(e) => Err(ClientError::Other(e.to_string())),
387 }
388 }
389
390 async fn test_mempool_accept(&self, tx: &Transaction) -> ClientResult<Vec<TestMempoolAccept>> {
391 let txstr = serialize_hex(tx);
392 trace!(%txstr, "Testing mempool accept");
393 self.call::<Vec<TestMempoolAccept>>("testmempoolaccept", &[to_value([txstr])?])
394 .await
395 }
396
397 async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<SubmitPackage> {
398 let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
399 self.call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
400 .await
401 }
402}
403
404impl Wallet for Client {
405 async fn get_new_address(&self) -> ClientResult<Address> {
406 let address_unchecked = self
407 .call::<GetNewAddress>("getnewaddress", &[])
408 .await?
409 .0
410 .parse::<Address<_>>()
411 .map_err(|e| ClientError::Parse(e.to_string()))?
412 .assume_checked();
413 Ok(address_unchecked)
414 }
415 async fn get_transaction(&self, txid: &Txid) -> ClientResult<GetTransaction> {
416 self.call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
417 .await
418 }
419
420 async fn get_utxos(&self) -> ClientResult<Vec<ListUnspent>> {
421 let resp = self.call::<Vec<ListUnspent>>("listunspent", &[]).await?;
422 trace!(?resp, "Got UTXOs");
423 Ok(resp)
424 }
425
426 async fn list_transactions(&self, count: Option<usize>) -> ClientResult<Vec<ListTransactions>> {
427 self.call::<Vec<ListTransactions>>("listtransactions", &[to_value(count)?])
428 .await
429 }
430
431 async fn list_wallets(&self) -> ClientResult<Vec<String>> {
432 self.call::<Vec<String>>("listwallets", &[]).await
433 }
434
435 async fn create_raw_transaction(
436 &self,
437 raw_tx: CreateRawTransaction,
438 ) -> ClientResult<Transaction> {
439 let raw_tx = self
440 .call::<String>(
441 "createrawtransaction",
442 &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
443 )
444 .await?;
445 trace!(%raw_tx, "Created raw transaction");
446 consensus::encode::deserialize_hex(&raw_tx)
447 .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
448 }
449}
450
451impl Signer for Client {
452 async fn sign_raw_transaction_with_wallet(
453 &self,
454 tx: &Transaction,
455 prev_outputs: Option<Vec<PreviousTransactionOutput>>,
456 ) -> ClientResult<SignRawTransactionWithWallet> {
457 let tx_hex = serialize_hex(tx);
458 trace!(tx_hex = %tx_hex, "Signing transaction");
459 trace!(?prev_outputs, "Signing transaction with previous outputs");
460 self.call::<SignRawTransactionWithWallet>(
461 "signrawtransactionwithwallet",
462 &[to_value(tx_hex)?, to_value(prev_outputs)?],
463 )
464 .await
465 }
466
467 async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
468 if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
470 return Ok(None);
471 }
472
473 let descriptors = self
474 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
476 .descriptors;
477 if descriptors.is_empty() {
478 return Err(ClientError::Other("No descriptors found".to_string()));
479 }
480
481 let descriptor = descriptors
483 .iter()
484 .find(|d| d.desc.contains("tr("))
485 .map(|d| d.desc.clone())
486 .ok_or(ClientError::Xpriv)?;
487
488 let xpriv_str = descriptor
490 .split("tr(")
491 .nth(1)
492 .ok_or(ClientError::Xpriv)?
493 .split("/")
494 .next()
495 .ok_or(ClientError::Xpriv)?;
496
497 let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
498 Ok(Some(xpriv))
499 }
500
501 async fn import_descriptors(
502 &self,
503 descriptors: Vec<ImportDescriptor>,
504 wallet_name: String,
505 ) -> ClientResult<Vec<ImportDescriptorResult>> {
506 let wallet_args = CreateWallet {
507 wallet_name,
508 load_on_startup: Some(true),
509 };
510
511 let _wallet_create = self
514 .call::<Value>("createwallet", &[to_value(wallet_args.clone())?])
515 .await;
516 let _wallet_load = self
518 .call::<Value>("loadwallet", &[to_value(wallet_args)?])
519 .await;
520
521 let result = self
522 .call::<Vec<ImportDescriptorResult>>("importdescriptors", &[to_value(descriptors)?])
523 .await?;
524 Ok(result)
525 }
526}
527
528#[cfg(test)]
529mod test {
530
531 use std::sync::Once;
532
533 use bitcoin::{
534 consensus::{self, encode::deserialize_hex},
535 hashes::Hash,
536 transaction, Amount, NetworkKind,
537 };
538 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
539
540 use super::*;
541 use crate::{
542 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
543 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
544 };
545
546 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
548
549 fn init_tracing() {
551 static INIT: Once = Once::new();
552
553 INIT.call_once(|| {
554 tracing_subscriber::registry()
555 .with(fmt::layer())
556 .with(EnvFilter::from_default_env())
557 .try_init()
558 .ok();
559 });
560 }
561
562 #[tokio::test()]
563 async fn client_works() {
564 init_tracing();
565
566 let (bitcoind, client) = get_bitcoind_and_client();
567
568 let got = client.network().await.unwrap();
570 let expected = Network::Regtest;
571
572 assert_eq!(expected, got);
573 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
575 assert_eq!(get_blockchain_info.blocks, 0);
576
577 let _ = client
579 .get_current_timestamp()
580 .await
581 .expect("must be able to get current timestamp");
582
583 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
584
585 let expected = blocks.last().unwrap();
587 let got = client.get_block(expected).await.unwrap().block_hash();
588 assert_eq!(*expected, got);
589
590 let target_height = blocks.len() as u64;
592 let expected = blocks.last().unwrap();
593 let got = client
594 .get_block_at(target_height)
595 .await
596 .unwrap()
597 .block_hash();
598 assert_eq!(*expected, got);
599
600 let expected = blocks.len() as u64;
602 let got = client.get_block_count().await.unwrap();
603 assert_eq!(expected, got);
604
605 let target_height = blocks.len() as u64;
607 let expected = blocks.last().unwrap();
608 let got = client.get_block_hash(target_height).await.unwrap();
609 assert_eq!(*expected, got);
610
611 let address = client.get_new_address().await.unwrap();
613 let txid = client
614 .call::<String>(
615 "sendtoaddress",
616 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
617 )
618 .await
619 .unwrap()
620 .parse::<Txid>()
621 .unwrap();
622
623 let tx = client.get_transaction(&txid).await.unwrap().hex;
625 let got = client.send_raw_transaction(&tx).await.unwrap();
626 let expected = txid; assert_eq!(expected, got);
628
629 let got = client
631 .get_raw_transaction_verbosity_zero(&txid)
632 .await
633 .unwrap()
634 .0;
635 let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
636 assert_eq!(expected, got);
637
638 let got = client
640 .get_raw_transaction_verbosity_one(&txid)
641 .await
642 .unwrap()
643 .transaction
644 .compute_txid();
645 assert_eq!(expected, got);
646
647 let got = client.get_raw_mempool().await.unwrap();
649 let expected = vec![txid];
650 assert_eq!(expected, got);
651
652 let got = client.get_mempool_info().await.unwrap();
654 assert!(got.loaded);
655 assert_eq!(got.size, 1);
656 assert_eq!(got.unbroadcastcount, 1);
657
658 let got = client.estimate_smart_fee(1).await.unwrap();
660 let expected = 1; assert_eq!(expected, got);
662
663 let got = client
665 .sign_raw_transaction_with_wallet(&tx, None)
666 .await
667 .unwrap();
668 assert!(got.complete);
669 assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
670
671 let txids = client
673 .test_mempool_accept(&tx)
674 .await
675 .expect("must be able to test mempool accept");
676 let got = txids.first().expect("there must be at least one txid");
677 assert_eq!(
678 got.txid,
679 tx.compute_txid(),
680 "txids must match in the mempool"
681 );
682
683 let got = client.send_raw_transaction(&tx).await.unwrap();
685 assert!(got.as_byte_array().len() == 32);
686
687 let got = client.list_transactions(None).await.unwrap();
689 assert_eq!(got.len(), 10);
690
691 mine_blocks(&bitcoind, 1, None).unwrap();
694 let got = client.get_utxos().await.unwrap();
695 assert_eq!(got.len(), 3);
696
697 let got = client.get_xpriv().await.unwrap().unwrap().network;
699 let expected = NetworkKind::Test;
700 assert_eq!(expected, got);
701
702 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
705 let timestamp = "now".to_owned();
706 let list_descriptors = vec![ImportDescriptor {
707 desc: descriptor_string,
708 active: Some(true),
709 timestamp,
710 }];
711 let got = client
712 .import_descriptors(list_descriptors, "strata".to_owned())
713 .await
714 .unwrap();
715 let expected = vec![ImportDescriptorResult { success: true }];
716 assert_eq!(expected, got);
717 }
718
719 #[tokio::test()]
720 async fn get_tx_out() {
721 init_tracing();
722
723 let (bitcoind, client) = get_bitcoind_and_client();
724
725 let got = client.network().await.unwrap();
727 let expected = Network::Regtest;
728 assert_eq!(expected, got);
729
730 let address = bitcoind.client.new_address().unwrap();
731 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
732 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
733 let coinbase_tx = last_block.coinbase().unwrap();
734
735 let got = client
737 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
738 .await
739 .unwrap();
740 assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
741
742 let new_address = bitcoind.client.new_address().unwrap();
744 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
746 .client
747 .send_to_address(&new_address, send_amount)
748 .unwrap()
749 .txid()
750 .unwrap();
751 let result = client
752 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
753 .await;
754 trace!(?result, "gettxout result");
755 assert!(result.is_err());
756 }
757
758 #[tokio::test()]
765 async fn submit_package() {
766 init_tracing();
767
768 let (bitcoind, client) = get_bitcoind_and_client();
769
770 let got = client.network().await.unwrap();
772 let expected = Network::Regtest;
773 assert_eq!(expected, got);
774
775 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
776 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
777 let coinbase_tx = last_block.coinbase().unwrap();
778
779 let destination = client.get_new_address().await.unwrap();
780 let change_address = client.get_new_address().await.unwrap();
781 let amount = Amount::from_btc(1.0).unwrap();
782 let fees = Amount::from_btc(0.0001).unwrap();
783 let change_amount = COINBASE_AMOUNT - amount - fees;
784 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
785
786 let send_back_address = client.get_new_address().await.unwrap();
787 let parent_raw_tx = CreateRawTransaction {
788 inputs: vec![CreateRawTransactionInput {
789 txid: coinbase_tx.compute_txid().to_string(),
790 vout: 0,
791 }],
792 outputs: vec![
793 CreateRawTransactionOutput::AddressAmount {
795 address: destination.to_string(),
796 amount: amount.to_btc(),
797 },
798 CreateRawTransactionOutput::AddressAmount {
800 address: change_address.to_string(),
801 amount: change_amount.to_btc(),
802 },
803 ],
804 };
805 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
806 let signed_parent: Transaction = consensus::encode::deserialize_hex(
807 client
808 .sign_raw_transaction_with_wallet(&parent, None)
809 .await
810 .unwrap()
811 .hex
812 .as_str(),
813 )
814 .unwrap();
815
816 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
818
819 let child_raw_tx = CreateRawTransaction {
820 inputs: vec![CreateRawTransactionInput {
821 txid: parent_submitted.to_string(),
822 vout: 0,
823 }],
824 outputs: vec![
825 CreateRawTransactionOutput::AddressAmount {
827 address: send_back_address.to_string(),
828 amount: amount_minus_fees.to_btc(),
829 },
830 ],
831 };
832 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
833 let signed_child: Transaction = consensus::encode::deserialize_hex(
834 client
835 .sign_raw_transaction_with_wallet(&child, None)
836 .await
837 .unwrap()
838 .hex
839 .as_str(),
840 )
841 .unwrap();
842
843 let result = client
845 .submit_package(&[signed_parent, signed_child])
846 .await
847 .unwrap();
848 assert_eq!(result.tx_results.len(), 2);
849 assert_eq!(result.package_msg, "success");
850 }
851
852 #[tokio::test]
859 async fn submit_package_1p1c() {
860 init_tracing();
861
862 let (bitcoind, client) = get_bitcoind_and_client();
863
864 let server_version = bitcoind.client.server_version().unwrap();
866 assert!(server_version > 28);
867
868 let destination = client.get_new_address().await.unwrap();
869
870 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
871 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
872 let coinbase_tx = last_block.coinbase().unwrap();
873
874 let parent_raw_tx = CreateRawTransaction {
875 inputs: vec![CreateRawTransactionInput {
876 txid: coinbase_tx.compute_txid().to_string(),
877 vout: 0,
878 }],
879 outputs: vec![CreateRawTransactionOutput::AddressAmount {
880 address: destination.to_string(),
881 amount: COINBASE_AMOUNT.to_btc(),
882 }],
883 };
884 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
885 parent.version = transaction::Version(3);
886 assert_eq!(parent.version, transaction::Version(3));
887 trace!(?parent, "parent:");
888 let signed_parent: Transaction = consensus::encode::deserialize_hex(
889 client
890 .sign_raw_transaction_with_wallet(&parent, None)
891 .await
892 .unwrap()
893 .hex
894 .as_str(),
895 )
896 .unwrap();
897 assert_eq!(signed_parent.version, transaction::Version(3));
898
899 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
901 assert!(parent_broadcasted.is_err());
902
903 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
905 let child_raw_tx = CreateRawTransaction {
906 inputs: vec![CreateRawTransactionInput {
907 txid: signed_parent.compute_txid().to_string(),
908 vout: 0,
909 }],
910 outputs: vec![CreateRawTransactionOutput::AddressAmount {
911 address: destination.to_string(),
912 amount: amount_minus_fees.to_btc(),
913 }],
914 };
915 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
916 child.version = transaction::Version(3);
917 assert_eq!(child.version, transaction::Version(3));
918 trace!(?child, "child:");
919 let prev_outputs = vec![PreviousTransactionOutput {
920 txid: parent.compute_txid(),
921 vout: 0,
922 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
923 redeem_script: None,
924 witness_script: None,
925 amount: Some(COINBASE_AMOUNT.to_btc()),
926 }];
927 let signed_child: Transaction = consensus::encode::deserialize_hex(
928 client
929 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
930 .await
931 .unwrap()
932 .hex
933 .as_str(),
934 )
935 .unwrap();
936 assert_eq!(signed_child.version, transaction::Version(3));
937
938 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
940 assert!(child_broadcasted.is_err());
941
942 let result = client
944 .submit_package(&[signed_parent, signed_child])
945 .await
946 .unwrap();
947 assert_eq!(result.tx_results.len(), 2);
948 assert_eq!(result.package_msg, "success");
949 }
950}