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#[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
59/// A module used for serde serialization of bytes in hexadecimal format.
60///
61/// The module is compatible with the serde attribute.
62mod 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
95/// deserialize_hex_array_opt deserializes a vector of hex-encoded byte arrays.
96fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
97where
98	D: serde::Deserializer<'de>,
99{
100	//TODO(stevenroose) Revisit when issue is fixed:
101	// https://github.com/serde-rs/serde/issues/723
102
103	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	/// The raw scriptSig in case of a coinbase tx.
124	#[serde(default, with = "serde_hex::opt")]
125	pub coinbase: Option<Vec<u8>>,
126	/// Not provided for coinbase txs.
127	pub txid: Option<Txid>,
128	/// Not provided for coinbase txs.
129	pub vout: Option<u32>,
130	/// The scriptSig in case of a non-coinbase tx.
131	pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
132	/// Not provided for coinbase txs.
133	#[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	// Deprecated in Bitcoin Core 22
173	#[serde(default)]
174	pub addresses: Vec<Address<NetworkUnchecked>>,
175	// Added in Bitcoin Core 22
176	#[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/// Result from the `submitpackage` RPC call.
202#[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/// Per-transaction result from the `submitpackage` RPC call.
210#[derive(Clone, Debug, Deserialize)]
211pub struct SubmitPackageTxResult {
212	pub txid: Txid,
213	pub error: Option<String>,
214}
215
216/// Shorthand for converting a variable into a serde_json::Value.
217fn 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
224/// Shorthand for converting an Option into an Option<serde_json::Value>.
225fn 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
235/// Handle default values in the argument list
236///
237/// Substitute `Value::Null`s with corresponding values from `defaults` table,
238/// except when they are trailing, in which case just skip them altogether
239/// in returned list.
240///
241/// Note, that `defaults` corresponds to the last elements of `args`.
242///
243/// ```norust
244/// arg1 arg2 arg3 arg4
245///           def1 def2
246/// ```
247///
248/// Elements of `args` without corresponding `defaults` value, won't
249/// be substituted, because they are required.
250fn 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	// Pass over the optional arguments in backwards order, filling in defaults after the first
257	// non-null optional argument has been observed.
258	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
283/// Shorthand for `serde_json::Value::Null`.
284fn null() -> serde_json::Value {
285	serde_json::Value::Null
286}
287
288pub trait BitcoinRpcErrorExt: Borrow<Error> {
289	/// Whether this error indicates that the tx was not found.
290	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	/// Whether this error indicates that the tx is already in the utxo set.
299	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	/// Get the transaction currently spending a given outpoint from the mempool.
382	///
383	/// Returns None if the outpoint is not being spent by any mempool transaction.
384	fn get_mempool_spending_tx(
385		&self,
386		outpoint: bitcoin::OutPoint,
387	) -> Result<Option<Txid>, Error> {
388		// Get all mempool txids
389		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	/// Estimate the effective feerate of a mempool transaction.
403	///
404	/// Returns the effective feerate considering ancestors and CPFP from direct descendants.
405	/// Returns None if the transaction is not in the mempool.
406	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		// Start with this tx's ancestor fee rate
422		let mut feerate = entry_feerate(&entry)?;
423
424		// Check direct descendants - if any has better ancestor rate, use that (CPFP)
425		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
437/// Effective feerate for a mempool entry given its ancestor fee total and
438/// ancestor package size (in vbytes, as returned by `getmempoolentry`).
439/// Returns `None` if the size is zero or the math overflows.
440fn 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
445/// Creates a bitcoind RPC client, optionally routing through a SOCKS5 proxy.
446///
447/// When no proxy is set, the standard transport is used.
448/// When a proxy is set, a ureq-based transport routes traffic through the SOCKS5 proxy.
449pub 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	// Regression: a previous implementation computed
472	// `sat * (250 / ancestor_size)` due to integer-division precedence,
473	// returning feerate 0 for any tx with `ancestor_size > 250` vbytes.
474
475	#[test]
476	fn ancestor_feerate_above_250_vbytes_is_nonzero() {
477		// 1000 vbytes is well above the buggy threshold.
478		// sat/kwu = ceil(10_000 * 1000 / (1000 * 4)) = 2_500
479		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		// 251 vbytes - one above where the old code started returning 0.
486		// sat/kwu = ceil(10_000 * 1000 / 1004) = ceil(9960.16) = 9_961
487		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		// 100 vbytes - old code gave sat * 2 instead of sat * 2.5 (20% off).
494		// sat/kwu = ceil(1_000 * 1000 / 400) = 2_500
495		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}