1#[cfg(feature = "rpc-socks5-proxy")]
2mod socks5_transport;
3
4pub use bdk_bitcoind_rpc::bitcoincore_rpc::{self, json, jsonrpc, Auth, Client, Error, RpcApi};
5
6use std::borrow::Borrow;
7use std::collections::HashMap;
8
9#[cfg(feature = "rpc-async")]
10use async_trait::async_trait;
11use bdk_bitcoind_rpc::bitcoincore_rpc::Result as RpcResult;
12#[cfg(feature = "rpc-async")]
13use bitcoind_async_client::Client as AsyncClient;
14#[cfg(feature = "rpc-async")]
15use bitcoind_async_client::error::ClientError as AsyncClientError;
16use bitcoin::address::NetworkUnchecked;
17use bitcoin::hex::FromHex;
18use bitcoin::{Address, Amount, FeeRate, Transaction, Txid, Weight};
19#[cfg(feature = "rpc-async")]
20use bitcoin::OutPoint;
21use serde::{self, Deserialize, Serialize};
22use serde::de::Error as SerdeError;
23
24use crate::{BlockHeight, BlockRef, FeeRateExt, TxStatus, DEEPLY_CONFIRMED};
25
26#[cfg(all(feature = "wasm-web", feature = "rpc-socks5-proxy"))]
27compile_error!("`wasm-web` does not support the `rpc-socks5-proxy` feature");
28
29pub const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
31
32pub const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
34
35#[derive(Debug, Clone)]
43pub struct BitcoinRpcClient {
44 client: std::sync::Arc<Client>,
45}
46
47impl BitcoinRpcClient {
48 pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
49 Ok(BitcoinRpcClient {
50 client: std::sync::Arc::new(Client::new(url, auth)?),
51 })
52 }
53}
54
55impl RpcApi for BitcoinRpcClient {
56 fn call<T: for<'a> serde::de::Deserialize<'a>>(
57 &self, cmd: &str, args: &[serde_json::Value],
58 ) -> Result<T, Error> {
59 self.client.call(cmd, args)
60 }
61}
62
63mod serde_hex {
67 use bitcoin::hex::{DisplayHex, FromHex};
68 use serde::de::Error;
69 use serde::{Deserializer, Serializer};
70
71 pub fn serialize<S: Serializer>(b: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
72 s.serialize_str(&b.to_lower_hex_string())
73 }
74
75 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
76 let hex_str: String = ::serde::Deserialize::deserialize(d)?;
77 Ok(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?)
78 }
79
80 pub mod opt {
81 use bitcoin::hex::{DisplayHex, FromHex};
82 use serde::de::Error;
83 use serde::{Deserializer, Serializer};
84
85 pub fn serialize<S: Serializer>(b: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
86 match *b {
87 None => s.serialize_none(),
88 Some(ref b) => s.serialize_str(&b.to_lower_hex_string()),
89 }
90 }
91
92 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
93 let hex_str: String = ::serde::Deserialize::deserialize(d)?;
94 Ok(Some(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?))
95 }
96 }
97}
98
99fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
101where
102 D: serde::Deserializer<'de>,
103{
104 let v: Vec<String> = Vec::deserialize(deserializer)?;
108 let mut res = Vec::new();
109 for h in v.into_iter() {
110 res.push(FromHex::from_hex(&h).map_err(D::Error::custom)?);
111 }
112 Ok(Some(res))
113}
114
115#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
116#[serde(rename_all = "camelCase")]
117pub struct GetRawTransactionResultVinScriptSig {
118 pub asm: String,
119 #[serde(with = "serde_hex")]
120 pub hex: Vec<u8>,
121}
122
123#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct GetRawTransactionResultVin {
126 pub sequence: u32,
127 #[serde(default, with = "serde_hex::opt")]
129 pub coinbase: Option<Vec<u8>>,
130 pub txid: Option<Txid>,
132 pub vout: Option<u32>,
134 pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
136 #[serde(default, deserialize_with = "deserialize_hex_array_opt")]
138 pub txinwitness: Option<Vec<Vec<u8>>>,
139}
140
141#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
142#[serde(rename_all = "camelCase")]
143pub struct GetRawTransactionResultVout {
144 #[serde(with = "bitcoin::amount::serde::as_btc")]
145 pub value: Amount,
146 pub n: u32,
147 pub script_pub_key: GetRawTransactionResultVoutScriptPubKey,
148}
149
150#[allow(non_camel_case_types)]
151#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
152#[serde(rename_all = "lowercase")]
153pub enum ScriptPubkeyType {
154 Nonstandard,
155 Anchor,
156 Pubkey,
157 PubkeyHash,
158 ScriptHash,
159 MultiSig,
160 NullData,
161 Witness_v0_KeyHash,
162 Witness_v0_ScriptHash,
163 Witness_v1_Taproot,
164 Witness_Unknown,
165}
166
167#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
168#[serde(rename_all = "camelCase")]
169pub struct GetRawTransactionResultVoutScriptPubKey {
170 pub asm: String,
171 #[serde(with = "serde_hex")]
172 pub hex: Vec<u8>,
173 pub req_sigs: Option<usize>,
174 #[serde(rename = "type")]
175 pub type_: Option<ScriptPubkeyType>,
176 #[serde(default)]
178 pub addresses: Vec<Address<NetworkUnchecked>>,
179 #[serde(default)]
181 pub address: Option<Address<NetworkUnchecked>>,
182}
183
184#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
185#[serde(rename_all = "camelCase")]
186pub struct GetRawTransactionResult {
187 #[serde(rename = "in_active_chain")]
188 pub in_active_chain: Option<bool>,
189 #[serde(with = "serde_hex")]
190 pub hex: Vec<u8>,
191 pub txid: Txid,
192 pub hash: bitcoin::Wtxid,
193 pub size: usize,
194 pub vsize: usize,
195 pub version: u32,
196 pub locktime: u32,
197 pub vin: Vec<GetRawTransactionResultVin>,
198 pub vout: Vec<GetRawTransactionResultVout>,
199 pub blockhash: Option<bitcoin::BlockHash>,
200 pub confirmations: Option<u32>,
201 pub time: Option<usize>,
202 pub blocktime: Option<usize>,
203}
204
205#[derive(Clone, Debug, Deserialize)]
207pub struct SubmitPackageResult {
208 #[serde(rename = "tx-results")]
209 pub tx_results: HashMap<bitcoin::Wtxid, SubmitPackageTxResult>,
210 pub package_msg: String,
211}
212
213#[derive(Clone, Debug, Deserialize)]
215pub struct SubmitPackageTxResult {
216 pub txid: Txid,
217 pub error: Option<String>,
218}
219
220fn into_json<T>(val: T) -> RpcResult<serde_json::Value>
222where
223 T: serde::ser::Serialize,
224{
225 Ok(serde_json::to_value(val)?)
226}
227
228fn opt_into_json<T>(opt: Option<T>) -> RpcResult<serde_json::Value>
230where
231 T: serde::ser::Serialize,
232{
233 match opt {
234 Some(val) => Ok(into_json(val)?),
235 None => Ok(serde_json::Value::Null),
236 }
237}
238
239fn handle_defaults<'a, 'b>(
255 args: &'a mut [serde_json::Value],
256 defaults: &'b [serde_json::Value],
257) -> &'a [serde_json::Value] {
258 assert!(args.len() >= defaults.len());
259
260 let mut first_non_null_optional_idx = None;
263 for i in 0..defaults.len() {
264 let args_i = args.len() - 1 - i;
265 let defaults_i = defaults.len() - 1 - i;
266 if args[args_i] == serde_json::Value::Null {
267 if first_non_null_optional_idx.is_some() {
268 if defaults[defaults_i] == serde_json::Value::Null {
269 panic!("Missing `default` for argument idx {}", args_i);
270 }
271 args[args_i] = defaults[defaults_i].clone();
272 }
273 } else if first_non_null_optional_idx.is_none() {
274 first_non_null_optional_idx = Some(args_i);
275 }
276 }
277
278 let required_num = args.len() - defaults.len();
279
280 if let Some(i) = first_non_null_optional_idx {
281 &args[..i + 1]
282 } else {
283 &args[..required_num]
284 }
285}
286
287fn null() -> serde_json::Value {
289 serde_json::Value::Null
290}
291
292pub trait BitcoinRpcErrorExt: Borrow<Error> {
293 fn is_not_found(&self) -> bool {
295 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
296 e.code == RPC_INVALID_ADDRESS_OR_KEY
297 } else {
298 false
299 }
300 }
301
302 fn is_in_utxo_set(&self) -> bool {
304 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
305 e.code == RPC_VERIFY_ALREADY_IN_UTXO_SET
306 } else {
307 false
308 }
309 }
310
311 fn is_already_in_mempool(&self) -> bool {
312 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
313 e.message.contains("txn-already-in-mempool")
314 } else {
315 false
316 }
317 }
318}
319impl BitcoinRpcErrorExt for Error {}
320
321pub trait BitcoinRpcExt: RpcApi {
322 fn custom_get_raw_transaction_info(
323 &self,
324 txid: Txid,
325 block_hash: Option<&bitcoin::BlockHash>,
326 ) -> RpcResult<Option<GetRawTransactionResult>> {
327 let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?];
328 match self.call("getrawtransaction", handle_defaults(&mut args, &[null()])) {
329 Ok(ret) => Ok(Some(ret)),
330 Err(e) if e.is_not_found() => Ok(None),
331 Err(e) => Err(e),
332 }
333 }
334
335 fn broadcast_tx(&self, tx: &Transaction) -> Result<(), Error> {
336 match self.send_raw_transaction(tx) {
337 Ok(_) => Ok(()),
338 Err(e) if e.is_in_utxo_set() => Ok(()),
339 Err(e) => Err(e),
340 }
341 }
342
343 fn tip(&self) -> Result<BlockRef, Error> {
344 let height = self.get_block_count()?;
345 let hash = self.get_block_hash(height)?;
346 Ok(BlockRef { height: height as BlockHeight, hash })
347 }
348
349 fn deep_tip(&self) -> Result<BlockRef, Error> {
350 let tip = self.get_block_count()?;
351 let height = tip.saturating_sub(DEEPLY_CONFIRMED as u64);
352 let hash = self.get_block_hash(height)?;
353 Ok(BlockRef { height: height as BlockHeight, hash })
354 }
355
356 fn get_block_by_height(&self, height: BlockHeight) -> Result<BlockRef, Error> {
357 let hash = self.get_block_hash(height as u64)?;
358 Ok(BlockRef { height, hash })
359 }
360
361 fn tx_status(&self, txid: Txid) -> Result<TxStatus, Error> {
362 match self.custom_get_raw_transaction_info(txid, None)? {
363 Some(tx) => match tx.blockhash {
364 Some(hash) => {
365 let block = self.get_block_header_info(&hash)?;
366 if block.confirmations > 0 {
367 Ok(TxStatus::Confirmed(BlockRef { height: block.height as BlockHeight, hash: block.hash }))
368 } else {
369 Ok(TxStatus::Mempool)
370 }
371 },
372 None => Ok(TxStatus::Mempool),
373 },
374 None => Ok(TxStatus::NotFound)
375 }
376 }
377
378 fn submit_package(&self, txs: &[impl Borrow<Transaction>]) -> Result<SubmitPackageResult, Error> {
379 let hexes = txs.iter()
380 .map(|t| bitcoin::consensus::encode::serialize_hex(t.borrow()))
381 .collect::<Vec<_>>();
382 self.call("submitpackage", &[hexes.into()])
383 }
384
385 fn get_mempool_spending_tx(
389 &self,
390 outpoint: bitcoin::OutPoint,
391 ) -> Result<Option<Txid>, Error> {
392 let mempool_txids: Vec<Txid> = self.call("getrawmempool", &[false.into()])?;
394
395 for txid in mempool_txids {
396 let tx = self.get_raw_transaction(&txid, None)?;
397 for input in &tx.input {
398 if input.previous_output == outpoint {
399 return Ok(Some(txid));
400 }
401 }
402 }
403 Ok(None)
404 }
405
406 fn estimate_mempool_feerate(
411 &self,
412 txid: Txid,
413 ) -> RpcResult<Option<FeeRate>> {
414 let entry = match self.get_mempool_entry(&txid) {
415 Ok(e) => e,
416 Err(e) if e.is_not_found() => return Ok(None),
417 Err(e) => return Err(e),
418 };
419
420 let entry_feerate = |e: &json::GetMempoolEntryResult| -> Result<FeeRate, Error> {
421 ancestor_feerate(e.fees.ancestor, e.ancestor_size)
422 .ok_or(Error::UnexpectedStructure)
423 };
424
425 let mut feerate = entry_feerate(&entry)?;
427
428 for descendant_txid in &entry.spent_by {
430 if let Ok(desc_entry) = self.get_mempool_entry(descendant_txid) {
431 feerate = std::cmp::max(feerate, entry_feerate(&desc_entry)?);
432 }
433 }
434
435 Ok(Some(feerate))
436 }
437}
438
439impl <T: RpcApi> BitcoinRpcExt for T {}
440
441fn ancestor_feerate(ancestor_fee: Amount, ancestor_size_vb: u64) -> Option<FeeRate> {
445 let weight = Weight::from_vb(ancestor_size_vb)?;
446 FeeRate::from_amount_and_weight_ceil(ancestor_fee, weight)
447}
448
449pub fn create_client(
454 url: &str,
455 auth: Auth,
456 #[cfg(feature = "rpc-socks5-proxy")]
457 socks5_proxy: Option<&str>,
458) -> Result<Client, Error> {
459 #[cfg(feature = "rpc-socks5-proxy")]
460 if let Some(proxy) = socks5_proxy {
461 let (user, pass) = auth.get_user_pass()?;
462 let rpc_auth = user.map(|u| (u, pass));
463 let transport = socks5_transport::Socks5Transport::new(url, proxy, rpc_auth)
464 .map_err(|e| Error::JsonRpc(jsonrpc::Error::Transport(e.into())))?;
465
466 return Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport(transport)));
467 }
468 Client::new(url, auth)
469}
470
471#[cfg(feature = "rpc-async")]
473#[derive(Debug, thiserror::Error)]
474pub enum TxindexError {
475 #[error("failed to getindexinfo from bitcoind")]
476 Rpc(#[from] AsyncClientError),
477 #[error("txindex is not enabled. Run bitcoind with txindex = 1")]
478 NotEnabled,
479}
480
481#[cfg(feature = "rpc-async")]
490const ASYNC_CLIENT_NULL_RESULT: &str = "Empty data received";
491
492#[cfg(feature = "rpc-async")]
494#[async_trait]
495pub trait BitcoinAsyncRpcExt {
496 async fn require_txindex(&self) -> Result<(), TxindexError>;
498
499 async fn try_get_tx_out(
506 &self,
507 outpoint: OutPoint,
508 include_mempool: bool,
509 ) -> Result<Option<json::GetTxOutResult>, AsyncClientError>;
510}
511
512#[cfg(feature = "rpc-async")]
513#[async_trait]
514impl BitcoinAsyncRpcExt for AsyncClient {
515 async fn require_txindex(&self) -> Result<(), TxindexError> {
516 let info: json::GetIndexInfoResult = self.call_raw("getindexinfo", &[]).await?;
517 if info.txindex.is_none() {
518 return Err(TxindexError::NotEnabled);
519 }
520 Ok(())
521 }
522
523 async fn try_get_tx_out(
524 &self,
525 outpoint: OutPoint,
526 include_mempool: bool,
527 ) -> Result<Option<json::GetTxOutResult>, AsyncClientError> {
528 let params = [
529 serde_json::Value::String(outpoint.txid.to_string()),
530 outpoint.vout.into(),
531 include_mempool.into(),
532 ];
533 match self.call_raw::<json::GetTxOutResult>("gettxout", ¶ms).await {
534 Ok(res) => Ok(Some(res)),
535 Err(AsyncClientError::Other(msg)) if msg == ASYNC_CLIENT_NULL_RESULT => Ok(None),
536 Err(e) => Err(e),
537 }
538 }
539}
540
541#[cfg(test)]
542mod test {
543 use super::*;
544
545 #[test]
550 fn ancestor_feerate_above_250_vbytes_is_nonzero() {
551 let fr = ancestor_feerate(Amount::from_sat(10_000), 1_000).unwrap();
554 assert_eq!(fr.to_sat_per_kwu(), 2_500);
555 }
556
557 #[test]
558 fn ancestor_feerate_just_above_threshold() {
559 let fr = ancestor_feerate(Amount::from_sat(10_000), 251).unwrap();
562 assert_eq!(fr.to_sat_per_kwu(), 9_961);
563 }
564
565 #[test]
566 fn ancestor_feerate_below_250_no_precision_loss() {
567 let fr = ancestor_feerate(Amount::from_sat(1_000), 100).unwrap();
570 assert_eq!(fr.to_sat_per_kwu(), 2_500);
571 }
572
573 #[test]
574 fn ancestor_feerate_zero_size_is_none() {
575 assert_eq!(ancestor_feerate(Amount::from_sat(1_000), 0), None);
576 }
577}