depolymerization

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

rpc.rs (21898B)


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