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
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
21/// Error code for RPC_VERIFY_ALREADY_IN_UTXO_SET.
22pub const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
23
24/// Error code for RPC_INVALID_ADDRESS_OR_KEY, used when a tx is not found.
25pub const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
26
27/// Clonable bitcoind rpc client.
28///
29/// Clones share the underlying [Client] and its single TCP connection.
30/// The client can safely be used from multiple threads, but only one
31/// request is in flight at a time: concurrent callers take turns on the
32/// connection. Create separate clients if requests must run in parallel.
33/// The connection is re-established transparently when it drops.
34#[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
55/// A module used for serde serialization of bytes in hexadecimal format.
56///
57/// The module is compatible with the serde attribute.
58mod 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
91/// deserialize_hex_array_opt deserializes a vector of hex-encoded byte arrays.
92fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
93where
94	D: serde::Deserializer<'de>,
95{
96	//TODO(stevenroose) Revisit when issue is fixed:
97	// https://github.com/serde-rs/serde/issues/723
98
99	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	/// The raw scriptSig in case of a coinbase tx.
120	#[serde(default, with = "serde_hex::opt")]
121	pub coinbase: Option<Vec<u8>>,
122	/// Not provided for coinbase txs.
123	pub txid: Option<Txid>,
124	/// Not provided for coinbase txs.
125	pub vout: Option<u32>,
126	/// The scriptSig in case of a non-coinbase tx.
127	pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
128	/// Not provided for coinbase txs.
129	#[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	// Deprecated in Bitcoin Core 22
169	#[serde(default)]
170	pub addresses: Vec<Address<NetworkUnchecked>>,
171	// Added in Bitcoin Core 22
172	#[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/// Result from the `submitpackage` RPC call.
198#[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/// Per-transaction result from the `submitpackage` RPC call.
206#[derive(Clone, Debug, Deserialize)]
207pub struct SubmitPackageTxResult {
208	pub txid: Txid,
209	pub error: Option<String>,
210}
211
212/// Shorthand for converting a variable into a serde_json::Value.
213fn 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
220/// Shorthand for converting an Option into an Option<serde_json::Value>.
221fn 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
231/// Handle default values in the argument list
232///
233/// Substitute `Value::Null`s with corresponding values from `defaults` table,
234/// except when they are trailing, in which case just skip them altogether
235/// in returned list.
236///
237/// Note, that `defaults` corresponds to the last elements of `args`.
238///
239/// ```norust
240/// arg1 arg2 arg3 arg4
241///           def1 def2
242/// ```
243///
244/// Elements of `args` without corresponding `defaults` value, won't
245/// be substituted, because they are required.
246fn 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	// Pass over the optional arguments in backwards order, filling in defaults after the first
253	// non-null optional argument has been observed.
254	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
279/// Shorthand for `serde_json::Value::Null`.
280fn null() -> serde_json::Value {
281	serde_json::Value::Null
282}
283
284pub trait BitcoinRpcErrorExt: Borrow<Error> {
285	/// Whether this error indicates that the tx was not found.
286	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	/// Whether this error indicates that the tx is already in the utxo set.
295	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	/// Get the transaction currently spending a given outpoint from the mempool.
378	///
379	/// Returns None if the outpoint is not being spent by any mempool transaction.
380	fn get_mempool_spending_tx(
381		&self,
382		outpoint: bitcoin::OutPoint,
383	) -> Result<Option<Txid>, Error> {
384		// Get all mempool txids
385		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	/// Estimate the effective feerate of a mempool transaction.
399	///
400	/// Returns the effective feerate considering ancestors and CPFP from direct descendants.
401	/// Returns None if the transaction is not in the mempool.
402	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		// Start with this tx's ancestor fee rate
418		let mut feerate = entry_feerate(&entry)?;
419
420		// Check direct descendants - if any has better ancestor rate, use that (CPFP)
421		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
433/// Effective feerate for a mempool entry given its ancestor fee total and
434/// ancestor package size (in vbytes, as returned by `getmempoolentry`).
435/// Returns `None` if the size is zero or the math overflows.
436fn 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
441/// Creates a bitcoind RPC client, optionally routing through a SOCKS5 proxy.
442///
443/// When no proxy is set, the standard transport is used.
444/// When a proxy is set, a ureq-based transport routes traffic through the SOCKS5 proxy.
445pub 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	// Regression: a previous implementation computed
468	// `sat * (250 / ancestor_size)` due to integer-division precedence,
469	// returning feerate 0 for any tx with `ancestor_size > 250` vbytes.
470
471	#[test]
472	fn ancestor_feerate_above_250_vbytes_is_nonzero() {
473		// 1000 vbytes is well above the buggy threshold.
474		// sat/kwu = ceil(10_000 * 1000 / (1000 * 4)) = 2_500
475		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		// 251 vbytes - one above where the old code started returning 0.
482		// sat/kwu = ceil(10_000 * 1000 / 1004) = ceil(9960.16) = 9_961
483		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		// 100 vbytes - old code gave sat * 2 instead of sat * 2.5 (20% off).
490		// sat/kwu = ceil(1_000 * 1000 / 400) = 2_500
491		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}