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
9use bdk_bitcoind_rpc::bitcoincore_rpc::Result as RpcResult;
10use bitcoin::address::NetworkUnchecked;
11use bitcoin::hex::FromHex;
12use bitcoin::{Address, Amount, FeeRate, Transaction, Txid, Weight};
13use serde::{self, Deserialize, Serialize};
14use serde::de::Error as SerdeError;
15
16use crate::{BlockHeight, BlockRef, FeeRateExt, TxStatus, DEEPLY_CONFIRMED};
17
18#[cfg(all(feature = "wasm-web", feature = "rpc-socks5-proxy"))]
19compile_error!("`wasm-web` does not support the `rpc-socks5-proxy` feature");
20
21pub const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
23
24pub const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
26
27#[derive(Debug, Clone)]
35pub struct BitcoinRpcClient {
36 client: std::sync::Arc<Client>,
37}
38
39impl BitcoinRpcClient {
40 pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
41 Ok(BitcoinRpcClient {
42 client: std::sync::Arc::new(Client::new(url, auth)?),
43 })
44 }
45}
46
47impl RpcApi for BitcoinRpcClient {
48 fn call<T: for<'a> serde::de::Deserialize<'a>>(
49 &self, cmd: &str, args: &[serde_json::Value],
50 ) -> Result<T, Error> {
51 self.client.call(cmd, args)
52 }
53}
54
55mod serde_hex {
59 use bitcoin::hex::{DisplayHex, FromHex};
60 use serde::de::Error;
61 use serde::{Deserializer, Serializer};
62
63 pub fn serialize<S: Serializer>(b: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
64 s.serialize_str(&b.to_lower_hex_string())
65 }
66
67 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
68 let hex_str: String = ::serde::Deserialize::deserialize(d)?;
69 Ok(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?)
70 }
71
72 pub mod opt {
73 use bitcoin::hex::{DisplayHex, FromHex};
74 use serde::de::Error;
75 use serde::{Deserializer, Serializer};
76
77 pub fn serialize<S: Serializer>(b: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
78 match *b {
79 None => s.serialize_none(),
80 Some(ref b) => s.serialize_str(&b.to_lower_hex_string()),
81 }
82 }
83
84 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
85 let hex_str: String = ::serde::Deserialize::deserialize(d)?;
86 Ok(Some(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?))
87 }
88 }
89}
90
91fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
93where
94 D: serde::Deserializer<'de>,
95{
96 let v: Vec<String> = Vec::deserialize(deserializer)?;
100 let mut res = Vec::new();
101 for h in v.into_iter() {
102 res.push(FromHex::from_hex(&h).map_err(D::Error::custom)?);
103 }
104 Ok(Some(res))
105}
106
107#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
108#[serde(rename_all = "camelCase")]
109pub struct GetRawTransactionResultVinScriptSig {
110 pub asm: String,
111 #[serde(with = "serde_hex")]
112 pub hex: Vec<u8>,
113}
114
115#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
116#[serde(rename_all = "camelCase")]
117pub struct GetRawTransactionResultVin {
118 pub sequence: u32,
119 #[serde(default, with = "serde_hex::opt")]
121 pub coinbase: Option<Vec<u8>>,
122 pub txid: Option<Txid>,
124 pub vout: Option<u32>,
126 pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
128 #[serde(default, deserialize_with = "deserialize_hex_array_opt")]
130 pub txinwitness: Option<Vec<Vec<u8>>>,
131}
132
133#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct GetRawTransactionResultVout {
136 #[serde(with = "bitcoin::amount::serde::as_btc")]
137 pub value: Amount,
138 pub n: u32,
139 pub script_pub_key: GetRawTransactionResultVoutScriptPubKey,
140}
141
142#[allow(non_camel_case_types)]
143#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
144#[serde(rename_all = "lowercase")]
145pub enum ScriptPubkeyType {
146 Nonstandard,
147 Anchor,
148 Pubkey,
149 PubkeyHash,
150 ScriptHash,
151 MultiSig,
152 NullData,
153 Witness_v0_KeyHash,
154 Witness_v0_ScriptHash,
155 Witness_v1_Taproot,
156 Witness_Unknown,
157}
158
159#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct GetRawTransactionResultVoutScriptPubKey {
162 pub asm: String,
163 #[serde(with = "serde_hex")]
164 pub hex: Vec<u8>,
165 pub req_sigs: Option<usize>,
166 #[serde(rename = "type")]
167 pub type_: Option<ScriptPubkeyType>,
168 #[serde(default)]
170 pub addresses: Vec<Address<NetworkUnchecked>>,
171 #[serde(default)]
173 pub address: Option<Address<NetworkUnchecked>>,
174}
175
176#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
177#[serde(rename_all = "camelCase")]
178pub struct GetRawTransactionResult {
179 #[serde(rename = "in_active_chain")]
180 pub in_active_chain: Option<bool>,
181 #[serde(with = "serde_hex")]
182 pub hex: Vec<u8>,
183 pub txid: Txid,
184 pub hash: bitcoin::Wtxid,
185 pub size: usize,
186 pub vsize: usize,
187 pub version: u32,
188 pub locktime: u32,
189 pub vin: Vec<GetRawTransactionResultVin>,
190 pub vout: Vec<GetRawTransactionResultVout>,
191 pub blockhash: Option<bitcoin::BlockHash>,
192 pub confirmations: Option<u32>,
193 pub time: Option<usize>,
194 pub blocktime: Option<usize>,
195}
196
197#[derive(Clone, Debug, Deserialize)]
199pub struct SubmitPackageResult {
200 #[serde(rename = "tx-results")]
201 pub tx_results: HashMap<bitcoin::Wtxid, SubmitPackageTxResult>,
202 pub package_msg: String,
203}
204
205#[derive(Clone, Debug, Deserialize)]
207pub struct SubmitPackageTxResult {
208 pub txid: Txid,
209 pub error: Option<String>,
210}
211
212fn into_json<T>(val: T) -> RpcResult<serde_json::Value>
214where
215 T: serde::ser::Serialize,
216{
217 Ok(serde_json::to_value(val)?)
218}
219
220fn opt_into_json<T>(opt: Option<T>) -> RpcResult<serde_json::Value>
222where
223 T: serde::ser::Serialize,
224{
225 match opt {
226 Some(val) => Ok(into_json(val)?),
227 None => Ok(serde_json::Value::Null),
228 }
229}
230
231fn handle_defaults<'a, 'b>(
247 args: &'a mut [serde_json::Value],
248 defaults: &'b [serde_json::Value],
249) -> &'a [serde_json::Value] {
250 assert!(args.len() >= defaults.len());
251
252 let mut first_non_null_optional_idx = None;
255 for i in 0..defaults.len() {
256 let args_i = args.len() - 1 - i;
257 let defaults_i = defaults.len() - 1 - i;
258 if args[args_i] == serde_json::Value::Null {
259 if first_non_null_optional_idx.is_some() {
260 if defaults[defaults_i] == serde_json::Value::Null {
261 panic!("Missing `default` for argument idx {}", args_i);
262 }
263 args[args_i] = defaults[defaults_i].clone();
264 }
265 } else if first_non_null_optional_idx.is_none() {
266 first_non_null_optional_idx = Some(args_i);
267 }
268 }
269
270 let required_num = args.len() - defaults.len();
271
272 if let Some(i) = first_non_null_optional_idx {
273 &args[..i + 1]
274 } else {
275 &args[..required_num]
276 }
277}
278
279fn null() -> serde_json::Value {
281 serde_json::Value::Null
282}
283
284pub trait BitcoinRpcErrorExt: Borrow<Error> {
285 fn is_not_found(&self) -> bool {
287 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
288 e.code == RPC_INVALID_ADDRESS_OR_KEY
289 } else {
290 false
291 }
292 }
293
294 fn is_in_utxo_set(&self) -> bool {
296 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
297 e.code == RPC_VERIFY_ALREADY_IN_UTXO_SET
298 } else {
299 false
300 }
301 }
302
303 fn is_already_in_mempool(&self) -> bool {
304 if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
305 e.message.contains("txn-already-in-mempool")
306 } else {
307 false
308 }
309 }
310}
311impl BitcoinRpcErrorExt for Error {}
312
313pub trait BitcoinRpcExt: RpcApi {
314 fn custom_get_raw_transaction_info(
315 &self,
316 txid: Txid,
317 block_hash: Option<&bitcoin::BlockHash>,
318 ) -> RpcResult<Option<GetRawTransactionResult>> {
319 let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?];
320 match self.call("getrawtransaction", handle_defaults(&mut args, &[null()])) {
321 Ok(ret) => Ok(Some(ret)),
322 Err(e) if e.is_not_found() => Ok(None),
323 Err(e) => Err(e),
324 }
325 }
326
327 fn broadcast_tx(&self, tx: &Transaction) -> Result<(), Error> {
328 match self.send_raw_transaction(tx) {
329 Ok(_) => Ok(()),
330 Err(e) if e.is_in_utxo_set() => Ok(()),
331 Err(e) => Err(e),
332 }
333 }
334
335 fn tip(&self) -> Result<BlockRef, Error> {
336 let height = self.get_block_count()?;
337 let hash = self.get_block_hash(height)?;
338 Ok(BlockRef { height: height as BlockHeight, hash })
339 }
340
341 fn deep_tip(&self) -> Result<BlockRef, Error> {
342 let tip = self.get_block_count()?;
343 let height = tip.saturating_sub(DEEPLY_CONFIRMED as u64);
344 let hash = self.get_block_hash(height)?;
345 Ok(BlockRef { height: height as BlockHeight, hash })
346 }
347
348 fn get_block_by_height(&self, height: BlockHeight) -> Result<BlockRef, Error> {
349 let hash = self.get_block_hash(height as u64)?;
350 Ok(BlockRef { height, hash })
351 }
352
353 fn tx_status(&self, txid: Txid) -> Result<TxStatus, Error> {
354 match self.custom_get_raw_transaction_info(txid, None)? {
355 Some(tx) => match tx.blockhash {
356 Some(hash) => {
357 let block = self.get_block_header_info(&hash)?;
358 if block.confirmations > 0 {
359 Ok(TxStatus::Confirmed(BlockRef { height: block.height as BlockHeight, hash: block.hash }))
360 } else {
361 Ok(TxStatus::Mempool)
362 }
363 },
364 None => Ok(TxStatus::Mempool),
365 },
366 None => Ok(TxStatus::NotFound)
367 }
368 }
369
370 fn submit_package(&self, txs: &[impl Borrow<Transaction>]) -> Result<SubmitPackageResult, Error> {
371 let hexes = txs.iter()
372 .map(|t| bitcoin::consensus::encode::serialize_hex(t.borrow()))
373 .collect::<Vec<_>>();
374 self.call("submitpackage", &[hexes.into()])
375 }
376
377 fn get_mempool_spending_tx(
381 &self,
382 outpoint: bitcoin::OutPoint,
383 ) -> Result<Option<Txid>, Error> {
384 let mempool_txids: Vec<Txid> = self.call("getrawmempool", &[false.into()])?;
386
387 for txid in mempool_txids {
388 let tx = self.get_raw_transaction(&txid, None)?;
389 for input in &tx.input {
390 if input.previous_output == outpoint {
391 return Ok(Some(txid));
392 }
393 }
394 }
395 Ok(None)
396 }
397
398 fn estimate_mempool_feerate(
403 &self,
404 txid: Txid,
405 ) -> RpcResult<Option<FeeRate>> {
406 let entry = match self.get_mempool_entry(&txid) {
407 Ok(e) => e,
408 Err(e) if e.is_not_found() => return Ok(None),
409 Err(e) => return Err(e),
410 };
411
412 let entry_feerate = |e: &json::GetMempoolEntryResult| -> Result<FeeRate, Error> {
413 ancestor_feerate(e.fees.ancestor, e.ancestor_size)
414 .ok_or(Error::UnexpectedStructure)
415 };
416
417 let mut feerate = entry_feerate(&entry)?;
419
420 for descendant_txid in &entry.spent_by {
422 if let Ok(desc_entry) = self.get_mempool_entry(descendant_txid) {
423 feerate = std::cmp::max(feerate, entry_feerate(&desc_entry)?);
424 }
425 }
426
427 Ok(Some(feerate))
428 }
429}
430
431impl <T: RpcApi> BitcoinRpcExt for T {}
432
433fn ancestor_feerate(ancestor_fee: Amount, ancestor_size_vb: u64) -> Option<FeeRate> {
437 let weight = Weight::from_vb(ancestor_size_vb)?;
438 FeeRate::from_amount_and_weight_ceil(ancestor_fee, weight)
439}
440
441pub fn create_client(
446 url: &str,
447 auth: Auth,
448 #[cfg(feature = "rpc-socks5-proxy")]
449 socks5_proxy: Option<&str>,
450) -> Result<Client, Error> {
451 #[cfg(feature = "rpc-socks5-proxy")]
452 if let Some(proxy) = socks5_proxy {
453 let (user, pass) = auth.get_user_pass()?;
454 let rpc_auth = user.map(|u| (u, pass));
455 let transport = socks5_transport::Socks5Transport::new(url, proxy, rpc_auth)
456 .map_err(|e| Error::JsonRpc(jsonrpc::Error::Transport(e.into())))?;
457
458 return Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport(transport)));
459 }
460 Client::new(url, auth)
461}
462
463#[cfg(test)]
464mod test {
465 use super::*;
466
467 #[test]
472 fn ancestor_feerate_above_250_vbytes_is_nonzero() {
473 let fr = ancestor_feerate(Amount::from_sat(10_000), 1_000).unwrap();
476 assert_eq!(fr.to_sat_per_kwu(), 2_500);
477 }
478
479 #[test]
480 fn ancestor_feerate_just_above_threshold() {
481 let fr = ancestor_feerate(Amount::from_sat(10_000), 251).unwrap();
484 assert_eq!(fr.to_sat_per_kwu(), 9_961);
485 }
486
487 #[test]
488 fn ancestor_feerate_below_250_no_precision_loss() {
489 let fr = ancestor_feerate(Amount::from_sat(1_000), 100).unwrap();
492 assert_eq!(fr.to_sat_per_kwu(), 2_500);
493 }
494
495 #[test]
496 fn ancestor_feerate_zero_size_is_none() {
497 assert_eq!(ancestor_feerate(Amount::from_sat(1_000), 0), None);
498 }
499}