depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

rpc.rs (22922B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2026, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 //! This is a very simple RPC client designed only for a specific bitcoind version
     17 //! and to use on an secure localhost connection to a trusted node
     18 //!
     19 //! No http format or body length check as we trust the node output
     20 //! No asynchronous request as bitcoind put requests in a queue and process
     21 //! them synchronously and we do not want to fill this queue
     22 //!
     23 //! We only parse the thing we actually use, this reduce memory usage and
     24 //! make our code more compatible with future deprecation
     25 //!
     26 //! bitcoincore RPC documentation: <https://bitcoincore.org/en/doc/29.0.0/>
     27 
     28 use std::{
     29     fmt::Debug,
     30     io::{ErrorKind, IoSlice, Write as _},
     31     net::SocketAddr,
     32     path::PathBuf,
     33     str::FromStr as _,
     34     time::Duration,
     35 };
     36 
     37 use base64::{Engine, prelude::BASE64_STANDARD};
     38 use bitcoin::{Address, Amount, BlockHash, SignedAmount, Txid, address::NetworkUnchecked};
     39 use compact_str::{CompactString, format_compact};
     40 use serde_json::{Value, json};
     41 use tokio::{
     42     io::{self, AsyncReadExt, AsyncWriteExt as _},
     43     net::TcpStream,
     44     time::timeout,
     45 };
     46 use tracing::trace;
     47 
     48 use crate::config::{RpcAuth, RpcCfg};
     49 
     50 const RPC_VERSION: &str = "2.0";
     51 
     52 #[derive(Debug, serde::Serialize)]
     53 struct RpcRequest<'a, T: serde::Serialize> {
     54     jsonrpc: &'static str,
     55     method: &'a str,
     56     id: u64,
     57     params: &'a T,
     58 }
     59 
     60 #[derive(Debug, serde::Deserialize)]
     61 struct RpcResponse<T> {
     62     result: Option<T>,
     63     error: Option<RpcError>,
     64     id: u64,
     65 }
     66 
     67 #[derive(Debug, serde::Deserialize)]
     68 struct RpcError {
     69     code: ErrorCode,
     70     message: CompactString,
     71 }
     72 
     73 #[derive(Debug, thiserror::Error)]
     74 pub enum Error {
     75     #[error("IO: {0:?}")]
     76     Transport(#[from] std::io::Error),
     77     #[error("RPC {method}: {code:?} - {msg}")]
     78     RPC {
     79         method: CompactString,
     80         code: ErrorCode,
     81         msg: CompactString,
     82     },
     83     #[error("BTC: {0}")]
     84     Bitcoin(CompactString),
     85     #[error("JSON {method}: {e}")]
     86     Json {
     87         method: CompactString,
     88         e: serde_json::Error,
     89     },
     90     #[error("failed to read cookie file at '{0}': {1}")]
     91     Cookie(String, ErrorKind),
     92     #[error("failed to connect {0}: {1}")]
     93     Tcp(SocketAddr, ErrorKind),
     94     #[error("failed to connect {0}: {1}")]
     95     Elapsed(SocketAddr, tokio::time::error::Elapsed),
     96     #[error("Null rpc, no result or error")]
     97     Null,
     98 }
     99 
    100 pub type Result<T> = std::result::Result<T, Error>;
    101 
    102 const EMPTY: [(); 0] = [];
    103 
    104 fn expect_null(result: Result<()>) -> Result<()> {
    105     match result {
    106         Err(Error::Null) => Ok(()),
    107         i => i,
    108     }
    109 }
    110 
    111 #[derive(Debug)]
    112 pub struct JsonSocket {
    113     sock: TcpStream,
    114     buf: Vec<u8>,
    115 }
    116 
    117 impl JsonSocket {
    118     async fn call<T>(
    119         &mut self,
    120         path: &str,
    121         method: &str,
    122         cookie: &str,
    123         body: &impl serde::Serialize,
    124     ) -> Result<T>
    125     where
    126         T: serde::de::DeserializeOwned,
    127     {
    128         let buf = &mut self.buf;
    129         let sock = &mut self.sock;
    130         buf.clear();
    131         serde_json::to_writer(&mut *buf, body).map_err(|e| Error::Json {
    132             method: method.into(),
    133             e,
    134         })?;
    135         let body_len = buf.len();
    136 
    137         // Write HTTP request
    138         writeln!(buf, "POST {path} HTTP/1.1\r")?;
    139         // Write headers
    140         writeln!(buf, "Accept: application/json-rpc\r")?;
    141         writeln!(buf, "Authorization: {cookie}\r")?;
    142         writeln!(buf, "Content-Type: application/json-rpc\r")?;
    143         writeln!(buf, "Content-Length: {body_len}\r")?;
    144         // Write separator
    145         writeln!(buf, "\r")?;
    146         let (body, head) = buf.split_at(body_len);
    147         let mut vectors = [IoSlice::new(head), IoSlice::new(body)];
    148         let mut vectors = vectors.as_mut_slice();
    149         while !vectors.is_empty() {
    150             let written = sock.write_vectored(vectors).await?;
    151             IoSlice::advance_slices(&mut vectors, written);
    152         }
    153         sock.flush().await?;
    154 
    155         // Skip response
    156         buf.clear();
    157         let header_pos = loop {
    158             let amount = sock.read_buf(buf).await?;
    159             if amount == 0 {
    160                 return Err(Error::Transport(io::Error::new(
    161                     io::ErrorKind::UnexpectedEof,
    162                     "End of file reached unexpectedly",
    163                 )));
    164             }
    165             if let Some(header_pos) = buf.windows(4).position(|w| w == b"\r\n\r\n")
    166                 && buf.ends_with(b"\n")
    167             {
    168                 break header_pos;
    169             }
    170         };
    171         // Read body
    172         let response = serde_json::from_slice(&buf[header_pos + 4..]).map_err(|e| Error::Json {
    173             method: method.into(),
    174             e,
    175         })?;
    176         Ok(response)
    177     }
    178 }
    179 
    180 /// Bitcoin RPC connection
    181 pub struct Rpc {
    182     socket: JsonSocket,
    183     cookie: CompactString,
    184     id: u64,
    185 }
    186 
    187 impl Rpc {
    188     /// Start a RPC connection
    189     pub async fn new(cfg: &RpcCfg) -> std::result::Result<Self, Error> {
    190         let token = match &cfg.auth {
    191             RpcAuth::Basic(s) => s.as_bytes().to_vec(),
    192             RpcAuth::Cookie(path) => match std::fs::read(path) {
    193                 Ok(content) => content,
    194                 Err(e) if e.kind() == ErrorKind::IsADirectory => {
    195                     let path = PathBuf::from_str(path).unwrap().join(".cookie");
    196                     std::fs::read(&path)
    197                         .map_err(|e| Error::Cookie(path.to_string_lossy().to_string(), e.kind()))?
    198                 }
    199                 Err(e) => return Err(Error::Cookie(path.clone(), e.kind())),
    200             },
    201         };
    202         // Open connection
    203         let sock = timeout(Duration::from_secs(5), TcpStream::connect(&cfg.addr))
    204             .await
    205             .map_err(|e| Error::Elapsed(cfg.addr, e))?
    206             .map_err(|e| Error::Tcp(cfg.addr, e.kind()))?;
    207 
    208         sock.set_nodelay(true).ok();
    209 
    210         Ok(Self {
    211             id: 0,
    212             cookie: format_compact!("Basic {}", BASE64_STANDARD.encode(&token)),
    213             socket: JsonSocket {
    214                 sock,
    215                 buf: Vec::with_capacity(16 * 1024),
    216             },
    217         })
    218     }
    219 
    220     /// Set current wallet name
    221     pub fn wallet(&mut self, wallet: &str) -> WalletRpc<'_> {
    222         WalletRpc {
    223             rpc: self,
    224             path: format_compact!("/wallet/{wallet}"),
    225         }
    226     }
    227 
    228     async fn call_rpc<T>(
    229         &mut self,
    230         path: &str,
    231         method: &str,
    232         params: &(impl serde::Serialize + Debug),
    233     ) -> Result<T>
    234     where
    235         T: serde::de::DeserializeOwned + Debug,
    236     {
    237         trace!("RPC > {method} {params:?}");
    238         let request = RpcRequest {
    239             jsonrpc: RPC_VERSION,
    240             method,
    241             id: self.id,
    242             params,
    243         };
    244         let response: RpcResponse<T> = self
    245             .socket
    246             .call(path, method, &self.cookie, &request)
    247             .await?;
    248         trace!("RPC < {response:?}");
    249         let RpcResponse { result, error, id } = response;
    250         assert_eq!(self.id, id);
    251         self.id += 1;
    252         if let Some(ok) = result {
    253             Ok(ok)
    254         } else {
    255             Err(match error {
    256                 Some(err) => Error::RPC {
    257                     method: method.into(),
    258                     code: err.code,
    259                     msg: err.message,
    260                 },
    261                 None => Error::Null,
    262             })
    263         }
    264     }
    265 }
    266 
    267 impl RpcApi for Rpc {
    268     async fn call<
    269         T: serde::de::DeserializeOwned + Debug + Send,
    270         A: serde::Serialize + Debug + Send,
    271     >(
    272         &mut self,
    273         method: &str,
    274         params: &A,
    275     ) -> Result<T> {
    276         self.call_rpc("/", method, params).await
    277     }
    278 }
    279 
    280 pub struct WalletRpc<'a> {
    281     rpc: &'a mut Rpc,
    282     path: CompactString,
    283 }
    284 
    285 impl<'a> RpcApi for WalletRpc<'a> {
    286     async fn call<
    287         T: serde::de::DeserializeOwned + Debug + Send,
    288         A: serde::Serialize + Debug + Send,
    289     >(
    290         &mut self,
    291         method: &str,
    292         params: &A,
    293     ) -> Result<T> {
    294         self.rpc.call_rpc(&self.path, method, params).await
    295     }
    296 }
    297 
    298 #[allow(async_fn_in_trait)]
    299 pub trait RpcApi {
    300     async fn call<
    301         T: serde::de::DeserializeOwned + Debug + Send,
    302         A: serde::Serialize + Debug + Send,
    303     >(
    304         &mut self,
    305         method: &str,
    306         params: &A,
    307     ) -> Result<T>;
    308 
    309     /* ----- Wallet management ----- */
    310 
    311     /// Create encrypted native bitcoin wallet
    312     async fn create_wallet(&mut self, name: &str, passwd: &str) -> Result<Wallet> {
    313         self.call("createwallet", &(name, (), (), passwd, (), true))
    314             .await
    315     }
    316 
    317     /// Load existing wallet
    318     async fn load_wallet(&mut self, name: &str) -> Result<Wallet> {
    319         match self.call("loadwallet", &[name]).await {
    320             Err(Error::RPC {
    321                 code: ErrorCode::RpcWalletAlreadyLoaded,
    322                 ..
    323             }) => Ok(Wallet {
    324                 name: name.to_string(),
    325             }),
    326             it => it,
    327         }
    328     }
    329 
    330     /// Unlock loaded wallet
    331     async fn unlock_wallet(&mut self, passwd: &str) -> Result<()> {
    332         expect_null(self.call("walletpassphrase", &(passwd, 100000000)).await)
    333     }
    334 
    335     /* ----- Wallet utils ----- */
    336 
    337     /// Generate a new address for the current wallet
    338     async fn gen_addr(&mut self) -> Result<Address> {
    339         Ok(self
    340             .call::<Address<NetworkUnchecked>, _>("getnewaddress", &EMPTY)
    341             .await?
    342             .assume_checked())
    343     }
    344 
    345     /// Get current balance amount
    346     async fn get_balance(&mut self) -> Result<Amount> {
    347         let btc: f64 = self.call("getbalance", &EMPTY).await?;
    348         Ok(Amount::from_btc(btc).unwrap())
    349     }
    350 
    351     /// Get current balance amount
    352     async fn addr_info(&mut self, addr: &Address) -> Result<AddressInfo> {
    353         self.call("getaddressinfo", &[addr]).await
    354     }
    355 
    356     /* ----- Mining ----- */
    357 
    358     /// Mine a certain amount of block to profit a given address
    359     async fn mine(&mut self, nb: u32, address: &Address) -> Result<Vec<BlockHash>> {
    360         self.call("generatetoaddress", &(nb, address)).await
    361     }
    362 
    363     /// Wait for a block height
    364     async fn wait_for_block_height(&mut self, height: u32) -> Result<Nothing> {
    365         self.call("waitforblockheight", &[height]).await
    366     }
    367 
    368     /* ----- Getter ----- */
    369 
    370     /// Get blockchain info
    371     async fn get_blockchain_info(&mut self) -> Result<BlockchainInfo> {
    372         self.call("getblockchaininfo", &EMPTY).await
    373     }
    374 
    375     /// Get mempool info
    376     async fn get_mempool_info(&mut self) -> Result<MemPoolInfo> {
    377         self.call("getmempoolinfo", &EMPTY).await
    378     }
    379 
    380     /// Get mempool entry
    381     async fn get_mempool_entry(&mut self, id: &Txid) -> Result<MemPoolEntry> {
    382         self.call("getmempoolentry", &[id]).await
    383     }
    384 
    385     /// Get blockchain info
    386     async fn get_wallet_info(&mut self) -> Result<WalletInfo> {
    387         self.call("getwalletinfo", &EMPTY).await
    388     }
    389 
    390     /// Get chain tips
    391     async fn get_chain_tips(&mut self) -> Result<Vec<ChainTips>> {
    392         self.call("getchaintips", &EMPTY).await
    393     }
    394 
    395     /// Get wallet transaction info from id
    396     async fn get_tx(&mut self, id: &Txid) -> Result<Transaction> {
    397         self.call("gettransaction", &(id, (), true)).await
    398     }
    399 
    400     /// Get transaction inputs and outputs
    401     async fn get_input_output(&mut self, id: &Txid) -> Result<InputOutput> {
    402         self.call("getrawtransaction", &(id, true)).await
    403     }
    404 
    405     /// Get genesis block hash
    406     async fn get_genesis(&mut self) -> Result<BlockHash> {
    407         self.call("getblockhash", &[0]).await
    408     }
    409 
    410     /* ----- Transactions ----- */
    411 
    412     /// Send bitcoin transaction
    413     async fn send(
    414         &mut self,
    415         to: &Address,
    416         amount: Amount,
    417         data: Option<&[u8]>,
    418         subtract_fee: bool,
    419     ) -> Result<Txid> {
    420         self.send_custom([], [(to, amount)], data, subtract_fee)
    421             .await
    422             .map(|it| it.txid)
    423     }
    424 
    425     /// Send bitcoin transaction with multiple recipients
    426     async fn send_many<'a>(
    427         &mut self,
    428         to: impl IntoIterator<Item = (&'a Address, Amount)>,
    429     ) -> Result<Txid> {
    430         self.send_custom([], to, None, false)
    431             .await
    432             .map(|it| it.txid)
    433     }
    434 
    435     async fn send_custom<'a>(
    436         &mut self,
    437         from: impl IntoIterator<Item = &'a Txid>,
    438         to: impl IntoIterator<Item = (&'a Address, Amount)>,
    439         data: Option<&[u8]>,
    440         subtract_fee: bool,
    441     ) -> Result<SendResult> {
    442         // We use the experimental 'send' rpc command as it is the only capable to send metadata in a single rpc call
    443         let inputs: Vec<_> = from
    444             .into_iter()
    445             .enumerate()
    446             .map(|(i, id)| json!({"txid": id, "vout": i}))
    447             .collect();
    448         let mut outputs: Vec<Value> = to
    449             .into_iter()
    450             .map(|(addr, amount)| json!({&addr.to_string(): amount.to_btc()}))
    451             .collect();
    452         let nb_outputs = outputs.len();
    453         if let Some(data) = data {
    454             assert!(!data.is_empty(), "No medatata");
    455             assert!(data.len() <= 80, "Max 80 bytes");
    456             outputs.push(json!({ "data".to_string(): hex::encode(data) }));
    457         }
    458         self.call(
    459             "send",
    460             &(
    461                 outputs,
    462                 (),
    463                 (),
    464                 (),
    465                 SendOption {
    466                     add_inputs: true,
    467                     inputs,
    468                     subtract_fee_from_outputs: if subtract_fee {
    469                         (0..nb_outputs).collect()
    470                     } else {
    471                         vec![]
    472                     },
    473                 },
    474             ),
    475         )
    476         .await
    477     }
    478 
    479     /// Bump transaction fees of a wallet debit
    480     async fn bump_fee(&mut self, id: &Txid) -> Result<BumpResult> {
    481         self.call("bumpfee", &[id]).await
    482     }
    483 
    484     /// Abandon a pending transaction
    485     async fn abandon_tx(&mut self, id: &Txid) -> Result<()> {
    486         expect_null(self.call("abandontransaction", &[&id]).await)
    487     }
    488 
    489     /* ----- Watcher ----- */
    490 
    491     /// Block until a new block is mined
    492     async fn wait_for_new_block(&mut self) -> Result<Nothing> {
    493         self.call("waitfornewblock", &[0]).await
    494     }
    495 
    496     /// List new and removed transaction since a block
    497     async fn list_since_block(
    498         &mut self,
    499         hash: Option<&BlockHash>,
    500         confirmation: u32,
    501     ) -> Result<ListSinceBlock> {
    502         self.call(
    503             "listsinceblock",
    504             &(hash, confirmation.max(1), (), true, false),
    505         )
    506         .await
    507     }
    508 
    509     /* ----- Cluster ----- */
    510 
    511     /// Try a connection to a node once
    512     async fn add_node(&mut self, addr: &str) -> Result<()> {
    513         expect_null(self.call("addnode", &(addr, "onetry")).await)
    514     }
    515 
    516     /// Immediately disconnects from the specified peer node.
    517     async fn disconnect_node(&mut self, addr: &str) -> Result<()> {
    518         expect_null(self.call("disconnectnode", &(addr, ())).await)
    519     }
    520 
    521     /* ----- Control ------ */
    522 
    523     /// Request a graceful shutdown
    524     fn stop(&mut self) -> impl Future<Output = Result<String>> {
    525         self.call("stop", &())
    526     }
    527 }
    528 
    529 #[derive(Debug, serde::Deserialize)]
    530 pub struct Wallet {
    531     pub name: String,
    532 }
    533 
    534 #[derive(Clone, Debug, serde::Deserialize)]
    535 pub struct BlockchainInfo {
    536     pub chain: String,
    537     #[serde(rename = "verificationprogress")]
    538     pub verification_progress: f32,
    539     #[serde(rename = "initialblockdownload")]
    540     pub initial_block_download: bool,
    541     pub blocks: u32,
    542     #[serde(rename = "bestblockhash")]
    543     pub best_block_hash: BlockHash,
    544 }
    545 
    546 #[derive(Clone, Debug, serde::Deserialize)]
    547 pub struct MemPoolInfo {
    548     pub size: u32,
    549 }
    550 
    551 #[derive(Clone, Debug, serde::Deserialize)]
    552 pub struct MemPoolEntry {
    553     pub height: u32,
    554 }
    555 
    556 #[derive(Clone, Debug, serde::Deserialize)]
    557 #[serde(untagged)]
    558 pub enum Scanning {
    559     Active { duration: u64, progress: f32 },
    560     Inactive(bool),
    561 }
    562 
    563 #[derive(Clone, Debug, serde::Deserialize)]
    564 pub struct WalletInfo {
    565     pub walletname: String,
    566     pub scanning: Scanning,
    567 }
    568 
    569 #[derive(Debug, serde::Deserialize)]
    570 pub struct BumpResult {
    571     pub txid: Txid,
    572 }
    573 
    574 #[derive(Debug, serde::Serialize)]
    575 pub struct SendOption {
    576     pub add_inputs: bool,
    577     pub inputs: Vec<Value>,
    578     pub subtract_fee_from_outputs: Vec<usize>,
    579 }
    580 
    581 #[derive(Debug, serde::Deserialize)]
    582 pub struct SendResult {
    583     pub txid: Txid,
    584 }
    585 
    586 /// Enum to represent the category of a transaction.
    587 #[derive(Copy, PartialEq, Eq, Clone, Debug, serde::Deserialize)]
    588 #[serde(rename_all = "lowercase")]
    589 pub enum Category {
    590     Send,
    591     Receive,
    592     Generate,
    593     Immature,
    594     Orphan,
    595 }
    596 
    597 #[derive(Debug, serde::Deserialize)]
    598 pub struct TransactionDetail {
    599     pub address: Option<Address<NetworkUnchecked>>,
    600     pub category: Category,
    601     #[serde(with = "bitcoin::amount::serde::as_btc")]
    602     pub amount: SignedAmount,
    603     #[serde(default, with = "bitcoin::amount::serde::as_btc::opt")]
    604     pub fee: Option<SignedAmount>,
    605     /// Only for send transaction
    606     pub abandoned: Option<bool>,
    607 }
    608 
    609 #[derive(Debug, serde::Deserialize)]
    610 pub struct ListTransaction {
    611     pub confirmations: i32,
    612     pub txid: Txid,
    613     pub category: Category,
    614 }
    615 
    616 #[derive(Debug, serde::Deserialize)]
    617 pub struct ListSinceBlock {
    618     pub transactions: Vec<ListTransaction>,
    619     #[serde(default)]
    620     pub removed: Vec<ListTransaction>,
    621     pub lastblock: BlockHash,
    622 }
    623 
    624 #[derive(Debug, serde::Deserialize)]
    625 pub struct VoutScriptPubKey {
    626     pub asm: String,
    627     // nulldata do not have an address
    628     pub address: Option<Address<NetworkUnchecked>>,
    629 }
    630 
    631 #[derive(Debug, serde::Deserialize)]
    632 #[serde(rename_all = "camelCase")]
    633 pub struct Vout {
    634     #[serde(with = "bitcoin::amount::serde::as_btc")]
    635     pub value: Amount,
    636     pub n: u32,
    637     pub script_pub_key: VoutScriptPubKey,
    638 }
    639 
    640 #[derive(Debug, serde::Deserialize)]
    641 pub struct Vin {
    642     /// Not provided for coinbase txs.
    643     pub txid: Option<Txid>,
    644     /// Not provided for coinbase txs.
    645     pub vout: Option<u32>,
    646 }
    647 
    648 #[derive(Debug, serde::Deserialize)]
    649 pub struct InputOutput {
    650     pub vin: Vec<Vin>,
    651     pub vout: Vec<Vout>,
    652 }
    653 
    654 #[derive(Debug, serde::Deserialize)]
    655 pub struct Transaction {
    656     pub confirmations: i32,
    657     pub time: u64,
    658     #[serde(with = "bitcoin::amount::serde::as_btc")]
    659     pub amount: SignedAmount,
    660     #[serde(default, with = "bitcoin::amount::serde::as_btc::opt")]
    661     pub fee: Option<SignedAmount>,
    662     pub replaces_txid: Option<Txid>,
    663     pub replaced_by_txid: Option<Txid>,
    664     pub details: Vec<TransactionDetail>,
    665     pub decoded: InputOutput,
    666 }
    667 
    668 #[derive(Clone, PartialEq, Eq, serde::Deserialize, Debug)]
    669 pub struct ChainTips {
    670     #[serde(rename = "branchlen")]
    671     pub length: usize,
    672     pub status: ChainTipsStatus,
    673 }
    674 
    675 #[derive(Copy, serde::Deserialize, Clone, PartialEq, Eq, Debug)]
    676 #[serde(rename_all = "lowercase")]
    677 pub enum ChainTipsStatus {
    678     Invalid,
    679     #[serde(rename = "headers-only")]
    680     HeadersOnly,
    681     #[serde(rename = "valid-headers")]
    682     ValidHeaders,
    683     #[serde(rename = "valid-fork")]
    684     ValidFork,
    685     Active,
    686 }
    687 
    688 #[derive(Debug, serde::Deserialize)]
    689 pub struct AddressInfo {
    690     pub ismine: bool,
    691     pub iswatchonly: bool,
    692     pub solvable: bool,
    693 }
    694 
    695 #[derive(Debug, serde::Deserialize)]
    696 pub struct Nothing {}
    697 
    698 /// Bitcoin RPC error codes <https://github.com/bitcoin/bitcoin/blob/master/src/rpc/protocol.h>
    699 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde_repr::Deserialize_repr)]
    700 #[repr(i32)]
    701 pub enum ErrorCode {
    702     RpcInvalidRequest = -32600,
    703     RpcMethodNotFound = -32601,
    704     RpcInvalidParams = -32602,
    705     RpcInternalError = -32603,
    706     RpcParseError = -32700,
    707 
    708     /// std::exception thrown in command handling
    709     RpcMiscError = -1,
    710     /// Unexpected type was passed as parameter
    711     RpcTypeError = -3,
    712     /// Invalid address or key
    713     RpcInvalidAddressOrKey = -5,
    714     /// Ran out of memory during operation
    715     RpcOutOfMemory = -7,
    716     /// Invalid, missing or duplicate parameter
    717     RpcInvalidParameter = -8,
    718     /// Database error
    719     RpcDatabaseError = -20,
    720     /// Error parsing or validating structure in raw format
    721     RpcDeserializationError = -22,
    722     /// General error during transaction or block submission
    723     RpcVerifyError = -25,
    724     /// Transaction or block was rejected by network rules
    725     RpcVerifyRejected = -26,
    726     /// Transaction already in chain
    727     RpcVerifyAlreadyInChain = -27,
    728     /// Client still warming up
    729     RpcInWarmup = -28,
    730     /// RPC method is deprecated
    731     RpcMethodDeprecated = -32,
    732     /// Bitcoin is not connected
    733     RpcClientNotConnected = -9,
    734     /// Still downloading initial blocks
    735     RpcClientInInitialDownload = -10,
    736     /// Node is already added
    737     RpcClientNodeAlreadyAdded = -23,
    738     /// Node has not been added before
    739     RpcClientNodeNotAdded = -24,
    740     /// Node to disconnect not found in connected nodes
    741     RpcClientNodeNotConnected = -29,
    742     /// Invalid IP/Subnet
    743     RpcClientInvalidIpOrSubnet = -30,
    744     /// No valid connection manager instance found
    745     RpcClientP2pDisabled = -31,
    746     /// Max number of outbound or block-relay connections already open
    747     RpcClientNodeCapacityReached = -34,
    748     /// No mempool instance found
    749     RpcClientMempoolDisabled = -33,
    750     /// Unspecified problem with wallet (key not found etc.)
    751     RpcWalletError = -4,
    752     /// Not enough funds in wallet or account
    753     RpcWalletInsufficientFunds = -6,
    754     /// Invalid label name
    755     RpcWalletInvalidLabelName = -11,
    756     /// Keypool ran out, call keypoolrefill first
    757     RpcWalletKeypoolRanOut = -12,
    758     /// Enter the wallet passphrase with walletpassphrase first
    759     RpcWalletUnlockNeeded = -13,
    760     /// The wallet passphrase entered was incorrect
    761     RpcWalletPassphraseIncorrect = -14,
    762     /// Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
    763     RpcWalletWrongEncState = -15,
    764     /// Failed to encrypt the wallet
    765     RpcWalletEncryptionFailed = -16,
    766     /// Wallet is already unlocked
    767     RpcWalletAlreadyUnlocked = -17,
    768     /// Invalid wallet specified
    769     RpcWalletNotFound = -18,
    770     /// No wallet specified (error when there are multiple wallets loaded)
    771     RpcWalletNotSpecified = -19,
    772     /// This same wallet is already loaded
    773     RpcWalletAlreadyLoaded = -35,
    774     /// There is already a wallet with the same name
    775     RpcWalletAlreadyExists = -36,
    776     /// Server is in safe mode, and command is not allowed in safe mode
    777     RpcForbiddenBySafeMode = -2,
    778 }