depolymerization

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

rpc.rs (22894B)


      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 pub trait RpcApi {
    299     async fn call<
    300         T: serde::de::DeserializeOwned + Debug + Send,
    301         A: serde::Serialize + Debug + Send,
    302     >(
    303         &mut self,
    304         method: &str,
    305         params: &A,
    306     ) -> Result<T>;
    307 
    308     /* ----- Wallet management ----- */
    309 
    310     /// Create encrypted native bitcoin wallet
    311     async fn create_wallet(&mut self, name: &str, passwd: &str) -> Result<Wallet> {
    312         self.call("createwallet", &(name, (), (), passwd, (), true))
    313             .await
    314     }
    315 
    316     /// Load existing wallet
    317     async fn load_wallet(&mut self, name: &str) -> Result<Wallet> {
    318         match self.call("loadwallet", &[name]).await {
    319             Err(Error::RPC {
    320                 code: ErrorCode::RpcWalletAlreadyLoaded,
    321                 ..
    322             }) => Ok(Wallet {
    323                 name: name.to_string(),
    324             }),
    325             it => it,
    326         }
    327     }
    328 
    329     /// Unlock loaded wallet
    330     async fn unlock_wallet(&mut self, passwd: &str) -> Result<()> {
    331         expect_null(self.call("walletpassphrase", &(passwd, 100000000)).await)
    332     }
    333 
    334     /* ----- Wallet utils ----- */
    335 
    336     /// Generate a new address for the current wallet
    337     async fn gen_addr(&mut self) -> Result<Address> {
    338         Ok(self
    339             .call::<Address<NetworkUnchecked>, _>("getnewaddress", &EMPTY)
    340             .await?
    341             .assume_checked())
    342     }
    343 
    344     /// Get current balance amount
    345     async fn get_balance(&mut self) -> Result<Amount> {
    346         let btc: f64 = self.call("getbalance", &EMPTY).await?;
    347         Ok(Amount::from_btc(btc).unwrap())
    348     }
    349 
    350     /// Get current balance amount
    351     async fn addr_info(&mut self, addr: &Address) -> Result<AddressInfo> {
    352         self.call("getaddressinfo", &[addr]).await
    353     }
    354 
    355     /* ----- Mining ----- */
    356 
    357     /// Mine a certain amount of block to profit a given address
    358     async fn mine(&mut self, nb: u32, address: &Address) -> Result<Vec<BlockHash>> {
    359         self.call("generatetoaddress", &(nb, address)).await
    360     }
    361 
    362     /// Wait for a block height
    363     async fn wait_for_block_height(&mut self, height: u32) -> Result<Nothing> {
    364         self.call("waitforblockheight", &[height]).await
    365     }
    366 
    367     /* ----- Getter ----- */
    368 
    369     /// Get blockchain info
    370     async fn get_blockchain_info(&mut self) -> Result<BlockchainInfo> {
    371         self.call("getblockchaininfo", &EMPTY).await
    372     }
    373 
    374     /// Get mempool info
    375     async fn get_mempool_info(&mut self) -> Result<MemPoolInfo> {
    376         self.call("getmempoolinfo", &EMPTY).await
    377     }
    378 
    379     /// Get mempool entry
    380     async fn get_mempool_entry(&mut self, id: &Txid) -> Result<MemPoolEntry> {
    381         self.call("getmempoolentry", &[id]).await
    382     }
    383 
    384     /// Get blockchain info
    385     async fn get_wallet_info(&mut self) -> Result<WalletInfo> {
    386         self.call("getwalletinfo", &EMPTY).await
    387     }
    388 
    389     /// Get chain tips
    390     async fn get_chain_tips(&mut self) -> Result<Vec<ChainTips>> {
    391         self.call("getchaintips", &EMPTY).await
    392     }
    393 
    394     /// Get wallet transaction info from id
    395     async fn get_tx(&mut self, id: &Txid) -> Result<Transaction> {
    396         self.call("gettransaction", &(id, (), true)).await
    397     }
    398 
    399     /// Get transaction inputs and outputs
    400     async fn get_input_output(&mut self, id: &Txid) -> Result<InputOutput> {
    401         self.call("getrawtransaction", &(id, true)).await
    402     }
    403 
    404     /// Get genesis block hash
    405     async fn get_genesis(&mut self) -> Result<BlockHash> {
    406         self.call("getblockhash", &[0]).await
    407     }
    408 
    409     /* ----- Transactions ----- */
    410 
    411     /// Send bitcoin transaction
    412     async fn send(
    413         &mut self,
    414         to: &Address,
    415         amount: Amount,
    416         data: Option<&[u8]>,
    417         subtract_fee: bool,
    418     ) -> Result<Txid> {
    419         self.send_custom([], [(to, amount)], data, subtract_fee)
    420             .await
    421             .map(|it| it.txid)
    422     }
    423 
    424     /// Send bitcoin transaction with multiple recipients
    425     async fn send_many<'a>(
    426         &mut self,
    427         to: impl IntoIterator<Item = (&'a Address, Amount)>,
    428     ) -> Result<Txid> {
    429         self.send_custom([], to, None, false)
    430             .await
    431             .map(|it| it.txid)
    432     }
    433 
    434     async fn send_custom<'a>(
    435         &mut self,
    436         from: impl IntoIterator<Item = &'a Txid>,
    437         to: impl IntoIterator<Item = (&'a Address, Amount)>,
    438         data: Option<&[u8]>,
    439         subtract_fee: bool,
    440     ) -> Result<SendResult> {
    441         // We use the experimental 'send' rpc command as it is the only capable to send metadata in a single rpc call
    442         let inputs: Vec<_> = from
    443             .into_iter()
    444             .enumerate()
    445             .map(|(i, id)| json!({"txid": id, "vout": i}))
    446             .collect();
    447         let mut outputs: Vec<Value> = to
    448             .into_iter()
    449             .map(|(addr, amount)| json!({&addr.to_string(): amount.to_btc()}))
    450             .collect();
    451         let nb_outputs = outputs.len();
    452         if let Some(data) = data {
    453             assert!(!data.is_empty(), "No medatata");
    454             assert!(data.len() <= 80, "Max 80 bytes");
    455             outputs.push(json!({ "data".to_string(): hex::encode(data) }));
    456         }
    457         self.call(
    458             "send",
    459             &(
    460                 outputs,
    461                 (),
    462                 (),
    463                 (),
    464                 SendOption {
    465                     add_inputs: true,
    466                     inputs,
    467                     subtract_fee_from_outputs: if subtract_fee {
    468                         (0..nb_outputs).collect()
    469                     } else {
    470                         vec![]
    471                     },
    472                 },
    473             ),
    474         )
    475         .await
    476     }
    477 
    478     /// Bump transaction fees of a wallet debit
    479     async fn bump_fee(&mut self, id: &Txid) -> Result<BumpResult> {
    480         self.call("bumpfee", &[id]).await
    481     }
    482 
    483     /// Abandon a pending transaction
    484     async fn abandon_tx(&mut self, id: &Txid) -> Result<()> {
    485         expect_null(self.call("abandontransaction", &[&id]).await)
    486     }
    487 
    488     /* ----- Watcher ----- */
    489 
    490     /// Block until a new block is mined
    491     async fn wait_for_new_block(&mut self) -> Result<Nothing> {
    492         self.call("waitfornewblock", &[0]).await
    493     }
    494 
    495     /// List new and removed transaction since a block
    496     async fn list_since_block(
    497         &mut self,
    498         hash: Option<&BlockHash>,
    499         confirmation: u32,
    500     ) -> Result<ListSinceBlock> {
    501         self.call(
    502             "listsinceblock",
    503             &(hash, confirmation.max(1), (), true, false),
    504         )
    505         .await
    506     }
    507 
    508     /* ----- Cluster ----- */
    509 
    510     /// Try a connection to a node once
    511     async fn add_node(&mut self, addr: &str) -> Result<()> {
    512         expect_null(self.call("addnode", &(addr, "onetry")).await)
    513     }
    514 
    515     /// Immediately disconnects from the specified peer node.
    516     async fn disconnect_node(&mut self, addr: &str) -> Result<()> {
    517         expect_null(self.call("disconnectnode", &(addr, ())).await)
    518     }
    519 
    520     /* ----- Control ------ */
    521 
    522     /// Request a graceful shutdown
    523     fn stop(&mut self) -> impl Future<Output = Result<String>> {
    524         self.call("stop", &())
    525     }
    526 }
    527 
    528 #[derive(Debug, serde::Deserialize)]
    529 pub struct Wallet {
    530     pub name: String,
    531 }
    532 
    533 #[derive(Clone, Debug, serde::Deserialize)]
    534 pub struct BlockchainInfo {
    535     pub chain: String,
    536     #[serde(rename = "verificationprogress")]
    537     pub verification_progress: f32,
    538     #[serde(rename = "initialblockdownload")]
    539     pub initial_block_download: bool,
    540     pub blocks: u32,
    541     #[serde(rename = "bestblockhash")]
    542     pub best_block_hash: BlockHash,
    543 }
    544 
    545 #[derive(Clone, Debug, serde::Deserialize)]
    546 pub struct MemPoolInfo {
    547     pub size: u32,
    548 }
    549 
    550 #[derive(Clone, Debug, serde::Deserialize)]
    551 pub struct MemPoolEntry {
    552     pub height: u32,
    553 }
    554 
    555 #[derive(Clone, Debug, serde::Deserialize)]
    556 #[serde(untagged)]
    557 pub enum Scanning {
    558     Active { duration: u64, progress: f32 },
    559     Inactive(bool),
    560 }
    561 
    562 #[derive(Clone, Debug, serde::Deserialize)]
    563 pub struct WalletInfo {
    564     pub walletname: String,
    565     pub scanning: Scanning,
    566 }
    567 
    568 #[derive(Debug, serde::Deserialize)]
    569 pub struct BumpResult {
    570     pub txid: Txid,
    571 }
    572 
    573 #[derive(Debug, serde::Serialize)]
    574 pub struct SendOption {
    575     pub add_inputs: bool,
    576     pub inputs: Vec<Value>,
    577     pub subtract_fee_from_outputs: Vec<usize>,
    578 }
    579 
    580 #[derive(Debug, serde::Deserialize)]
    581 pub struct SendResult {
    582     pub txid: Txid,
    583 }
    584 
    585 /// Enum to represent the category of a transaction.
    586 #[derive(Copy, PartialEq, Eq, Clone, Debug, serde::Deserialize)]
    587 #[serde(rename_all = "lowercase")]
    588 pub enum Category {
    589     Send,
    590     Receive,
    591     Generate,
    592     Immature,
    593     Orphan,
    594 }
    595 
    596 #[derive(Debug, serde::Deserialize)]
    597 pub struct TransactionDetail {
    598     pub address: Option<Address<NetworkUnchecked>>,
    599     pub category: Category,
    600     #[serde(with = "bitcoin::amount::serde::as_btc")]
    601     pub amount: SignedAmount,
    602     #[serde(default, with = "bitcoin::amount::serde::as_btc::opt")]
    603     pub fee: Option<SignedAmount>,
    604     /// Only for send transaction
    605     pub abandoned: Option<bool>,
    606 }
    607 
    608 #[derive(Debug, serde::Deserialize)]
    609 pub struct ListTransaction {
    610     pub confirmations: i32,
    611     pub txid: Txid,
    612     pub category: Category,
    613 }
    614 
    615 #[derive(Debug, serde::Deserialize)]
    616 pub struct ListSinceBlock {
    617     pub transactions: Vec<ListTransaction>,
    618     #[serde(default)]
    619     pub removed: Vec<ListTransaction>,
    620     pub lastblock: BlockHash,
    621 }
    622 
    623 #[derive(Debug, serde::Deserialize)]
    624 pub struct VoutScriptPubKey {
    625     pub asm: String,
    626     // nulldata do not have an address
    627     pub address: Option<Address<NetworkUnchecked>>,
    628 }
    629 
    630 #[derive(Debug, serde::Deserialize)]
    631 #[serde(rename_all = "camelCase")]
    632 pub struct Vout {
    633     #[serde(with = "bitcoin::amount::serde::as_btc")]
    634     pub value: Amount,
    635     pub n: u32,
    636     pub script_pub_key: VoutScriptPubKey,
    637 }
    638 
    639 #[derive(Debug, serde::Deserialize)]
    640 pub struct Vin {
    641     /// Not provided for coinbase txs.
    642     pub txid: Option<Txid>,
    643     /// Not provided for coinbase txs.
    644     pub vout: Option<u32>,
    645 }
    646 
    647 #[derive(Debug, serde::Deserialize)]
    648 pub struct InputOutput {
    649     pub vin: Vec<Vin>,
    650     pub vout: Vec<Vout>,
    651 }
    652 
    653 #[derive(Debug, serde::Deserialize)]
    654 pub struct Transaction {
    655     pub confirmations: i32,
    656     pub time: u64,
    657     #[serde(with = "bitcoin::amount::serde::as_btc")]
    658     pub amount: SignedAmount,
    659     #[serde(default, with = "bitcoin::amount::serde::as_btc::opt")]
    660     pub fee: Option<SignedAmount>,
    661     pub replaces_txid: Option<Txid>,
    662     pub replaced_by_txid: Option<Txid>,
    663     pub details: Vec<TransactionDetail>,
    664     pub decoded: InputOutput,
    665 }
    666 
    667 #[derive(Clone, PartialEq, Eq, serde::Deserialize, Debug)]
    668 pub struct ChainTips {
    669     #[serde(rename = "branchlen")]
    670     pub length: usize,
    671     pub status: ChainTipsStatus,
    672 }
    673 
    674 #[derive(Copy, serde::Deserialize, Clone, PartialEq, Eq, Debug)]
    675 #[serde(rename_all = "lowercase")]
    676 pub enum ChainTipsStatus {
    677     Invalid,
    678     #[serde(rename = "headers-only")]
    679     HeadersOnly,
    680     #[serde(rename = "valid-headers")]
    681     ValidHeaders,
    682     #[serde(rename = "valid-fork")]
    683     ValidFork,
    684     Active,
    685 }
    686 
    687 #[derive(Debug, serde::Deserialize)]
    688 pub struct AddressInfo {
    689     pub ismine: bool,
    690     pub iswatchonly: bool,
    691     pub solvable: bool,
    692 }
    693 
    694 #[derive(Debug, serde::Deserialize)]
    695 pub struct Nothing {}
    696 
    697 /// Bitcoin RPC error codes <https://github.com/bitcoin/bitcoin/blob/master/src/rpc/protocol.h>
    698 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde_repr::Deserialize_repr)]
    699 #[repr(i32)]
    700 pub enum ErrorCode {
    701     RpcInvalidRequest = -32600,
    702     RpcMethodNotFound = -32601,
    703     RpcInvalidParams = -32602,
    704     RpcInternalError = -32603,
    705     RpcParseError = -32700,
    706 
    707     /// std::exception thrown in command handling
    708     RpcMiscError = -1,
    709     /// Unexpected type was passed as parameter
    710     RpcTypeError = -3,
    711     /// Invalid address or key
    712     RpcInvalidAddressOrKey = -5,
    713     /// Ran out of memory during operation
    714     RpcOutOfMemory = -7,
    715     /// Invalid, missing or duplicate parameter
    716     RpcInvalidParameter = -8,
    717     /// Database error
    718     RpcDatabaseError = -20,
    719     /// Error parsing or validating structure in raw format
    720     RpcDeserializationError = -22,
    721     /// General error during transaction or block submission
    722     RpcVerifyError = -25,
    723     /// Transaction or block was rejected by network rules
    724     RpcVerifyRejected = -26,
    725     /// Transaction already in chain
    726     RpcVerifyAlreadyInChain = -27,
    727     /// Client still warming up
    728     RpcInWarmup = -28,
    729     /// RPC method is deprecated
    730     RpcMethodDeprecated = -32,
    731     /// Bitcoin is not connected
    732     RpcClientNotConnected = -9,
    733     /// Still downloading initial blocks
    734     RpcClientInInitialDownload = -10,
    735     /// Node is already added
    736     RpcClientNodeAlreadyAdded = -23,
    737     /// Node has not been added before
    738     RpcClientNodeNotAdded = -24,
    739     /// Node to disconnect not found in connected nodes
    740     RpcClientNodeNotConnected = -29,
    741     /// Invalid IP/Subnet
    742     RpcClientInvalidIpOrSubnet = -30,
    743     /// No valid connection manager instance found
    744     RpcClientP2pDisabled = -31,
    745     /// Max number of outbound or block-relay connections already open
    746     RpcClientNodeCapacityReached = -34,
    747     /// No mempool instance found
    748     RpcClientMempoolDisabled = -33,
    749     /// Unspecified problem with wallet (key not found etc.)
    750     RpcWalletError = -4,
    751     /// Not enough funds in wallet or account
    752     RpcWalletInsufficientFunds = -6,
    753     /// Invalid label name
    754     RpcWalletInvalidLabelName = -11,
    755     /// Keypool ran out, call keypoolrefill first
    756     RpcWalletKeypoolRanOut = -12,
    757     /// Enter the wallet passphrase with walletpassphrase first
    758     RpcWalletUnlockNeeded = -13,
    759     /// The wallet passphrase entered was incorrect
    760     RpcWalletPassphraseIncorrect = -14,
    761     /// Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
    762     RpcWalletWrongEncState = -15,
    763     /// Failed to encrypt the wallet
    764     RpcWalletEncryptionFailed = -16,
    765     /// Wallet is already unlocked
    766     RpcWalletAlreadyUnlocked = -17,
    767     /// Invalid wallet specified
    768     RpcWalletNotFound = -18,
    769     /// No wallet specified (error when there are multiple wallets loaded)
    770     RpcWalletNotSpecified = -19,
    771     /// This same wallet is already loaded
    772     RpcWalletAlreadyLoaded = -35,
    773     /// There is already a wallet with the same name
    774     RpcWalletAlreadyExists = -36,
    775     /// Server is in safe mode, and command is not allowed in safe mode
    776     RpcForbiddenBySafeMode = -2,
    777 }