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 }