bitcoin_ext/
rpc.rs

1
2pub use bdk_bitcoind_rpc::bitcoincore_rpc::{self, json, jsonrpc, Auth, Client, Error, RpcApi};
3
4use std::borrow::Borrow;
5
6use bdk_bitcoind_rpc::bitcoincore_rpc::Result as RpcResult;
7use bitcoin::address::NetworkUnchecked;
8use bitcoin::hex::FromHex;
9use bitcoin::{Address, Amount, Transaction};
10use serde::{self, Deserialize, Serialize};
11use serde::de::Error as SerdeError;
12
13use crate::{BlockHeight, BlockRef, TxStatus, DEEPLY_CONFIRMED};
14
15/// Error code for RPC_VERIFY_ALREADY_IN_UTXO_SET.
16const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
17
18/// Error code for RPC_INVALID_ADDRESS_OR_KEY, used when a tx is not found.
19const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
20
21/// Clonable bitcoind rpc client.
22#[derive(Debug)]
23pub struct BitcoinRpcClient {
24	client: Client,
25	url: String,
26	auth: Auth,
27}
28
29impl BitcoinRpcClient {
30	pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
31		Ok(BitcoinRpcClient {
32			client: Client::new(url, auth.clone())?,
33			url: url.to_owned(),
34			auth: auth,
35		})
36	}
37}
38
39impl RpcApi for BitcoinRpcClient {
40	fn call<T: for<'a> serde::de::Deserialize<'a>>(
41		&self, cmd: &str, args: &[serde_json::Value],
42	) -> Result<T, Error> {
43		self.client.call(cmd, args)
44	}
45}
46
47impl Clone for BitcoinRpcClient {
48	fn clone(&self) -> Self {
49		Self::new(&self.url, self.auth.clone()).unwrap()
50	}
51}
52
53/// A module used for serde serialization of bytes in hexadecimal format.
54///
55/// The module is compatible with the serde attribute.
56mod serde_hex {
57	use bitcoin::hex::{DisplayHex, FromHex};
58	use serde::de::Error;
59	use serde::{Deserializer, Serializer};
60
61	pub fn serialize<S: Serializer>(b: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
62		s.serialize_str(&b.to_lower_hex_string())
63	}
64
65	pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
66		let hex_str: String = ::serde::Deserialize::deserialize(d)?;
67		Ok(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?)
68	}
69
70	pub mod opt {
71		use bitcoin::hex::{DisplayHex, FromHex};
72		use serde::de::Error;
73		use serde::{Deserializer, Serializer};
74
75		pub fn serialize<S: Serializer>(b: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
76			match *b {
77				None => s.serialize_none(),
78				Some(ref b) => s.serialize_str(&b.to_lower_hex_string()),
79			}
80		}
81
82		pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
83			let hex_str: String = ::serde::Deserialize::deserialize(d)?;
84			Ok(Some(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?))
85		}
86	}
87}
88
89/// deserialize_hex_array_opt deserializes a vector of hex-encoded byte arrays.
90fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
91where
92	D: serde::Deserializer<'de>,
93{
94	//TODO(stevenroose) Revisit when issue is fixed:
95	// https://github.com/serde-rs/serde/issues/723
96
97	let v: Vec<String> = Vec::deserialize(deserializer)?;
98	let mut res = Vec::new();
99	for h in v.into_iter() {
100		res.push(FromHex::from_hex(&h).map_err(D::Error::custom)?);
101	}
102	Ok(Some(res))
103}
104
105#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
106#[serde(rename_all = "camelCase")]
107pub struct GetRawTransactionResultVinScriptSig {
108	pub asm: String,
109	#[serde(with = "serde_hex")]
110	pub hex: Vec<u8>,
111}
112
113#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct GetRawTransactionResultVin {
116	pub sequence: u32,
117	/// The raw scriptSig in case of a coinbase tx.
118	#[serde(default, with = "serde_hex::opt")]
119	pub coinbase: Option<Vec<u8>>,
120	/// Not provided for coinbase txs.
121	pub txid: Option<bitcoin::Txid>,
122	/// Not provided for coinbase txs.
123	pub vout: Option<u32>,
124	/// The scriptSig in case of a non-coinbase tx.
125	pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
126	/// Not provided for coinbase txs.
127	#[serde(default, deserialize_with = "deserialize_hex_array_opt")]
128	pub txinwitness: Option<Vec<Vec<u8>>>,
129}
130
131#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
132#[serde(rename_all = "camelCase")]
133pub struct GetRawTransactionResultVout {
134	#[serde(with = "bitcoin::amount::serde::as_btc")]
135	pub value: Amount,
136	pub n: u32,
137	pub script_pub_key: GetRawTransactionResultVoutScriptPubKey,
138}
139
140#[allow(non_camel_case_types)]
141#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
142#[serde(rename_all = "lowercase")]
143pub enum ScriptPubkeyType {
144	Nonstandard,
145	Anchor,
146	Pubkey,
147	PubkeyHash,
148	ScriptHash,
149	MultiSig,
150	NullData,
151	Witness_v0_KeyHash,
152	Witness_v0_ScriptHash,
153	Witness_v1_Taproot,
154	Witness_Unknown,
155}
156
157#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
158#[serde(rename_all = "camelCase")]
159pub struct GetRawTransactionResultVoutScriptPubKey {
160	pub asm: String,
161	#[serde(with = "serde_hex")]
162	pub hex: Vec<u8>,
163	pub req_sigs: Option<usize>,
164	#[serde(rename = "type")]
165	pub type_: Option<ScriptPubkeyType>,
166	// Deprecated in Bitcoin Core 22
167	#[serde(default)]
168	pub addresses: Vec<Address<NetworkUnchecked>>,
169	// Added in Bitcoin Core 22
170	#[serde(default)]
171	pub address: Option<Address<NetworkUnchecked>>,
172}
173
174#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
175#[serde(rename_all = "camelCase")]
176pub struct GetRawTransactionResult {
177	#[serde(rename = "in_active_chain")]
178	pub in_active_chain: Option<bool>,
179	#[serde(with = "serde_hex")]
180	pub hex: Vec<u8>,
181	pub txid: bitcoin::Txid,
182	pub hash: bitcoin::Wtxid,
183	pub size: usize,
184	pub vsize: usize,
185	pub version: u32,
186	pub locktime: u32,
187	pub vin: Vec<GetRawTransactionResultVin>,
188	pub vout: Vec<GetRawTransactionResultVout>,
189	pub blockhash: Option<bitcoin::BlockHash>,
190	pub confirmations: Option<u32>,
191	pub time: Option<usize>,
192	pub blocktime: Option<usize>,
193}
194
195/// Shorthand for converting a variable into a serde_json::Value.
196fn into_json<T>(val: T) -> RpcResult<serde_json::Value>
197where
198	T: serde::ser::Serialize,
199{
200	Ok(serde_json::to_value(val)?)
201}
202
203/// Shorthand for converting an Option into an Option<serde_json::Value>.
204fn opt_into_json<T>(opt: Option<T>) -> RpcResult<serde_json::Value>
205where
206	T: serde::ser::Serialize,
207{
208	match opt {
209		Some(val) => Ok(into_json(val)?),
210		None => Ok(serde_json::Value::Null),
211	}
212}
213
214/// Handle default values in the argument list
215///
216/// Substitute `Value::Null`s with corresponding values from `defaults` table,
217/// except when they are trailing, in which case just skip them altogether
218/// in returned list.
219///
220/// Note, that `defaults` corresponds to the last elements of `args`.
221///
222/// ```norust
223/// arg1 arg2 arg3 arg4
224///           def1 def2
225/// ```
226///
227/// Elements of `args` without corresponding `defaults` value, won't
228/// be substituted, because they are required.
229fn handle_defaults<'a, 'b>(
230	args: &'a mut [serde_json::Value],
231	defaults: &'b [serde_json::Value],
232) -> &'a [serde_json::Value] {
233	assert!(args.len() >= defaults.len());
234
235	// Pass over the optional arguments in backwards order, filling in defaults after the first
236	// non-null optional argument has been observed.
237	let mut first_non_null_optional_idx = None;
238	for i in 0..defaults.len() {
239		let args_i = args.len() - 1 - i;
240		let defaults_i = defaults.len() - 1 - i;
241		if args[args_i] == serde_json::Value::Null {
242			if first_non_null_optional_idx.is_some() {
243				if defaults[defaults_i] == serde_json::Value::Null {
244					panic!("Missing `default` for argument idx {}", args_i);
245				}
246				args[args_i] = defaults[defaults_i].clone();
247			}
248		} else if first_non_null_optional_idx.is_none() {
249			first_non_null_optional_idx = Some(args_i);
250		}
251	}
252
253	let required_num = args.len() - defaults.len();
254
255	if let Some(i) = first_non_null_optional_idx {
256		&args[..i + 1]
257	} else {
258		&args[..required_num]
259	}
260}
261
262/// Shorthand for `serde_json::Value::Null`.
263fn null() -> serde_json::Value {
264	serde_json::Value::Null
265}
266
267pub trait BitcoinRpcErrorExt: Borrow<Error> {
268	/// Whether this error indicates that the tx was not found.
269	fn is_not_found(&self) -> bool {
270		if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
271			e.code == RPC_INVALID_ADDRESS_OR_KEY
272		} else {
273			false
274		}
275	}
276
277	/// Whether this error indicates that the tx is already in the utxo set.
278	fn is_in_utxo_set(&self) -> bool {
279		if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
280			e.code == RPC_VERIFY_ALREADY_IN_UTXO_SET
281		} else {
282			false
283		}
284	}
285
286	fn is_already_in_mempool(&self) -> bool {
287		if let Error::JsonRpc(jsonrpc::Error::Rpc(e)) = self.borrow() {
288			e.message.contains("txn-already-in-mempool")
289		} else {
290			false
291		}
292	}
293}
294impl BitcoinRpcErrorExt for Error {}
295
296pub trait BitcoinRpcExt: RpcApi {
297	fn custom_get_raw_transaction_info(
298		&self,
299		txid: &bitcoin::Txid,
300		block_hash: Option<&bitcoin::BlockHash>,
301	) -> RpcResult<Option<GetRawTransactionResult>> {
302		let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?];
303		match self.call("getrawtransaction", handle_defaults(&mut args, &[null()])) {
304			Ok(ret) => Ok(Some(ret)),
305			Err(e) if e.is_not_found() => Ok(None),
306			Err(e) => Err(e),
307		}
308	}
309
310	fn broadcast_tx(&self, tx: &Transaction) -> Result<(), Error> {
311		match self.send_raw_transaction(tx) {
312			Ok(_) => Ok(()),
313			Err(e) if e.is_in_utxo_set() => Ok(()),
314			Err(e) => Err(e),
315		}
316	}
317
318	fn tip(&self) -> Result<BlockRef, Error> {
319		let height = self.get_block_count()?;
320		let hash = self.get_block_hash(height)?;
321		Ok(BlockRef { height: height as BlockHeight, hash })
322	}
323
324	fn deep_tip(&self) -> Result<BlockRef, Error> {
325		let tip = self.get_block_count()?;
326		let height = tip.saturating_sub(DEEPLY_CONFIRMED as u64);
327		let hash = self.get_block_hash(height)?;
328		Ok(BlockRef { height: height as BlockHeight, hash })
329	}
330
331	fn tx_status(&self, txid: &bitcoin::Txid) -> Result<TxStatus, Error> {
332		match self.custom_get_raw_transaction_info(txid, None)? {
333			Some(tx) => match tx.blockhash {
334				Some(hash) => {
335					let block = self.get_block_header_info(&hash)?;
336					if block.confirmations > 0 {
337						Ok(TxStatus::Confirmed(BlockRef { height: block.height as BlockHeight, hash: block.hash }))
338					} else {
339						Ok(TxStatus::Mempool)
340					}
341				},
342				None => Ok(TxStatus::Mempool),
343			},
344			None => Ok(TxStatus::NotFound)
345		}
346	}
347}
348
349impl <T: RpcApi> BitcoinRpcExt for T {}