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