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
16const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
18
19const RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
21
22#[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
54mod 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
90fn deserialize_hex_array_opt<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<u8>>>, D::Error>
92where
93 D: serde::Deserializer<'de>,
94{
95 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 #[serde(default, with = "serde_hex::opt")]
120 pub coinbase: Option<Vec<u8>>,
121 pub txid: Option<bitcoin::Txid>,
123 pub vout: Option<u32>,
125 pub script_sig: Option<GetRawTransactionResultVinScriptSig>,
127 #[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 #[serde(default)]
169 pub addresses: Vec<Address<NetworkUnchecked>>,
170 #[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#[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#[derive(Clone, Debug, Deserialize)]
206pub struct SubmitPackageTxResult {
207 pub txid: bitcoin::Txid,
208 pub error: Option<String>,
209}
210
211fn 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
219fn 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
230fn 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 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
278fn null() -> serde_json::Value {
280 serde_json::Value::Null
281}
282
283pub trait BitcoinRpcErrorExt: Borrow<Error> {
284 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 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 {}