bitcoin_ext/
rpc.rs

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