Skip to main content

bitcoin_ext/rpc/
mod.rs

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
29/// Error code for RPC_VERIFY_ALREADY_IN_UTXO_SET.
30pub const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
31
32/// Error code for RPC_INVALID_ADDRESS_OR_KEY, used when a tx is not found.
33pub const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
34
35/// Clonable bitcoind rpc client.
36///
37/// Clones share the underlying [Client] and its single TCP connection.
38/// The client can safely be used from multiple threads, but only one
39/// request is in flight at a time: concurrent callers take turns on the
40/// connection. Create separate clients if requests must run in parallel.
41/// The connection is re-established transparently when it drops.
42#[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
63/// A module used for serde serialization of bytes in hexadecimal format.
64///
65/// The module is compatible with the serde attribute.
66mod 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
99/// deserialize_hex_array_opt deserializes a vector of hex-encoded byte arrays.
100fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
101where
102	D: serde::Deserializer<'de>,
103{
104	//TODO(stevenroose) Revisit when issue is fixed:
105	// https://github.com/serde-rs/serde/issues/723
106
107	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	/// The raw scriptSig in case of a coinbase tx.
128	#[serde(default, with = "serde_hex::opt")]
129	pub coinbase: Option<Vec<u8>>,
130	/// Not provided for coinbase txs.
131	pub txid: Option<Txid>,
132	/// Not provided for coinbase txs.
133	pub vout: Option<u32>,
134	/// The scriptSig in case of a non-coinbase tx.
135	pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
136	/// Not provided for coinbase txs.
137	#[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	// Deprecated in Bitcoin Core 22
177	#[serde(default)]
178	pub addresses: Vec<Address<NetworkUnchecked>>,
179	// Added in Bitcoin Core 22
180	#[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/// Result from the `submitpackage` RPC call.
206#[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/// Per-transaction result from the `submitpackage` RPC call.
214#[derive(Clone, Debug, Deserialize)]
215pub struct SubmitPackageTxResult {
216	pub txid: Txid,
217	pub error: Option<String>,
218}
219
220/// Shorthand for converting a variable into a serde_json::Value.
221fn 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
228/// Shorthand for converting an Option into an Option<serde_json::Value>.
229fn 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
239/// Handle default values in the argument list
240///
241/// Substitute `Value::Null`s with corresponding values from `defaults` table,
242/// except when they are trailing, in which case just skip them altogether
243/// in returned list.
244///
245/// Note, that `defaults` corresponds to the last elements of `args`.
246///
247/// ```norust
248/// arg1 arg2 arg3 arg4
249///           def1 def2
250/// ```
251///
252/// Elements of `args` without corresponding `defaults` value, won't
253/// be substituted, because they are required.
254fn 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	// Pass over the optional arguments in backwards order, filling in defaults after the first
261	// non-null optional argument has been observed.
262	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
287/// Shorthand for `serde_json::Value::Null`.
288fn null() -> serde_json::Value {
289	serde_json::Value::Null
290}
291
292pub trait BitcoinRpcErrorExt: Borrow<Error> {
293	/// Whether this error indicates that the tx was not found.
294	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	/// Whether this error indicates that the tx is already in the utxo set.
303	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	/// Get the transaction currently spending a given outpoint from the mempool.
386	///
387	/// Returns None if the outpoint is not being spent by any mempool transaction.
388	fn get_mempool_spending_tx(
389		&self,
390		outpoint: bitcoin::OutPoint,
391	) -> Result<Option<Txid>, Error> {
392		// Get all mempool txids
393		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	/// Estimate the effective feerate of a mempool transaction.
407	///
408	/// Returns the effective feerate considering ancestors and CPFP from direct descendants.
409	/// Returns None if the transaction is not in the mempool.
410	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		// Start with this tx's ancestor fee rate
426		let mut feerate = entry_feerate(&entry)?;
427
428		// Check direct descendants - if any has better ancestor rate, use that (CPFP)
429		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
441/// Effective feerate for a mempool entry given its ancestor fee total and
442/// ancestor package size (in vbytes, as returned by `getmempoolentry`).
443/// Returns `None` if the size is zero or the math overflows.
444fn 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
449/// Creates a bitcoind RPC client, optionally routing through a SOCKS5 proxy.
450///
451/// When no proxy is set, the standard transport is used.
452/// When a proxy is set, a ureq-based transport routes traffic through the SOCKS5 proxy.
453pub 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/// Error from [BitcoinAsyncRpcExt::require_txindex].
472#[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/// How the async client reports a JSON-RPC `result` of `null`.
482///
483/// It has no typed representation for one: `Client::call` turns a missing result
484/// into `ClientError::Other` carrying this message, and `call_raw` delegates to
485/// `call`, so matching the message is the only way to tell a null result from a
486/// genuine failure. Keep it in one place — were the upstream wording to change,
487/// every caller of [BitcoinAsyncRpcExt::try_get_tx_out] would quietly stop recognising
488/// a spent output.
489#[cfg(feature = "rpc-async")]
490const ASYNC_CLIENT_NULL_RESULT: &str = "Empty data received";
491
492/// Extension trait for the async bitcoind rpc client.
493#[cfg(feature = "rpc-async")]
494#[async_trait]
495pub trait BitcoinAsyncRpcExt {
496	/// Checks that the connected bitcoind runs with `txindex=1`.
497	async fn require_txindex(&self) -> Result<(), TxindexError>;
498
499	/// `gettxout`, reporting the `null` of a spent or unknown output as `Ok(None)`.
500	///
501	/// Distinct from `Reader::get_tx_out`, which surfaces that `null` as an error.
502	///
503	/// Without `include_mempool` this reads the confirmed utxo set alone, so an
504	/// output spent only by a mempool transaction still reports as present.
505	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", &params).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	// Regression: a previous implementation computed
546	// `sat * (250 / ancestor_size)` due to integer-division precedence,
547	// returning feerate 0 for any tx with `ancestor_size > 250` vbytes.
548
549	#[test]
550	fn ancestor_feerate_above_250_vbytes_is_nonzero() {
551		// 1000 vbytes is well above the buggy threshold.
552		// sat/kwu = ceil(10_000 * 1000 / (1000 * 4)) = 2_500
553		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		// 251 vbytes - one above where the old code started returning 0.
560		// sat/kwu = ceil(10_000 * 1000 / 1004) = ceil(9960.16) = 9_961
561		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		// 100 vbytes - old code gave sat * 2 instead of sat * 2.5 (20% off).
568		// sat/kwu = ceil(1_000 * 1000 / 400) = 2_500
569		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}