depolymerization

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

rpc.rs (22750B)


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