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