1use std::{
2 env::var,
3 fmt,
4 sync::{
5 Arc,
6 atomic::{AtomicUsize, Ordering},
7 },
8 time::Duration,
9};
10
11use base64::{Engine, engine::general_purpose};
12use bitcoin::{
13 Address, Block, BlockHash, Network, Transaction, Txid,
14 bip32::Xpriv,
15 block::Header,
16 consensus::{self, encode::serialize_hex},
17};
18use reqwest::{
19 Client as ReqwestClient,
20 header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap},
21};
22use serde::{Deserialize, Serialize, de};
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 Amount, NetworkKind,
531 consensus::{self, encode::deserialize_hex},
532 hashes::Hash,
533 transaction,
534 };
535 use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
536
537 use super::*;
538 use crate::{
539 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
540 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
541 };
542
543 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
545
546 fn init_tracing() {
548 static INIT: Once = Once::new();
549
550 INIT.call_once(|| {
551 tracing_subscriber::registry()
552 .with(fmt::layer())
553 .with(EnvFilter::from_default_env())
554 .try_init()
555 .ok();
556 });
557 }
558
559 #[tokio::test()]
560 async fn client_works() {
561 init_tracing();
562
563 let (bitcoind, client) = get_bitcoind_and_client();
564
565 let got = client.network().await.unwrap();
567 let expected = Network::Regtest;
568
569 assert_eq!(expected, got);
570 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
572 assert_eq!(get_blockchain_info.blocks, 0);
573
574 let _ = client
576 .get_current_timestamp()
577 .await
578 .expect("must be able to get current timestamp");
579
580 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
581
582 let expected = blocks.last().unwrap();
584 let got = client.get_block(expected).await.unwrap().block_hash();
585 assert_eq!(*expected, got);
586
587 let target_height = blocks.len() as u64;
589 let expected = blocks.last().unwrap();
590 let got = client
591 .get_block_at(target_height)
592 .await
593 .unwrap()
594 .block_hash();
595 assert_eq!(*expected, got);
596
597 let expected = blocks.len() as u64;
599 let got = client.get_block_count().await.unwrap();
600 assert_eq!(expected, got);
601
602 let target_height = blocks.len() as u64;
604 let expected = blocks.last().unwrap();
605 let got = client.get_block_hash(target_height).await.unwrap();
606 assert_eq!(*expected, got);
607
608 let address = client.get_new_address().await.unwrap();
610 let txid = client
611 .call::<String>(
612 "sendtoaddress",
613 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
614 )
615 .await
616 .unwrap()
617 .parse::<Txid>()
618 .unwrap();
619
620 let tx = client.get_transaction(&txid).await.unwrap().hex;
622 let got = client.send_raw_transaction(&tx).await.unwrap();
623 let expected = txid; assert_eq!(expected, got);
625
626 let got = client
628 .get_raw_transaction_verbosity_zero(&txid)
629 .await
630 .unwrap()
631 .0;
632 let got = deserialize_hex::<Transaction>(&got).unwrap().compute_txid();
633 assert_eq!(expected, got);
634
635 let got = client
637 .get_raw_transaction_verbosity_one(&txid)
638 .await
639 .unwrap()
640 .transaction
641 .compute_txid();
642 assert_eq!(expected, got);
643
644 let got = client.get_raw_mempool().await.unwrap();
646 let expected = vec![txid];
647 assert_eq!(expected, got);
648
649 let got = client.estimate_smart_fee(1).await.unwrap();
651 let expected = 1; assert_eq!(expected, got);
653
654 let got = client
656 .sign_raw_transaction_with_wallet(&tx, None)
657 .await
658 .unwrap();
659 assert!(got.complete);
660 assert!(consensus::encode::deserialize_hex::<Transaction>(&got.hex).is_ok());
661
662 let txids = client
664 .test_mempool_accept(&tx)
665 .await
666 .expect("must be able to test mempool accept");
667 let got = txids.first().expect("there must be at least one txid");
668 assert_eq!(
669 got.txid,
670 tx.compute_txid(),
671 "txids must match in the mempool"
672 );
673
674 let got = client.send_raw_transaction(&tx).await.unwrap();
676 assert!(got.as_byte_array().len() == 32);
677
678 let got = client.list_transactions(None).await.unwrap();
680 assert_eq!(got.len(), 10);
681
682 mine_blocks(&bitcoind, 1, None).unwrap();
685 let got = client.get_utxos().await.unwrap();
686 assert_eq!(got.len(), 3);
687
688 let got = client.get_xpriv().await.unwrap().unwrap().network;
690 let expected = NetworkKind::Test;
691 assert_eq!(expected, got);
692
693 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
696 let timestamp = "now".to_owned();
697 let list_descriptors = vec![ImportDescriptor {
698 desc: descriptor_string,
699 active: Some(true),
700 timestamp,
701 }];
702 let got = client
703 .import_descriptors(list_descriptors, "strata".to_owned())
704 .await
705 .unwrap();
706 let expected = vec![ImportDescriptorResult { success: true }];
707 assert_eq!(expected, got);
708 }
709
710 #[tokio::test()]
711 async fn get_tx_out() {
712 init_tracing();
713
714 let (bitcoind, client) = get_bitcoind_and_client();
715
716 let got = client.network().await.unwrap();
718 let expected = Network::Regtest;
719 assert_eq!(expected, got);
720
721 let address = bitcoind.client.new_address().unwrap();
722 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
723 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
724 let coinbase_tx = last_block.coinbase().unwrap();
725
726 let got = client
728 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
729 .await
730 .unwrap();
731 assert_eq!(got.value, COINBASE_AMOUNT.to_btc());
732
733 let new_address = bitcoind.client.new_address().unwrap();
735 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
737 .client
738 .send_to_address(&new_address, send_amount)
739 .unwrap()
740 .txid()
741 .unwrap();
742 let result = client
743 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
744 .await;
745 trace!(?result, "gettxout result");
746 assert!(result.is_err());
747 }
748
749 #[tokio::test()]
756 async fn submit_package() {
757 init_tracing();
758
759 let (bitcoind, client) = get_bitcoind_and_client();
760
761 let got = client.network().await.unwrap();
763 let expected = Network::Regtest;
764 assert_eq!(expected, got);
765
766 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
767 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
768 let coinbase_tx = last_block.coinbase().unwrap();
769
770 let destination = client.get_new_address().await.unwrap();
771 let change_address = client.get_new_address().await.unwrap();
772 let amount = Amount::from_btc(1.0).unwrap();
773 let fees = Amount::from_btc(0.0001).unwrap();
774 let change_amount = COINBASE_AMOUNT - amount - fees;
775 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
776
777 let send_back_address = client.get_new_address().await.unwrap();
778 let parent_raw_tx = CreateRawTransaction {
779 inputs: vec![CreateRawTransactionInput {
780 txid: coinbase_tx.compute_txid().to_string(),
781 vout: 0,
782 }],
783 outputs: vec![
784 CreateRawTransactionOutput::AddressAmount {
786 address: destination.to_string(),
787 amount: amount.to_btc(),
788 },
789 CreateRawTransactionOutput::AddressAmount {
791 address: change_address.to_string(),
792 amount: change_amount.to_btc(),
793 },
794 ],
795 };
796 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
797 let signed_parent: Transaction = consensus::encode::deserialize_hex(
798 client
799 .sign_raw_transaction_with_wallet(&parent, None)
800 .await
801 .unwrap()
802 .hex
803 .as_str(),
804 )
805 .unwrap();
806
807 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
809
810 let child_raw_tx = CreateRawTransaction {
811 inputs: vec![CreateRawTransactionInput {
812 txid: parent_submitted.to_string(),
813 vout: 0,
814 }],
815 outputs: vec![
816 CreateRawTransactionOutput::AddressAmount {
818 address: send_back_address.to_string(),
819 amount: amount_minus_fees.to_btc(),
820 },
821 ],
822 };
823 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
824 let signed_child: Transaction = consensus::encode::deserialize_hex(
825 client
826 .sign_raw_transaction_with_wallet(&child, None)
827 .await
828 .unwrap()
829 .hex
830 .as_str(),
831 )
832 .unwrap();
833
834 let result = client
836 .submit_package(&[signed_parent, signed_child])
837 .await
838 .unwrap();
839 assert_eq!(result.tx_results.len(), 2);
840 assert_eq!(result.package_msg, "success");
841 }
842
843 #[tokio::test]
850 async fn submit_package_1p1c() {
851 init_tracing();
852
853 let (bitcoind, client) = get_bitcoind_and_client();
854
855 let server_version = bitcoind.client.server_version().unwrap();
857 assert!(server_version > 28);
858
859 let destination = client.get_new_address().await.unwrap();
860
861 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
862 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
863 let coinbase_tx = last_block.coinbase().unwrap();
864
865 let parent_raw_tx = CreateRawTransaction {
866 inputs: vec![CreateRawTransactionInput {
867 txid: coinbase_tx.compute_txid().to_string(),
868 vout: 0,
869 }],
870 outputs: vec![CreateRawTransactionOutput::AddressAmount {
871 address: destination.to_string(),
872 amount: COINBASE_AMOUNT.to_btc(),
873 }],
874 };
875 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
876 parent.version = transaction::Version(3);
877 assert_eq!(parent.version, transaction::Version(3));
878 trace!(?parent, "parent:");
879 let signed_parent: Transaction = consensus::encode::deserialize_hex(
880 client
881 .sign_raw_transaction_with_wallet(&parent, None)
882 .await
883 .unwrap()
884 .hex
885 .as_str(),
886 )
887 .unwrap();
888 assert_eq!(signed_parent.version, transaction::Version(3));
889
890 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
892 assert!(parent_broadcasted.is_err());
893
894 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
896 let child_raw_tx = CreateRawTransaction {
897 inputs: vec![CreateRawTransactionInput {
898 txid: signed_parent.compute_txid().to_string(),
899 vout: 0,
900 }],
901 outputs: vec![CreateRawTransactionOutput::AddressAmount {
902 address: destination.to_string(),
903 amount: amount_minus_fees.to_btc(),
904 }],
905 };
906 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
907 child.version = transaction::Version(3);
908 assert_eq!(child.version, transaction::Version(3));
909 trace!(?child, "child:");
910 let prev_outputs = vec![PreviousTransactionOutput {
911 txid: parent.compute_txid(),
912 vout: 0,
913 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
914 redeem_script: None,
915 witness_script: None,
916 amount: Some(COINBASE_AMOUNT.to_btc()),
917 }];
918 let signed_child: Transaction = consensus::encode::deserialize_hex(
919 client
920 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
921 .await
922 .unwrap()
923 .hex
924 .as_str(),
925 )
926 .unwrap();
927 assert_eq!(signed_child.version, transaction::Version(3));
928
929 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
931 assert!(child_broadcasted.is_err());
932
933 let result = client
935 .submit_package(&[signed_parent, signed_child])
936 .await
937 .unwrap();
938 assert_eq!(result.tx_results.len(), 2);
939 assert_eq!(result.package_msg, "success");
940 }
941}