1use std::env::var;
4
5use bitcoin::{
6 bip32::Xpriv,
7 block::Header,
8 consensus::{self, encode::serialize_hex},
9 Address, Block, BlockHash, Network, Transaction, Txid,
10};
11use corepc_types::model;
12use corepc_types::v29::{
13 CreateWallet, GetAddressInfo, GetBlockHeaderVerbose, GetBlockVerboseOne, GetBlockVerboseZero,
14 GetBlockchainInfo, GetMempoolInfo, GetNewAddress, GetRawMempool, GetRawMempoolVerbose,
15 GetRawTransaction, GetRawTransactionVerbose, GetTransaction, GetTxOut, ImportDescriptors,
16 ListDescriptors, ListTransactions, ListUnspent, PsbtBumpFee, SignRawTransactionWithWallet,
17 SubmitPackage, TestMempoolAccept, WalletCreateFundedPsbt, WalletProcessPsbt,
18};
19use serde_json::value::{RawValue, Value};
20use tracing::*;
21
22use crate::{
23 client::Client,
24 error::ClientError,
25 to_value,
26 traits::{Broadcaster, Reader, Signer, Wallet},
27 types::{
28 CreateRawTransactionArguments, CreateRawTransactionInput, CreateRawTransactionOutput,
29 CreateWalletArguments, ImportDescriptorInput, ListUnspentQueryOptions,
30 PreviousTransactionOutput, PsbtBumpFeeOptions, SighashType, WalletCreateFundedPsbtOptions,
31 },
32 ClientResult,
33};
34
35impl Reader for Client {
36 async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
37 let result = self
38 .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
39 .await?
40 .to_string();
41
42 let result_map: Value = result.parse::<Value>()?;
43
44 let btc_vkb = result_map
45 .get("feerate")
46 .unwrap_or(&"0.00001".parse::<Value>().unwrap())
47 .as_f64()
48 .unwrap();
49
50 Ok((btc_vkb * 100_000_000.0 / 1000.0) as u64)
52 }
53
54 async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
55 let get_block_header = self
56 .call::<GetBlockHeaderVerbose>(
57 "getblockheader",
58 &[to_value(hash.to_string())?, to_value(false)?],
59 )
60 .await?;
61 let header = get_block_header
62 .block_header()
63 .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
64 Ok(header)
65 }
66
67 async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
68 let get_block = self
69 .call::<GetBlockVerboseZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
70 .await?;
71 let block = get_block
72 .into_model()
73 .map_err(|e| ClientError::Parse(e.to_string()))?
74 .0;
75 Ok(block)
76 }
77
78 async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
79 let block_verobose = self
80 .call::<GetBlockVerboseOne>("getblock", &[to_value(hash.to_string())?])
81 .await?;
82
83 let block_height = block_verobose.height as u64;
84 Ok(block_height)
85 }
86
87 async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
88 let hash = self.get_block_hash(height).await?;
89 self.get_block_header(&hash).await
90 }
91
92 async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
93 let hash = self.get_block_hash(height).await?;
94 self.get_block(&hash).await
95 }
96
97 async fn get_block_count(&self) -> ClientResult<u64> {
98 self.call::<u64>("getblockcount", &[]).await
99 }
100
101 async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
102 self.call::<BlockHash>("getblockhash", &[to_value(height)?])
103 .await
104 }
105
106 async fn get_blockchain_info(&self) -> ClientResult<model::GetBlockchainInfo> {
107 let res = self
108 .call::<GetBlockchainInfo>("getblockchaininfo", &[])
109 .await?;
110 res.into_model()
111 .map_err(|e| ClientError::Parse(e.to_string()))
112 }
113
114 async fn get_current_timestamp(&self) -> ClientResult<u32> {
115 let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
116 let block = self.get_block(&best_block_hash).await?;
117 Ok(block.header.time)
118 }
119
120 async fn get_raw_mempool(&self) -> ClientResult<model::GetRawMempool> {
121 let resp = self.call::<GetRawMempool>("getrawmempool", &[]).await?;
122 resp.into_model()
123 .map_err(|e| ClientError::Parse(e.to_string()))
124 }
125
126 async fn get_raw_mempool_verbose(&self) -> ClientResult<model::GetRawMempoolVerbose> {
127 let resp = self
128 .call::<GetRawMempoolVerbose>("getrawmempool", &[to_value(true)?])
129 .await?;
130
131 resp.into_model()
132 .map_err(|e| ClientError::Parse(e.to_string()))
133 }
134
135 async fn get_mempool_info(&self) -> ClientResult<model::GetMempoolInfo> {
136 let resp = self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await?;
137 resp.into_model()
138 .map_err(|e| ClientError::Parse(e.to_string()))
139 }
140
141 async fn get_raw_transaction_verbosity_zero(
142 &self,
143 txid: &Txid,
144 ) -> ClientResult<model::GetRawTransaction> {
145 let resp = self
146 .call::<GetRawTransaction>(
147 "getrawtransaction",
148 &[to_value(txid.to_string())?, to_value(0)?],
149 )
150 .await
151 .map_err(|e| ClientError::Parse(e.to_string()))?;
152 resp.into_model()
153 .map_err(|e| ClientError::Parse(e.to_string()))
154 }
155
156 async fn get_raw_transaction_verbosity_one(
157 &self,
158 txid: &Txid,
159 ) -> ClientResult<model::GetRawTransactionVerbose> {
160 let resp = self
161 .call::<GetRawTransactionVerbose>(
162 "getrawtransaction",
163 &[to_value(txid.to_string())?, to_value(1)?],
164 )
165 .await
166 .map_err(|e| ClientError::Parse(e.to_string()))?;
167 resp.into_model()
168 .map_err(|e| ClientError::Parse(e.to_string()))
169 }
170
171 async fn get_tx_out(
172 &self,
173 txid: &Txid,
174 vout: u32,
175 include_mempool: bool,
176 ) -> ClientResult<model::GetTxOut> {
177 let resp = self
178 .call::<GetTxOut>(
179 "gettxout",
180 &[
181 to_value(txid.to_string())?,
182 to_value(vout)?,
183 to_value(include_mempool)?,
184 ],
185 )
186 .await
187 .map_err(|e| ClientError::Parse(e.to_string()))?;
188 resp.into_model()
189 .map_err(|e| ClientError::Parse(e.to_string()))
190 }
191
192 async fn network(&self) -> ClientResult<Network> {
193 self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
194 .await?
195 .chain
196 .parse::<Network>()
197 .map_err(|e| ClientError::Parse(e.to_string()))
198 }
199}
200
201impl Broadcaster for Client {
202 async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
203 let txstr = serialize_hex(tx);
204 trace!(txstr = %txstr, "Sending raw transaction");
205 match self
206 .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
207 .await
208 {
209 Ok(txid) => {
210 trace!(?txid, "Transaction sent");
211 Ok(txid)
212 }
213 Err(ClientError::Server(i, s)) => match i {
214 -27 => Ok(tx.compute_txid()), _ => Err(ClientError::Server(i, s)),
217 },
218 Err(e) => Err(ClientError::Other(e.to_string())),
219 }
220 }
221
222 async fn test_mempool_accept(
223 &self,
224 tx: &Transaction,
225 ) -> ClientResult<model::TestMempoolAccept> {
226 let txstr = serialize_hex(tx);
227 trace!(%txstr, "Testing mempool accept");
228 let resp = self
229 .call::<TestMempoolAccept>("testmempoolaccept", &[to_value([txstr])?])
230 .await?;
231 resp.into_model()
232 .map_err(|e| ClientError::Parse(e.to_string()))
233 }
234
235 async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<model::SubmitPackage> {
236 let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
237 let resp = self
238 .call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
239 .await?;
240 trace!(?resp, "Got submit package response");
241
242 resp.into_model()
243 .map_err(|e| ClientError::Parse(e.to_string()))
244 }
245}
246
247impl Wallet for Client {
248 async fn get_new_address(&self) -> ClientResult<Address> {
249 let address_unchecked = self
250 .call::<GetNewAddress>("getnewaddress", &[])
251 .await?
252 .0
253 .parse::<Address<_>>()
254 .map_err(|e| ClientError::Parse(e.to_string()))?
255 .assume_checked();
256 Ok(address_unchecked)
257 }
258 async fn get_transaction(&self, txid: &Txid) -> ClientResult<model::GetTransaction> {
259 let resp = self
260 .call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
261 .await?;
262 resp.into_model()
263 .map_err(|e| ClientError::Parse(e.to_string()))
264 }
265
266 async fn list_transactions(
267 &self,
268 count: Option<usize>,
269 ) -> ClientResult<model::ListTransactions> {
270 let resp = self
271 .call::<ListTransactions>("listtransactions", &[to_value(count)?])
272 .await?;
273 resp.into_model()
274 .map_err(|e| ClientError::Parse(e.to_string()))
275 }
276
277 async fn list_wallets(&self) -> ClientResult<Vec<String>> {
278 self.call::<Vec<String>>("listwallets", &[]).await
279 }
280
281 async fn create_raw_transaction(
282 &self,
283 raw_tx: CreateRawTransactionArguments,
284 ) -> ClientResult<Transaction> {
285 let raw_tx = self
286 .call::<String>(
287 "createrawtransaction",
288 &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
289 )
290 .await?;
291 trace!(%raw_tx, "Created raw transaction");
292 consensus::encode::deserialize_hex(&raw_tx)
293 .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
294 }
295
296 async fn wallet_create_funded_psbt(
297 &self,
298 inputs: &[CreateRawTransactionInput],
299 outputs: &[CreateRawTransactionOutput],
300 locktime: Option<u32>,
301 options: Option<WalletCreateFundedPsbtOptions>,
302 bip32_derivs: Option<bool>,
303 ) -> ClientResult<model::WalletCreateFundedPsbt> {
304 let resp = self
305 .call::<WalletCreateFundedPsbt>(
306 "walletcreatefundedpsbt",
307 &[
308 to_value(inputs)?,
309 to_value(outputs)?,
310 to_value(locktime.unwrap_or(0))?,
311 to_value(options.unwrap_or_default())?,
312 to_value(bip32_derivs)?,
313 ],
314 )
315 .await?;
316 resp.into_model()
317 .map_err(|e| ClientError::Parse(e.to_string()))
318 }
319
320 async fn get_address_info(&self, address: &Address) -> ClientResult<model::GetAddressInfo> {
321 trace!(address = %address, "Getting address info");
322 let resp = self
323 .call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
324 .await?;
325 resp.into_model()
326 .map_err(|e| ClientError::Parse(e.to_string()))
327 }
328
329 async fn list_unspent(
330 &self,
331 min_conf: Option<u32>,
332 max_conf: Option<u32>,
333 addresses: Option<&[Address]>,
334 include_unsafe: Option<bool>,
335 query_options: Option<ListUnspentQueryOptions>,
336 ) -> ClientResult<model::ListUnspent> {
337 let addr_strings: Vec<String> = addresses
338 .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
339 .unwrap_or_default();
340
341 let mut params = vec![
342 to_value(min_conf.unwrap_or(1))?,
343 to_value(max_conf.unwrap_or(9_999_999))?,
344 to_value(addr_strings)?,
345 to_value(include_unsafe.unwrap_or(true))?,
346 ];
347
348 if let Some(query_options) = query_options {
349 params.push(to_value(query_options)?);
350 }
351
352 let resp = self.call::<ListUnspent>("listunspent", ¶ms).await?;
353 trace!(?resp, "Got UTXOs");
354
355 resp.into_model()
356 .map_err(|e| ClientError::Parse(e.to_string()))
357 }
358}
359
360impl Signer for Client {
361 async fn sign_raw_transaction_with_wallet(
362 &self,
363 tx: &Transaction,
364 prev_outputs: Option<Vec<PreviousTransactionOutput>>,
365 ) -> ClientResult<model::SignRawTransactionWithWallet> {
366 let tx_hex = serialize_hex(tx);
367 trace!(tx_hex = %tx_hex, "Signing transaction");
368 trace!(?prev_outputs, "Signing transaction with previous outputs");
369 let resp = self
370 .call::<SignRawTransactionWithWallet>(
371 "signrawtransactionwithwallet",
372 &[to_value(tx_hex)?, to_value(prev_outputs)?],
373 )
374 .await?;
375 resp.into_model()
376 .map_err(|e| ClientError::Parse(e.to_string()))
377 }
378
379 async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
380 if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
382 return Ok(None);
383 }
384
385 let descriptors = self
386 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
388 .descriptors;
389 if descriptors.is_empty() {
390 return Err(ClientError::Other("No descriptors found".to_string()));
391 }
392
393 let descriptor = descriptors
395 .iter()
396 .find(|d| d.descriptor.contains("tr("))
397 .map(|d| d.descriptor.clone())
398 .ok_or(ClientError::Xpriv)?;
399
400 let xpriv_str = descriptor
402 .split("tr(")
403 .nth(1)
404 .ok_or(ClientError::Xpriv)?
405 .split("/")
406 .next()
407 .ok_or(ClientError::Xpriv)?;
408
409 let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
410 Ok(Some(xpriv))
411 }
412
413 async fn import_descriptors(
414 &self,
415 descriptors: Vec<ImportDescriptorInput>,
416 wallet_name: String,
417 ) -> ClientResult<ImportDescriptors> {
418 let wallet_args = CreateWalletArguments {
419 name: wallet_name,
420 load_on_startup: Some(true),
421 };
422
423 let _wallet_create = self
426 .call::<CreateWallet>("createwallet", &[to_value(wallet_args.clone())?])
427 .await;
428 let _wallet_load = self
430 .call::<CreateWallet>("loadwallet", &[to_value(wallet_args)?])
431 .await;
432
433 let result = self
434 .call::<ImportDescriptors>("importdescriptors", &[to_value(descriptors)?])
435 .await?;
436 Ok(result)
437 }
438
439 async fn wallet_process_psbt(
440 &self,
441 psbt: &str,
442 sign: Option<bool>,
443 sighashtype: Option<SighashType>,
444 bip32_derivs: Option<bool>,
445 ) -> ClientResult<model::WalletProcessPsbt> {
446 let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
447
448 if let Some(sighashtype) = sighashtype {
449 params.push(to_value(sighashtype)?);
450 }
451
452 if let Some(bip32_derivs) = bip32_derivs {
453 params.push(to_value(bip32_derivs)?);
454 }
455
456 let resp = self
457 .call::<WalletProcessPsbt>("walletprocesspsbt", ¶ms)
458 .await?;
459 resp.into_model()
460 .map_err(|e| ClientError::Parse(e.to_string()))
461 }
462
463 async fn psbt_bump_fee(
464 &self,
465 txid: &Txid,
466 options: Option<PsbtBumpFeeOptions>,
467 ) -> ClientResult<model::PsbtBumpFee> {
468 let mut params = vec![to_value(txid.to_string())?];
469
470 if let Some(options) = options {
471 params.push(to_value(options)?);
472 }
473
474 let resp = self.call::<PsbtBumpFee>("psbtbumpfee", ¶ms).await?;
475 resp.into_model()
476 .map_err(|e| ClientError::Parse(e.to_string()))
477 }
478}
479
480#[cfg(test)]
481mod test {
482
483 use std::sync::Once;
484
485 use bitcoin::{hashes::Hash, transaction, Amount, FeeRate, NetworkKind};
486 use corepc_types::v29::ImportDescriptorsResult;
487 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
488
489 use super::*;
490 use crate::{
491 test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
492 types::{CreateRawTransactionInput, CreateRawTransactionOutput},
493 };
494
495 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
497
498 fn init_tracing() {
500 static INIT: Once = Once::new();
501
502 INIT.call_once(|| {
503 tracing_subscriber::registry()
504 .with(fmt::layer())
505 .with(EnvFilter::from_default_env())
506 .try_init()
507 .ok();
508 });
509 }
510
511 #[tokio::test()]
512 async fn client_works() {
513 init_tracing();
514
515 let (bitcoind, client) = get_bitcoind_and_client();
516
517 let got = client.network().await.unwrap();
519 let expected = Network::Regtest;
520
521 assert_eq!(expected, got);
522 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
524 assert_eq!(get_blockchain_info.blocks, 0);
525
526 let _ = client
528 .get_current_timestamp()
529 .await
530 .expect("must be able to get current timestamp");
531
532 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
533
534 let expected = blocks.last().unwrap();
536 let got = client.get_block(expected).await.unwrap().block_hash();
537 assert_eq!(*expected, got);
538
539 let target_height = blocks.len() as u64;
541 let expected = blocks.last().unwrap();
542 let got = client
543 .get_block_at(target_height)
544 .await
545 .unwrap()
546 .block_hash();
547 assert_eq!(*expected, got);
548
549 let expected = blocks.len() as u64;
551 let got = client.get_block_count().await.unwrap();
552 assert_eq!(expected, got);
553
554 let target_height = blocks.len() as u64;
556 let expected = blocks.last().unwrap();
557 let got = client.get_block_hash(target_height).await.unwrap();
558 assert_eq!(*expected, got);
559
560 let address = client.get_new_address().await.unwrap();
562 let txid = client
563 .call::<String>(
564 "sendtoaddress",
565 &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
566 )
567 .await
568 .unwrap()
569 .parse::<Txid>()
570 .unwrap();
571
572 let tx = client.get_transaction(&txid).await.unwrap().tx;
574 let got = client.send_raw_transaction(&tx).await.unwrap();
575 let expected = txid; assert_eq!(expected, got);
577
578 let got = client
580 .get_raw_transaction_verbosity_zero(&txid)
581 .await
582 .unwrap()
583 .0
584 .compute_txid();
585 assert_eq!(expected, got);
586
587 let got = client
589 .get_raw_transaction_verbosity_one(&txid)
590 .await
591 .unwrap()
592 .transaction
593 .compute_txid();
594 assert_eq!(expected, got);
595
596 let got = client.get_raw_mempool().await.unwrap();
598 let expected = vec![txid];
599 assert_eq!(expected, got.0);
600
601 let got = client.get_raw_mempool_verbose().await.unwrap();
603 assert_eq!(got.0.len(), 1);
604 assert_eq!(got.0.get(&txid).unwrap().height, 101);
605
606 let got = client.get_mempool_info().await.unwrap();
608 assert!(got.loaded.unwrap_or(false));
609 assert_eq!(got.size, 1);
610 assert_eq!(got.unbroadcast_count, Some(1));
611
612 let got = client.estimate_smart_fee(1).await.unwrap();
614 let expected = 1; assert_eq!(expected, got);
616
617 let got = client
619 .sign_raw_transaction_with_wallet(&tx, None)
620 .await
621 .unwrap();
622 assert!(got.complete);
623 assert!(got.errors.is_empty());
624
625 let txids = client
627 .test_mempool_accept(&tx)
628 .await
629 .expect("must be able to test mempool accept");
630 let got = txids
631 .results
632 .first()
633 .expect("there must be at least one txid");
634 assert_eq!(
635 got.txid,
636 tx.compute_txid(),
637 "txids must match in the mempool"
638 );
639
640 let got = client.send_raw_transaction(&tx).await.unwrap();
642 assert!(got.as_byte_array().len() == 32);
643
644 let got = client.list_transactions(None).await.unwrap();
646 assert_eq!(got.0.len(), 10);
647
648 mine_blocks(&bitcoind, 1, None).unwrap();
651 let got = client
652 .list_unspent(None, None, None, None, None)
653 .await
654 .unwrap();
655 assert_eq!(got.0.len(), 3);
656
657 let got = client.get_xpriv().await.unwrap().unwrap().network;
659 let expected = NetworkKind::Test;
660 assert_eq!(expected, got);
661
662 let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
665 let timestamp = "now".to_owned();
666 let list_descriptors = vec![ImportDescriptorInput {
667 desc: descriptor_string,
668 active: Some(true),
669 timestamp,
670 }];
671 let got = client
672 .import_descriptors(list_descriptors, "strata".to_owned())
673 .await
674 .unwrap()
675 .0;
676 let expected = vec![ImportDescriptorsResult {
677 success: true,
678 warnings: Some(vec![
679 "Range not given, using default keypool range".to_string()
680 ]),
681 error: None,
682 }];
683 assert_eq!(expected, got);
684
685 let psbt_address = client.get_new_address().await.unwrap();
686 let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
687 address: psbt_address.to_string(),
688 amount: 1.0,
689 }];
690
691 let funded_psbt = client
692 .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
693 .await
694 .unwrap();
695 assert!(!funded_psbt.psbt.inputs.is_empty());
696 assert!(funded_psbt.fee.to_sat() > 0);
697
698 let processed_psbt = client
699 .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
700 .await
701 .unwrap();
702 assert!(!processed_psbt.psbt.inputs.is_empty());
703 assert!(processed_psbt.complete);
704
705 let finalized_psbt = client
706 .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
707 .await
708 .unwrap();
709 assert!(finalized_psbt.complete);
710 assert!(finalized_psbt.hex.is_some());
711 let signed_tx = finalized_psbt.hex.as_ref().unwrap();
712 let signed_txid = signed_tx.compute_txid();
713 let got = client
714 .test_mempool_accept(signed_tx)
715 .await
716 .unwrap()
717 .results
718 .first()
719 .unwrap()
720 .txid;
721 assert_eq!(signed_txid, got);
722
723 let info_address = client.get_new_address().await.unwrap();
724 let address_info = client.get_address_info(&info_address).await.unwrap();
725 assert_eq!(address_info.address, info_address.as_unchecked().clone());
726 assert!(address_info.is_mine);
727 assert!(address_info.solvable.unwrap_or(false));
728
729 let unspent_address = client.get_new_address().await.unwrap();
730 let unspent_txid = client
731 .call::<String>(
732 "sendtoaddress",
733 &[
734 to_value(unspent_address.to_string()).unwrap(),
735 to_value(1.0).unwrap(),
736 ],
737 )
738 .await
739 .unwrap();
740 mine_blocks(&bitcoind, 1, None).unwrap();
741
742 let utxos = client
743 .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
744 .await
745 .unwrap();
746 assert!(!utxos.0.is_empty());
747
748 let utxos_filtered = client
749 .list_unspent(
750 Some(1),
751 Some(9_999_999),
752 Some(std::slice::from_ref(&unspent_address)),
753 Some(true),
754 None,
755 )
756 .await
757 .unwrap();
758 assert!(!utxos_filtered.0.is_empty());
759 let found_utxo = utxos_filtered.0.iter().any(|utxo| {
760 utxo.txid.to_string() == unspent_txid
761 && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
762 });
763 assert!(found_utxo);
764
765 let query_options = ListUnspentQueryOptions {
766 minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
767 maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
768 maximum_count: Some(10),
769 };
770 let utxos_with_query = client
771 .list_unspent(
772 Some(1),
773 Some(9_999_999),
774 None,
775 Some(true),
776 Some(query_options),
777 )
778 .await
779 .unwrap();
780 assert!(!utxos_with_query.0.is_empty());
781 for utxo in &utxos_with_query.0 {
782 let amount_btc = utxo.amount.to_btc();
783 assert!((0.5..=2.0).contains(&amount_btc));
784 }
785
786 let tx = finalized_psbt.hex.unwrap();
787 assert!(!tx.input.is_empty());
788 assert!(!tx.output.is_empty());
789 }
790
791 #[tokio::test()]
792 async fn get_tx_out() {
793 init_tracing();
794
795 let (bitcoind, client) = get_bitcoind_and_client();
796
797 let got = client.network().await.unwrap();
799 let expected = Network::Regtest;
800 assert_eq!(expected, got);
801
802 let address = bitcoind.client.new_address().unwrap();
803 let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
804 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
805 let coinbase_tx = last_block.coinbase().unwrap();
806
807 let got = client
809 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
810 .await
811 .unwrap();
812 assert_eq!(got.tx_out.value, COINBASE_AMOUNT);
813
814 let new_address = bitcoind.client.new_address().unwrap();
816 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); let _send_tx = bitcoind
818 .client
819 .send_to_address(&new_address, send_amount)
820 .unwrap()
821 .txid()
822 .unwrap();
823 let result = client
824 .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
825 .await;
826 trace!(?result, "gettxout result");
827 assert!(result.is_err());
828 }
829
830 #[tokio::test()]
837 async fn submit_package() {
838 init_tracing();
839
840 let (bitcoind, client) = get_bitcoind_and_client();
841
842 let got = client.network().await.unwrap();
844 let expected = Network::Regtest;
845 assert_eq!(expected, got);
846
847 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
848 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
849 let coinbase_tx = last_block.coinbase().unwrap();
850
851 let destination = client.get_new_address().await.unwrap();
852 let change_address = client.get_new_address().await.unwrap();
853 let amount = Amount::from_btc(1.0).unwrap();
854 let fees = Amount::from_btc(0.0001).unwrap();
855 let change_amount = COINBASE_AMOUNT - amount - fees;
856 let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
857
858 let send_back_address = client.get_new_address().await.unwrap();
859 let parent_raw_tx = CreateRawTransactionArguments {
860 inputs: vec![CreateRawTransactionInput {
861 txid: coinbase_tx.compute_txid().to_string(),
862 vout: 0,
863 }],
864 outputs: vec![
865 CreateRawTransactionOutput::AddressAmount {
867 address: destination.to_string(),
868 amount: amount.to_btc(),
869 },
870 CreateRawTransactionOutput::AddressAmount {
872 address: change_address.to_string(),
873 amount: change_amount.to_btc(),
874 },
875 ],
876 };
877 let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
878 let signed_parent = client
879 .sign_raw_transaction_with_wallet(&parent, None)
880 .await
881 .unwrap()
882 .tx;
883
884 let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
886
887 let child_raw_tx = CreateRawTransactionArguments {
888 inputs: vec![CreateRawTransactionInput {
889 txid: parent_submitted.to_string(),
890 vout: 0,
891 }],
892 outputs: vec![
893 CreateRawTransactionOutput::AddressAmount {
895 address: send_back_address.to_string(),
896 amount: amount_minus_fees.to_btc(),
897 },
898 ],
899 };
900 let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
901 let signed_child = client
902 .sign_raw_transaction_with_wallet(&child, None)
903 .await
904 .unwrap()
905 .tx;
906
907 let result = client
909 .submit_package(&[signed_parent, signed_child])
910 .await
911 .unwrap();
912 assert_eq!(result.tx_results.len(), 2);
913 assert_eq!(result.package_msg, "success");
914 }
915
916 #[tokio::test]
923 async fn submit_package_1p1c() {
924 init_tracing();
925
926 let (bitcoind, client) = get_bitcoind_and_client();
927
928 let server_version = bitcoind.client.server_version().unwrap();
930 assert!(server_version > 28);
931
932 let destination = client.get_new_address().await.unwrap();
933
934 let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
935 let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
936 let coinbase_tx = last_block.coinbase().unwrap();
937
938 let parent_raw_tx = CreateRawTransactionArguments {
939 inputs: vec![CreateRawTransactionInput {
940 txid: coinbase_tx.compute_txid().to_string(),
941 vout: 0,
942 }],
943 outputs: vec![CreateRawTransactionOutput::AddressAmount {
944 address: destination.to_string(),
945 amount: COINBASE_AMOUNT.to_btc(),
946 }],
947 };
948 let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
949 parent.version = transaction::Version(3);
950 assert_eq!(parent.version, transaction::Version(3));
951 trace!(?parent, "parent:");
952 let signed_parent = client
953 .sign_raw_transaction_with_wallet(&parent, None)
954 .await
955 .unwrap()
956 .tx;
957 assert_eq!(signed_parent.version, transaction::Version(3));
958
959 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
961 assert!(parent_broadcasted.is_err());
962
963 let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
965 let child_raw_tx = CreateRawTransactionArguments {
966 inputs: vec![CreateRawTransactionInput {
967 txid: signed_parent.compute_txid().to_string(),
968 vout: 0,
969 }],
970 outputs: vec![CreateRawTransactionOutput::AddressAmount {
971 address: destination.to_string(),
972 amount: amount_minus_fees.to_btc(),
973 }],
974 };
975 let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
976 child.version = transaction::Version(3);
977 assert_eq!(child.version, transaction::Version(3));
978 trace!(?child, "child:");
979 let prev_outputs = vec![PreviousTransactionOutput {
980 txid: parent.compute_txid(),
981 vout: 0,
982 script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
983 redeem_script: None,
984 witness_script: None,
985 amount: Some(COINBASE_AMOUNT.to_btc()),
986 }];
987 let signed_child = client
988 .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
989 .await
990 .unwrap()
991 .tx;
992 assert_eq!(signed_child.version, transaction::Version(3));
993
994 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
996 assert!(child_broadcasted.is_err());
997
998 let result = client
1000 .submit_package(&[signed_parent, signed_child])
1001 .await
1002 .unwrap();
1003 assert_eq!(result.tx_results.len(), 2);
1004 assert_eq!(result.package_msg, "success");
1005 }
1006
1007 #[tokio::test]
1008 async fn test_invalid_credentials_return_401_error() {
1009 init_tracing();
1010
1011 let (bitcoind, _) = get_bitcoind_and_client();
1012 let url = bitcoind.rpc_url();
1013
1014 let invalid_client = Client::new(
1016 url,
1017 "wrong_user".to_string(),
1018 "wrong_password".to_string(),
1019 None,
1020 None,
1021 )
1022 .unwrap();
1023
1024 let result = invalid_client.get_blockchain_info().await;
1026
1027 assert!(result.is_err());
1029 let error = result.unwrap_err();
1030
1031 match error {
1032 ClientError::Status(status_code, message) => {
1033 assert_eq!(status_code, 401);
1034 assert!(message.contains("Unauthorized"));
1035 }
1036 _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1037 }
1038 }
1039
1040 #[tokio::test]
1041 async fn psbt_bump_fee() {
1042 init_tracing();
1043
1044 let (bitcoind, client) = get_bitcoind_and_client();
1045
1046 mine_blocks(&bitcoind, 101, None).unwrap();
1048
1049 let destination = client.get_new_address().await.unwrap();
1051 let amount = Amount::from_btc(0.001).unwrap(); let txid = bitcoind
1055 .client
1056 .send_to_address_rbf(&destination, amount)
1057 .unwrap()
1058 .txid()
1059 .unwrap();
1060
1061 let mempool = client.get_raw_mempool().await.unwrap();
1063 assert!(
1064 mempool.0.contains(&txid),
1065 "Transaction should be in mempool for RBF"
1066 );
1067
1068 let signed_tx = client
1070 .psbt_bump_fee(&txid, None)
1071 .await
1072 .unwrap()
1073 .psbt
1074 .extract_tx()
1075 .unwrap();
1076 let signed_txid = signed_tx.compute_txid();
1077 let got = client
1078 .test_mempool_accept(&signed_tx)
1079 .await
1080 .unwrap()
1081 .results
1082 .first()
1083 .unwrap()
1084 .txid;
1085 assert_eq!(
1086 got, signed_txid,
1087 "Bumped transaction should be accepted in mempool"
1088 );
1089
1090 let options = PsbtBumpFeeOptions {
1092 fee_rate: Some(FeeRate::from_sat_per_kwu(20)), ..Default::default()
1094 };
1095 trace!(?options, "Calling psbt_bump_fee");
1096 let signed_tx = client
1097 .psbt_bump_fee(&txid, Some(options))
1098 .await
1099 .unwrap()
1100 .psbt
1101 .extract_tx()
1102 .unwrap();
1103 let signed_txid = signed_tx.compute_txid();
1104 let got = client
1105 .test_mempool_accept(&signed_tx)
1106 .await
1107 .unwrap()
1108 .results
1109 .first()
1110 .unwrap()
1111 .txid;
1112 assert_eq!(
1113 got, signed_txid,
1114 "Bumped transaction should be accepted in mempool"
1115 );
1116 }
1117}