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 }