From fbf2dd16727f14c23c576885268eb89996c94e60 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Fri, 13 Mar 2020 00:20:49 -0600 Subject: [PATCH] CLI: Error message cleanup (#8804) automerge --- Cargo.lock | 1 + archiver-lib/src/archiver.rs | 1 + clap-utils/Cargo.toml | 1 + clap-utils/src/input_validators.rs | 26 +- clap-utils/src/keypair.rs | 14 +- clap-utils/src/lib.rs | 19 + cli/src/cli.rs | 58 +-- cli/src/cluster_query.rs | 2 +- cli/src/main.rs | 10 +- cli/src/nonce.rs | 2 +- cli/src/stake.rs | 6 +- cli/src/storage.rs | 2 +- cli/src/validator_info.rs | 2 +- client/src/client_error.rs | 153 +++++- client/src/generic_rpc_client_request.rs | 4 +- client/src/mock_rpc_client_request.rs | 4 +- client/src/rpc_client.rs | 627 +++++++---------------- client/src/rpc_client_request.rs | 4 +- client/src/rpc_request.rs | 28 +- client/src/rpc_response.rs | 8 +- client/src/thin_client.rs | 106 ++-- keygen/src/keygen.rs | 6 + local-cluster/src/cluster_tests.rs | 2 +- local-cluster/src/local_cluster.rs | 3 +- runtime/src/bank_client.rs | 8 +- sdk/src/client.rs | 12 +- sdk/src/hash.rs | 5 +- sdk/src/instruction.rs | 34 +- sdk/src/signature.rs | 4 +- sdk/src/transport.rs | 35 +- watchtower/src/main.rs | 8 +- 31 files changed, 556 insertions(+), 639 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce3ac9ea98..00e222e148 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3792,6 +3792,7 @@ dependencies = [ "rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "solana-remote-wallet 1.1.0", "solana-sdk 1.1.0", + "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-bip39 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/archiver-lib/src/archiver.rs b/archiver-lib/src/archiver.rs index d1c9c72f13..26a1ed030f 100644 --- a/archiver-lib/src/archiver.rs +++ b/archiver-lib/src/archiver.rs @@ -612,6 +612,7 @@ impl Archiver { ErrorKind::Other, "setup_mining_account: signature not found", ), + TransportError::Custom(e) => io::Error::new(ErrorKind::Other, e), })?; } Ok(()) diff --git a/clap-utils/Cargo.toml b/clap-utils/Cargo.toml index 6e805c168d..3a632436e4 100644 --- a/clap-utils/Cargo.toml +++ b/clap-utils/Cargo.toml @@ -13,6 +13,7 @@ clap = "2.33.0" rpassword = "4.0" solana-remote-wallet = { path = "../remote-wallet", version = "1.1.0" } solana-sdk = { path = "../sdk", version = "1.1.0" } +thiserror = "1.0.11" tiny-bip39 = "0.7.0" url = "2.1.0" chrono = "0.4" diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index 0e6b659a1c..88ec1c7b13 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -12,7 +12,7 @@ use std::str::FromStr; pub fn is_pubkey(string: String) -> Result<(), String> { match string.parse::() { Ok(_) => Ok(()), - Err(err) => Err(format!("{:?}", err)), + Err(err) => Err(format!("{}", err)), } } @@ -20,7 +20,7 @@ pub fn is_pubkey(string: String) -> Result<(), String> { pub fn is_hash(string: String) -> Result<(), String> { match string.parse::() { Ok(_) => Ok(()), - Err(err) => Err(format!("{:?}", err)), + Err(err) => Err(format!("{}", err)), } } @@ -28,7 +28,7 @@ pub fn is_hash(string: String) -> Result<(), String> { pub fn is_keypair(string: String) -> Result<(), String> { read_keypair_file(&string) .map(|_| ()) - .map_err(|err| format!("{:?}", err)) + .map_err(|err| format!("{}", err)) } // Return an error if a keypair file cannot be parsed @@ -38,7 +38,7 @@ pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> { } read_keypair_file(&string) .map(|_| ()) - .map_err(|err| format!("{:?}", err)) + .map_err(|err| format!("{}", err)) } // Return an error if string cannot be parsed as pubkey string or keypair file location @@ -73,10 +73,10 @@ pub fn is_pubkey_sig(string: String) -> Result<(), String> { .ok_or_else(|| "Malformed signer string".to_string())?, ) { Ok(_) => Ok(()), - Err(err) => Err(format!("{:?}", err)), + Err(err) => Err(format!("{}", err)), } } - Err(err) => Err(format!("{:?}", err)), + Err(err) => Err(format!("{}", err)), } } @@ -90,20 +90,20 @@ pub fn is_url(string: String) -> Result<(), String> { Err("no host provided".to_string()) } } - Err(err) => Err(format!("{:?}", err)), + Err(err) => Err(format!("{}", err)), } } pub fn is_slot(slot: String) -> Result<(), String> { slot.parse::() .map(|_| ()) - .map_err(|e| format!("{:?}", e)) + .map_err(|e| format!("{}", e)) } pub fn is_port(port: String) -> Result<(), String> { port.parse::() .map(|_| ()) - .map_err(|e| format!("{:?}", e)) + .map_err(|e| format!("{}", e)) } pub fn is_valid_percentage(percentage: String) -> Result<(), String> { @@ -111,7 +111,7 @@ pub fn is_valid_percentage(percentage: String) -> Result<(), String> { .parse::() .map_err(|e| { format!( - "Unable to parse input percentage, provided: {}, err: {:?}", + "Unable to parse input percentage, provided: {}, err: {}", percentage, e ) }) @@ -141,7 +141,7 @@ pub fn is_amount(amount: String) -> Result<(), String> { pub fn is_rfc3339_datetime(value: String) -> Result<(), String> { DateTime::parse_from_rfc3339(&value) .map(|_| ()) - .map_err(|e| format!("{:?}", e)) + .map_err(|e| format!("{}", e)) } pub fn is_derivation(value: String) -> Result<(), String> { @@ -152,7 +152,7 @@ pub fn is_derivation(value: String) -> Result<(), String> { .parse::() .map_err(|e| { format!( - "Unable to parse derivation, provided: {}, err: {:?}", + "Unable to parse derivation, provided: {}, err: {}", account, e ) }) @@ -160,7 +160,7 @@ pub fn is_derivation(value: String) -> Result<(), String> { if let Some(change) = parts.next() { change.parse::().map_err(|e| { format!( - "Unable to parse derivation, provided: {}, err: {:?}", + "Unable to parse derivation, provided: {}, err: {}", change, e ) }) diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index a0fcf85945..1c210255d7 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -1,6 +1,6 @@ use crate::{input_parsers::pubkeys_sigs_of, offline::SIGNER_ARG, ArgConstant}; use bip39::{Language, Mnemonic, Seed}; -use clap::{ArgMatches, Error, ErrorKind}; +use clap::ArgMatches; use rpassword::prompt_password_stderr; use solana_remote_wallet::{ remote_keypair::generate_remote_keypair, @@ -72,9 +72,9 @@ pub fn signer_from_path( )?)) } KeypairUrl::Filepath(path) => match read_keypair_file(&path) { - Err(e) => Err(Error::with_description( - &format!("Couldn't find keypair file: {:?} error: {:?}", path, e), - ErrorKind::InvalidValue, + Err(e) => Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("could not find keypair file: {} error: {}", path, e), ) .into()), Ok(file) => Ok(Box::new(file)), @@ -102,9 +102,9 @@ pub fn signer_from_path( if let Some(presigner) = presigner { Ok(Box::new(presigner)) } else { - Err(Error::with_description( - "Missing signature for supplied pubkey", - ErrorKind::MissingRequiredArgument, + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "missing signature for supplied pubkey".to_string(), ) .into()) } diff --git a/clap-utils/src/lib.rs b/clap-utils/src/lib.rs index a66efa3981..37926d6fbe 100644 --- a/clap-utils/src/lib.rs +++ b/clap-utils/src/lib.rs @@ -1,3 +1,5 @@ +use thiserror::Error; + #[macro_export] macro_rules! version { () => { @@ -23,6 +25,23 @@ pub struct ArgConstant<'a> { pub help: &'a str, } +/// Error type for forwarding Errors out of `main()` of a `clap` app +/// and still using the `Display` formatter +#[derive(Error)] +#[error("{0}")] +pub struct DisplayError(Box); +impl DisplayError { + pub fn new_as_boxed(inner: Box) -> Box { + DisplayError(inner).into() + } +} + +impl std::fmt::Debug for DisplayError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "{}", self.0) + } +} + pub mod input_parsers; pub mod input_validators; pub mod keypair; diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 41df117b07..8358d4e9a8 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -18,7 +18,10 @@ use solana_clap_utils::{ input_parsers::*, input_validators::*, keypair::signer_from_path, offline::SIGN_ONLY_ARG, ArgConstant, }; -use solana_client::{client_error::ClientError, rpc_client::RpcClient}; +use solana_client::{ + client_error::{ClientErrorKind, Result as ClientResult}, + rpc_client::RpcClient, +}; #[cfg(not(test))] use solana_faucet::faucet::request_airdrop_transaction; #[cfg(test)] @@ -47,14 +50,15 @@ use solana_stake_program::{ use solana_storage_program::storage_instruction::StorageAccountType; use solana_vote_program::vote_state::VoteAuthorize; use std::{ + error, fs::File, io::{Read, Write}, net::{IpAddr, SocketAddr}, sync::Arc, thread::sleep, time::Duration, - {error, fmt}, }; +use thiserror::Error; use url::Url; pub type CliSigners = Vec>; @@ -409,46 +413,34 @@ pub struct CliCommandInfo { pub signers: CliSigners, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Error, PartialEq)] pub enum CliError { + #[error("bad parameter: {0}")] BadParameter(String), + #[error("command not recognized: {0}")] CommandNotRecognized(String), + #[error("insuficient funds for fee")] InsufficientFundsForFee, + #[error(transparent)] InvalidNonce(CliNonceError), + #[error("dynamic program error: {0}")] DynamicProgramError(String), + #[error("rpc request error: {0}")] RpcRequestError(String), + #[error("keypair file not found: {0}")] KeypairFileNotFound(String), } -impl fmt::Display for CliError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid") - } -} - -impl error::Error for CliError { - fn description(&self) -> &str { - "invalid" - } - - fn cause(&self) -> Option<&dyn error::Error> { - // Generic error, underlying cause isn't tracked. - None - } -} - impl From> for CliError { fn from(error: Box) -> Self { - CliError::DynamicProgramError(format!("{:?}", error)) + CliError::DynamicProgramError(error.to_string()) } } impl From for CliError { fn from(error: CliNonceError) -> Self { match error { - CliNonceError::Client(client_error) => { - Self::RpcRequestError(format!("{:?}", client_error)) - } + CliNonceError::Client(client_error) => Self::RpcRequestError(client_error), _ => Self::InvalidNonce(error), } } @@ -721,7 +713,7 @@ pub fn parse_command( .parse() .or_else(|err| { Err(CliError::BadParameter(format!( - "Invalid faucet port: {:?}", + "Invalid faucet port: {}", err ))) })?; @@ -729,7 +721,7 @@ pub fn parse_command( let faucet_host = if let Some(faucet_host) = matches.value_of("faucet_host") { Some(solana_net_utils::parse_host(faucet_host).or_else(|err| { Err(CliError::BadParameter(format!( - "Invalid faucet host: {:?}", + "Invalid faucet host: {}", err ))) })?) @@ -1141,13 +1133,13 @@ fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResu if let Some(result) = status { match result { Ok(_) => Ok("Confirmed".to_string()), - Err(err) => Ok(format!("Transaction failed with error {:?}", err)), + Err(err) => Ok(format!("Transaction failed with error: {}", err)), } } else { Ok("Not found".to_string()) } } - Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {:?}", err)).into()), + Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {}", err)).into()), } } @@ -2118,18 +2110,18 @@ pub fn request_and_confirm_airdrop( log_instruction_custom_error::(result) } -pub fn log_instruction_custom_error(result: Result) -> ProcessResult +pub fn log_instruction_custom_error(result: ClientResult) -> ProcessResult where E: 'static + std::error::Error + DecodeError + FromPrimitive, { match result { Err(err) => { - if let ClientError::TransactionError(TransactionError::InstructionError( + if let ClientErrorKind::TransactionError(TransactionError::InstructionError( _, InstructionError::CustomError(code), - )) = err + )) = err.kind() { - if let Some(specific_error) = E::decode_custom_error_to_enum(code) { + if let Some(specific_error) = E::decode_custom_error_to_enum(*code) { error!("{}::{:?}", E::type_of(), specific_error); eprintln!( "Program Error ({}::{:?}): {}", @@ -3332,7 +3324,7 @@ mod tests { assert_eq!( process_command(&config).unwrap(), format!( - "Transaction failed with error {:?}", + "Transaction failed with error: {}", TransactionError::AccountInUse ) ); diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index a74f9282fb..29cb7225c5 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -1011,7 +1011,7 @@ pub fn process_live_slots(url: &str) -> ProcessResult { current = Some(new_info); } Err(err) => { - eprintln!("disconnected: {:?}", err); + eprintln!("disconnected: {}", err); break; } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 345448b9ee..639f8442f8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,9 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand}; use console::style; -use solana_clap_utils::{input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG}; +use solana_clap_utils::{ + input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError, +}; use solana_cli::{ cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners}, display::{println_name_value, println_name_value_or}, @@ -230,6 +232,10 @@ fn main() -> Result<(), Box> { ) .get_matches(); + do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into()) +} + +fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box> { if parse_settings(&matches)? { let wallet_manager = maybe_wallet_manager()?; @@ -237,6 +243,6 @@ fn main() -> Result<(), Box> { config.signers = signers.iter().map(|s| s.as_ref()).collect(); let result = process_command(&config)?; println!("{}", result); - } + }; Ok(()) } diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index 45be826542..f6e744e48f 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -238,7 +238,7 @@ pub fn get_account( ) -> Result { rpc_client .get_account(nonce_pubkey) - .map_err(|e| CliNonceError::Client(format!("{:?}", e))) + .map_err(|e| CliNonceError::Client(format!("{}", e))) .and_then(|a| match account_identity_ok(&a) { Ok(()) => Ok(a), Err(e) => Err(e), diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 8f3113c9a6..90242eb0d0 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1300,7 +1300,7 @@ pub fn process_show_stake_account( Ok("".to_string()) } Err(err) => Err(CliError::RpcRequestError(format!( - "Account data could not be deserialized to stake state: {:?}", + "Account data could not be deserialized to stake state: {}", err )) .into()), @@ -1396,11 +1396,11 @@ pub fn process_delegate_stake( } }; - if sanity_check_result.is_err() { + if let Err(err) = &sanity_check_result { if !force { sanity_check_result?; } else { - println!("--force supplied, ignoring: {:?}", sanity_check_result); + println!("--force supplied, ignoring: {}", err); } } } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index f207bd2dc7..1f6595c90d 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -266,7 +266,7 @@ pub fn process_show_storage_account( use solana_storage_program::storage_contract::StorageContract; let storage_contract: StorageContract = account.state().map_err(|err| { - CliError::RpcRequestError(format!("Unable to deserialize storage account: {:?}", err)) + CliError::RpcRequestError(format!("Unable to deserialize storage account: {}", err)) })?; println!("{:#?}", storage_contract); println!("Account Lamports: {}", account.lamports); diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index bd83bdf132..672f1bee09 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -274,7 +274,7 @@ pub fn process_set_validator_info( println!("--force supplied, ignoring: {:?}", result); } else { result.map_err(|err| { - CliError::BadParameter(format!("Invalid validator keybase username: {:?}", err)) + CliError::BadParameter(format!("Invalid validator keybase username: {}", err)) })?; } } diff --git a/client/src/client_error.rs b/client/src/client_error.rs index a9ccaf469d..131f1cefee 100644 --- a/client/src/client_error.rs +++ b/client/src/client_error.rs @@ -1,20 +1,161 @@ use crate::rpc_request; -use solana_sdk::{signature::SignerError, transaction::TransactionError}; -use std::{fmt, io}; +use solana_sdk::{ + signature::SignerError, transaction::TransactionError, transport::TransportError, +}; +use std::io; use thiserror::Error; #[derive(Error, Debug)] -pub enum ClientError { +pub enum ClientErrorKind { + #[error(transparent)] Io(#[from] io::Error), + #[error(transparent)] Reqwest(#[from] reqwest::Error), + #[error(transparent)] RpcError(#[from] rpc_request::RpcError), + #[error(transparent)] SerdeJson(#[from] serde_json::error::Error), + #[error(transparent)] SigningError(#[from] SignerError), + #[error(transparent)] TransactionError(#[from] TransactionError), + #[error("Custom: {0}")] + Custom(String), } -impl fmt::Display for ClientError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "solana client error") +impl From for ClientErrorKind { + fn from(err: TransportError) -> Self { + match err { + TransportError::IoError(err) => Self::Io(err), + TransportError::TransactionError(err) => Self::TransactionError(err), + TransportError::Custom(err) => Self::Custom(err), + } } } + +impl Into for ClientErrorKind { + fn into(self) -> TransportError { + match self { + Self::Io(err) => TransportError::IoError(err), + Self::TransactionError(err) => TransportError::TransactionError(err), + Self::Reqwest(err) => TransportError::Custom(format!("{:?}", err)), + Self::RpcError(err) => TransportError::Custom(format!("{:?}", err)), + Self::SerdeJson(err) => TransportError::Custom(format!("{:?}", err)), + Self::SigningError(err) => TransportError::Custom(format!("{:?}", err)), + Self::Custom(err) => TransportError::Custom(format!("{:?}", err)), + } + } +} + +#[derive(Error, Debug)] +#[error("{kind}")] +pub struct ClientError { + command: Option<&'static str>, + #[source] + #[error(transparent)] + kind: ClientErrorKind, +} + +impl ClientError { + pub fn new_with_command(kind: ClientErrorKind, command: &'static str) -> Self { + Self { + command: Some(command), + kind, + } + } + + pub fn into_with_command(self, command: &'static str) -> Self { + Self { + command: Some(command), + ..self + } + } + + pub fn command(&self) -> Option<&'static str> { + self.command + } + + pub fn kind(&self) -> &ClientErrorKind { + &self.kind + } +} + +impl From for ClientError { + fn from(kind: ClientErrorKind) -> Self { + Self { + command: None, + kind, + } + } +} + +impl From for ClientError { + fn from(err: TransportError) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl Into for ClientError { + fn into(self) -> TransportError { + self.kind.into() + } +} + +impl From for ClientError { + fn from(err: std::io::Error) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl From for ClientError { + fn from(err: reqwest::Error) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl From for ClientError { + fn from(err: rpc_request::RpcError) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl From for ClientError { + fn from(err: serde_json::error::Error) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl From for ClientError { + fn from(err: SignerError) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +impl From for ClientError { + fn from(err: TransactionError) -> Self { + Self { + command: None, + kind: err.into(), + } + } +} + +pub type Result = std::result::Result; diff --git a/client/src/generic_rpc_client_request.rs b/client/src/generic_rpc_client_request.rs index 5c9f337542..509c98fd38 100644 --- a/client/src/generic_rpc_client_request.rs +++ b/client/src/generic_rpc_client_request.rs @@ -1,4 +1,4 @@ -use crate::{client_error::ClientError, rpc_request::RpcRequest}; +use crate::{client_error::Result, rpc_request::RpcRequest}; pub(crate) trait GenericRpcClientRequest { fn send( @@ -6,5 +6,5 @@ pub(crate) trait GenericRpcClientRequest { request: &RpcRequest, params: serde_json::Value, retries: usize, - ) -> Result; + ) -> Result; } diff --git a/client/src/mock_rpc_client_request.rs b/client/src/mock_rpc_client_request.rs index 90a7d3d098..db464b6175 100644 --- a/client/src/mock_rpc_client_request.rs +++ b/client/src/mock_rpc_client_request.rs @@ -1,5 +1,5 @@ use crate::{ - client_error::ClientError, + client_error::Result, generic_rpc_client_request::GenericRpcClientRequest, rpc_request::RpcRequest, rpc_response::{Response, RpcResponseContext}, @@ -41,7 +41,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest { request: &RpcRequest, params: serde_json::Value, _retries: usize, - ) -> Result { + ) -> Result { if let Some(value) = self.mocks.write().unwrap().remove(request) { return Ok(value); } diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 477cc0f4a9..96732fe0be 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1,13 +1,13 @@ use crate::{ - client_error::ClientError, + client_error::{ClientError, Result as ClientResult}, generic_rpc_client_request::GenericRpcClientRequest, mock_rpc_client_request::{MockRpcClientRequest, Mocks}, rpc_client_request::RpcClientRequest, - rpc_request::RpcRequest, + rpc_request::{RpcError, RpcRequest}, rpc_response::{ Response, RpcAccount, RpcBlockhashFeeCalculator, RpcConfirmedBlock, RpcContactInfo, RpcEpochInfo, RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity, RpcKeyedAccount, - RpcLeaderSchedule, RpcResponse, RpcVersionInfo, RpcVoteAccountStatus, + RpcLeaderSchedule, RpcResult, RpcVersionInfo, RpcVoteAccountStatus, }, }; use bincode::serialize; @@ -27,7 +27,7 @@ use solana_sdk::{ transaction::{self, Transaction, TransactionError}, }; use std::{ - error, io, + error, net::SocketAddr, thread::sleep, time::{Duration, Instant}, @@ -67,7 +67,7 @@ impl RpcClient { } } - pub fn confirm_transaction(&self, signature: &str) -> io::Result { + pub fn confirm_transaction(&self, signature: &str) -> ClientResult { Ok(self .confirm_transaction_with_commitment(signature, CommitmentConfig::default())? .value) @@ -77,7 +77,7 @@ impl RpcClient { &self, signature: &str, commitment_config: CommitmentConfig, - ) -> RpcResponse { + ) -> RpcResult { let response = self .client .send( @@ -85,32 +85,19 @@ impl RpcClient { json!([signature, commitment_config]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("ConfirmTransaction request failure {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("ConfirmTransaction"))?; - serde_json::from_value::>(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("Received result of an unexpected type {:?}", err), - ) - }) + serde_json::from_value::>(response) + .map_err(|err| ClientError::new_with_command(err.into(), "ConfirmTransaction")) } - pub fn send_transaction(&self, transaction: &Transaction) -> Result { + pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult { let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string(); let signature = self.client .send(&RpcRequest::SendTransaction, json!([serialized_encoded]), 5)?; if signature.as_str().is_none() { - Err(io::Error::new( - io::ErrorKind::Other, - "Received result of an unexpected type", - ) - .into()) + Err(RpcError::ForUser("Received result of an unexpected type".to_string()).into()) } else { Ok(signature.as_str().unwrap().to_string()) } @@ -119,7 +106,7 @@ impl RpcClient { pub fn get_signature_status( &self, signature: &str, - ) -> Result>, ClientError> { + ) -> ClientResult>> { self.get_signature_status_with_commitment(signature, CommitmentConfig::default()) } @@ -127,7 +114,7 @@ impl RpcClient { &self, signature: &str, commitment_config: CommitmentConfig, - ) -> Result>, ClientError> { + ) -> ClientResult>> { let signature_status = self.client.send( &RpcRequest::GetSignatureStatus, json!([signature.to_string(), commitment_config]), @@ -138,127 +125,82 @@ impl RpcClient { Ok(result) } - pub fn get_slot(&self) -> io::Result { + pub fn get_slot(&self) -> ClientResult { self.get_slot_with_commitment(CommitmentConfig::default()) } pub fn get_slot_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send(&RpcRequest::GetSlot, json!([commitment_config]), 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetSlot request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetSlot"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetSlot parse failure: {}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetSlot")) } - pub fn total_supply(&self) -> io::Result { + pub fn total_supply(&self) -> ClientResult { self.total_supply_with_commitment(CommitmentConfig::default()) } pub fn total_supply_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send(&RpcRequest::GetTotalSupply, json!([commitment_config]), 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetTotalSupply request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetTotalSupply"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetTotalSupply parse failure: {}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetTotalSupply")) } - pub fn get_vote_accounts(&self) -> io::Result { + pub fn get_vote_accounts(&self) -> ClientResult { self.get_vote_accounts_with_commitment(CommitmentConfig::default()) } pub fn get_vote_accounts_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send(&RpcRequest::GetVoteAccounts, json!([commitment_config]), 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetVoteAccounts request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetVoteAccounts"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetVoteAccounts parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetVoteAccounts")) } - pub fn get_cluster_nodes(&self) -> io::Result> { + pub fn get_cluster_nodes(&self) -> ClientResult> { let response = self .client .send(&RpcRequest::GetClusterNodes, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetClusterNodes request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetClusterNodes"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetClusterNodes parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetClusterNodes")) } - pub fn get_confirmed_block(&self, slot: Slot) -> io::Result { + pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult { let response = self .client .send(&RpcRequest::GetConfirmedBlock, json!([slot]), 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetConfirmedBlock request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetConfirmedBlock"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetConfirmedBlock parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetConfirmedBlock")) } pub fn get_confirmed_blocks( &self, start_slot: Slot, end_slot: Option, - ) -> io::Result> { + ) -> ClientResult> { let response = self .client .send( @@ -266,22 +208,13 @@ impl RpcClient { json!([start_slot, end_slot]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetConfirmedBlocks request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetConfirmedBlocks"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetConfirmedBlocks parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetConfirmedBlocks")) } - pub fn get_block_time(&self, slot: Slot) -> io::Result { + pub fn get_block_time(&self, slot: Slot) -> ClientResult { let response = self .client .send(&RpcRequest::GetBlockTime, json!([slot]), 0); @@ -289,50 +222,37 @@ impl RpcClient { response .map(|result_json| { if result_json.is_null() { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Block Not Found: slot={}", slot), - )); + return Err(RpcError::ForUser(format!("Block Not Found: slot={}", slot)).into()); } - let result = serde_json::from_value(result_json)?; + let result = serde_json::from_value(result_json) + .map_err(|err| ClientError::new_with_command(err.into(), "GetBlockTime"))?; trace!("Response block timestamp {:?} {:?}", slot, result); Ok(result) }) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetBlockTime request failure: {:?}", err), - ) - })? + .map_err(|err| err.into_with_command("GetBlockTime"))? } - pub fn get_epoch_info(&self) -> io::Result { + pub fn get_epoch_info(&self) -> ClientResult { self.get_epoch_info_with_commitment(CommitmentConfig::default()) } pub fn get_epoch_info_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send(&RpcRequest::GetEpochInfo, json!([commitment_config]), 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetEpochInfo request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetEpochInfo"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetEpochInfo parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetEpochInfo")) } - pub fn get_leader_schedule(&self, slot: Option) -> io::Result> { + pub fn get_leader_schedule( + &self, + slot: Option, + ) -> ClientResult> { self.get_leader_schedule_with_commitment(slot, CommitmentConfig::default()) } @@ -340,7 +260,7 @@ impl RpcClient { &self, slot: Option, commitment_config: CommitmentConfig, - ) -> io::Result> { + ) -> ClientResult> { let response = self .client .send( @@ -348,130 +268,75 @@ impl RpcClient { json!([slot, commitment_config]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetLeaderSchedule request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetLeaderSchedule"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetLeaderSchedule failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetLeaderSchedule")) } - pub fn get_epoch_schedule(&self) -> io::Result { + pub fn get_epoch_schedule(&self) -> ClientResult { let response = self .client .send(&RpcRequest::GetEpochSchedule, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetEpochSchedule request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetEpochSchedule"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetEpochSchedule parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetEpochSchedule")) } - pub fn get_identity(&self) -> io::Result { + pub fn get_identity(&self) -> ClientResult { let response = self .client .send(&RpcRequest::GetIdentity, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetIdentity request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetIdentity"))?; serde_json::from_value(response) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetIdentity failure: {:?}", err), - ) - }) + .map_err(|err| ClientError::new_with_command(err.into(), "GetIdentity")) .and_then(|rpc_identity: RpcIdentity| { - rpc_identity.identity.parse::().map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetIdentity invalid pubkey failure: {:?}", err), + rpc_identity.identity.parse::().map_err(|_| { + ClientError::new_with_command( + RpcError::ParseError("Pubkey".to_string()).into(), + "GetIdentity", ) }) }) } - pub fn get_inflation(&self) -> io::Result { + pub fn get_inflation(&self) -> ClientResult { let response = self .client .send(&RpcRequest::GetInflation, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetInflation request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetInflation"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetInflation parse failure: {}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetInflation")) } - pub fn get_version(&self) -> io::Result { + pub fn get_version(&self) -> ClientResult { let response = self .client .send(&RpcRequest::GetVersion, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetVersion request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetVersion"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetVersion parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetVersion")) } - pub fn minimum_ledger_slot(&self) -> io::Result { + pub fn minimum_ledger_slot(&self) -> ClientResult { let response = self .client .send(&RpcRequest::MinimumLedgerSlot, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("MinimumLedgerSlot request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("MinimumLedgerSlot"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("MinimumLedgerSlot parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "MinimumLedgerSlot")) } pub fn send_and_confirm_transaction( &self, transaction: &mut Transaction, signer_keys: &T, - ) -> Result { + ) -> ClientResult { let mut send_retries = 20; loop { let mut status_retries = 15; @@ -508,11 +373,9 @@ impl RpcClient { if let Some(err) = status { return Err(err.unwrap_err().into()); } else { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Transaction {:?} failed: {:?}", signature_str, status), - ) - .into()); + return Err( + RpcError::ForUser("unable to confirm transaction. This can happen in situations such as transaction expiration and insufficient fee-payer funds".to_string()).into(), + ); } } } @@ -571,7 +434,7 @@ impl RpcClient { } if send_retries == 0 { - return Err(io::Error::new(io::ErrorKind::Other, "Transactions failed").into()); + return Err(RpcError::ForUser("Transactions failed".to_string()).into()); } send_retries -= 1; @@ -590,7 +453,7 @@ impl RpcClient { &self, tx: &mut Transaction, signer_keys: &T, - ) -> Result<(), ClientError> { + ) -> ClientResult<()> { let (blockhash, _fee_calculator) = self.get_new_blockhash(&tx.message().recent_blockhash)?; tx.try_sign(signer_keys, blockhash)?; @@ -609,41 +472,26 @@ impl RpcClient { json!([pubkey.to_string()]), retries, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("RetryGetBalance request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("RetryGetBalance"))?; Ok(Some( serde_json::from_value::>(balance_json) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("RetryGetBalance parse failure: {:?}", err), - ) - })? + .map_err(|err| ClientError::new_with_command(err.into(), "RetryGetBalance"))? .value, )) } - pub fn get_account(&self, pubkey: &Pubkey) -> io::Result { + pub fn get_account(&self, pubkey: &Pubkey) -> ClientResult { self.get_account_with_commitment(pubkey, CommitmentConfig::default())? .value - .ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - format!("AccountNotFound: pubkey={}", pubkey), - ) - }) + .ok_or_else(|| RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into()) } pub fn get_account_with_commitment( &self, pubkey: &Pubkey, commitment_config: CommitmentConfig, - ) -> RpcResponse> { + ) -> RpcResult> { let response = self.client.send( &RpcRequest::GetAccountInfo, json!([pubkey.to_string(), commitment_config]), @@ -653,10 +501,9 @@ impl RpcClient { response .map(|result_json| { if result_json.is_null() { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("AccountNotFound: pubkey={}", pubkey), - )); + return Err( + RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(), + ); } let Response { context, @@ -670,18 +517,18 @@ impl RpcClient { }) }) .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("AccountNotFound: pubkey={}: {:?}", pubkey, err), - ) + Into::::into(RpcError::ForUser(format!( + "AccountNotFound: pubkey={}: {}", + pubkey, err + ))) })? } - pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result> { + pub fn get_account_data(&self, pubkey: &Pubkey) -> ClientResult> { Ok(self.get_account(pubkey)?.data) } - pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> io::Result { + pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> ClientResult { let minimum_balance_json = self .client .send( @@ -689,21 +536,10 @@ impl RpcClient { json!([data_len]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!( - "GetMinimumBalanceForRentExemption request failure: {:?}", - err - ), - ) - })?; + .map_err(|err| err.into_with_command("GetMinimumBalanceForRentExemption"))?; let minimum_balance: u64 = serde_json::from_value(minimum_balance_json).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetMinimumBalanceForRentExemption parse failure: {:?}", err), - ) + ClientError::new_with_command(err.into(), "GetMinimumBalanceForRentExemption") })?; trace!( "Response minimum balance {:?} {:?}", @@ -714,7 +550,7 @@ impl RpcClient { } /// Request the balance of the account `pubkey`. - pub fn get_balance(&self, pubkey: &Pubkey) -> io::Result { + pub fn get_balance(&self, pubkey: &Pubkey) -> ClientResult { Ok(self .get_balance_with_commitment(pubkey, CommitmentConfig::default())? .value) @@ -724,7 +560,7 @@ impl RpcClient { &self, pubkey: &Pubkey, commitment_config: CommitmentConfig, - ) -> RpcResponse { + ) -> RpcResult { let balance_json = self .client .send( @@ -732,22 +568,13 @@ impl RpcClient { json!([pubkey.to_string(), commitment_config]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetBalance request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetBalance"))?; - serde_json::from_value::>(balance_json).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetBalance parse failure: {:?}", err), - ) - }) + serde_json::from_value::>(balance_json) + .map_err(|err| ClientError::new_with_command(err.into(), "GetBalance")) } - pub fn get_program_accounts(&self, pubkey: &Pubkey) -> io::Result> { + pub fn get_program_accounts(&self, pubkey: &Pubkey) -> ClientResult> { let response = self .client .send( @@ -755,27 +582,18 @@ impl RpcClient { json!([pubkey.to_string()]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("AccountNotFound: pubkey={}: {:?}", pubkey, err), - ) - })?; + .map_err(|err| err.into_with_command("GetProgramAccounts"))?; let accounts: Vec = - serde_json::from_value::>(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetProgramAccounts parse failure: {:?}", err), - ) - })?; + serde_json::from_value::>(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetProgramAccounts"))?; let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new(); for RpcKeyedAccount { pubkey, account } in accounts.into_iter() { - let pubkey = pubkey.parse().map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetProgramAccounts parse failure: {:?}", err), + let pubkey = pubkey.parse().map_err(|_| { + ClientError::new_with_command( + RpcError::ParseError("Pubkey".to_string()).into(), + "GetProgramAccounts", ) })?; pubkey_accounts.push((pubkey, account.decode().unwrap())); @@ -784,14 +602,14 @@ impl RpcClient { } /// Request the transaction count. - pub fn get_transaction_count(&self) -> io::Result { + pub fn get_transaction_count(&self) -> ClientResult { self.get_transaction_count_with_commitment(CommitmentConfig::default()) } pub fn get_transaction_count_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send( @@ -799,22 +617,13 @@ impl RpcClient { json!([commitment_config]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetTransactionCount request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetTransactionCount"))?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetTransactionCount parse failure: {:?}", err), - ) - }) + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetTransactionCount")) } - pub fn get_recent_blockhash(&self) -> io::Result<(Hash, FeeCalculator)> { + pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { Ok(self .get_recent_blockhash_with_commitment(CommitmentConfig::default())? .value) @@ -823,7 +632,7 @@ impl RpcClient { pub fn get_recent_blockhash_with_commitment( &self, commitment_config: CommitmentConfig, - ) -> RpcResponse<(Hash, FeeCalculator)> { + ) -> RpcResult<(Hash, FeeCalculator)> { let response = self .client .send( @@ -831,12 +640,7 @@ impl RpcClient { json!([commitment_config]), 0, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetRecentBlockhash request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetRecentBlockhash"))?; let Response { context, @@ -845,18 +649,12 @@ impl RpcClient { blockhash, fee_calculator, }, - } = serde_json::from_value::>(response).map_err( - |err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetRecentBlockhash parse failure: {:?}", err), - ) - }, - )?; - let blockhash = blockhash.parse().map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetRecentBlockhash hash parse failure: {:?}", err), + } = serde_json::from_value::>(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetRecentBlockhash"))?; + let blockhash = blockhash.parse().map_err(|_| { + ClientError::new_with_command( + RpcError::ParseError("Hash".to_string()).into(), + "GetRecentBlockhash", ) })?; Ok(Response { @@ -868,7 +666,7 @@ impl RpcClient { pub fn get_fee_calculator_for_blockhash( &self, blockhash: &Hash, - ) -> io::Result> { + ) -> ClientResult> { let response = self .client .send( @@ -876,50 +674,31 @@ impl RpcClient { json!([blockhash.to_string()]), 0, ) - .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("GetFeeCalculatorForBlockhash request failure: {:?}", e), - ) - })?; + .map_err(|err| err.into_with_command("GetFeeCalculatorForBlockhash"))?; let Response { value, .. } = serde_json::from_value::>>( response, ) - .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("GetFeeCalculatorForBlockhash parse failure: {:?}", e), - ) - })?; + .map_err(|e| ClientError::new_with_command(e.into(), "GetFeeCalculatorForBlockhash"))?; Ok(value.map(|rf| rf.fee_calculator)) } - pub fn get_fee_rate_governor(&self) -> RpcResponse { + pub fn get_fee_rate_governor(&self) -> RpcResult { let response = self .client .send(&RpcRequest::GetFeeRateGovernor, Value::Null, 0) - .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("GetFeeRateGovernor request failure: {:?}", e), - ) - })?; + .map_err(|err| err.into_with_command("GetFeeRateGovernor"))?; let Response { context, value: RpcFeeRateGovernor { fee_rate_governor }, - } = serde_json::from_value::>(response).map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("GetFeeRateGovernor parse failure: {:?}", e), - ) - })?; + } = serde_json::from_value::>(response) + .map_err(|e| ClientError::new_with_command(e.into(), "GetFeeRateGovernor"))?; Ok(Response { context, value: fee_rate_governor, }) } - pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> { + pub fn get_new_blockhash(&self, blockhash: &Hash) -> ClientResult<(Hash, FeeCalculator)> { let mut num_retries = 0; let start = Instant::now(); while start.elapsed().as_secs() < 5 { @@ -936,39 +715,28 @@ impl RpcClient { )); num_retries += 1; } - Err(io::Error::new( - io::ErrorKind::Other, - format!( - "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", - start.elapsed().as_millis(), - num_retries, - blockhash - ), + Err(RpcError::ForUser(format!( + "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", + start.elapsed().as_millis(), + num_retries, + blockhash )) + .into()) } - pub fn get_genesis_hash(&self) -> io::Result { + pub fn get_genesis_hash(&self) -> ClientResult { let response = self .client .send(&RpcRequest::GetGenesisHash, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetGenesisHash request failure: {:?}", err), - ) - })?; + .map_err(|err| err.into_with_command("GetGenesisHash"))?; - let hash = serde_json::from_value::(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetGenesisHash parse failure: {:?}", err), - ) - })?; + let hash = serde_json::from_value::(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetGenesisHash"))?; - let hash = hash.parse().map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("GetGenesisHash hash parse failure: {:?}", err), + let hash = hash.parse().map_err(|_| { + ClientError::new_with_command( + RpcError::ParseError("Hash".to_string()).into(), + "GetGenesisHash", ) })?; Ok(hash) @@ -980,7 +748,7 @@ impl RpcClient { polling_frequency: &Duration, timeout: &Duration, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { let now = Instant::now(); loop { match self.get_balance_with_commitment(&pubkey, commitment_config.clone()) { @@ -1001,7 +769,7 @@ impl RpcClient { &self, pubkey: &Pubkey, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> ClientResult { self.poll_balance_with_timeout_and_commitment( pubkey, &Duration::from_millis(100), @@ -1040,7 +808,7 @@ impl RpcClient { } /// Poll the server to confirm a transaction. - pub fn poll_for_signature(&self, signature: &Signature) -> io::Result<()> { + pub fn poll_for_signature(&self, signature: &Signature) -> ClientResult<()> { self.poll_for_signature_with_commitment(signature, CommitmentConfig::default()) } @@ -1049,7 +817,7 @@ impl RpcClient { &self, signature: &Signature, commitment_config: CommitmentConfig, - ) -> io::Result<()> { + ) -> ClientResult<()> { let now = Instant::now(); loop { if let Ok(Some(_)) = self.get_signature_status_with_commitment( @@ -1059,13 +827,11 @@ impl RpcClient { break; } if now.elapsed().as_secs() > 15 { - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "signature not found after {} seconds", - now.elapsed().as_secs() - ), - )); + return Err(RpcError::ForUser(format!( + "signature not found after {} seconds", + now.elapsed().as_secs() + )) + .into()); } sleep(Duration::from_millis(250)); } @@ -1114,7 +880,7 @@ impl RpcClient { &self, signature: &Signature, min_confirmed_blocks: usize, - ) -> io::Result { + ) -> ClientResult { let mut now = Instant::now(); let mut confirmed_blocks = 0; loop { @@ -1151,13 +917,11 @@ impl RpcClient { if confirmed_blocks > 0 { return Ok(confirmed_blocks); } else { - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "signature not found after {} seconds", - now.elapsed().as_secs() - ), - )); + return Err(RpcError::ForUser(format!( + "signature not found after {} seconds", + now.elapsed().as_secs() + )) + .into()); } } sleep(Duration::from_millis(250)); @@ -1168,7 +932,7 @@ impl RpcClient { pub fn get_num_blocks_since_signature_confirmation( &self, signature: &Signature, - ) -> io::Result { + ) -> ClientResult { let response = self .client .send( @@ -1176,50 +940,22 @@ impl RpcClient { json!([signature.to_string(), CommitmentConfig::recent().ok()]), 1, ) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!( - "GetNumBlocksSinceSignatureConfirmation request failure: {}", - err - ), - ) - })?; + .map_err(|err| err.into_with_command("GetNumBlocksSinceSignatureConfirmation"))?; serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!( - "GetNumBlocksSinceSignatureConfirmation parse failure: {}", - err - ), - ) + ClientError::new_with_command(err.into(), "GetNumBlocksSinceSignatureConfirmation") }) } - pub fn validator_exit(&self) -> io::Result { + pub fn validator_exit(&self) -> ClientResult { let response = self .client .send(&RpcRequest::ValidatorExit, Value::Null, 0) - .map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("ValidatorExit request failure: {:?}", err), - ) - })?; - serde_json::from_value(response).map_err(|err| { - io::Error::new( - io::ErrorKind::Other, - format!("ValidatorExit parse failure: {:?}", err), - ) - }) + .map_err(|err| err.into_with_command("ValidatorExit"))?; + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "ValidatorExit")) } - pub fn send( - &self, - request: &RpcRequest, - params: Value, - retries: usize, - ) -> Result { + pub fn send(&self, request: &RpcRequest, params: Value, retries: usize) -> ClientResult { assert!(params.is_array() || params.is_null()); self.client.send(request, params, retries) } @@ -1236,7 +972,10 @@ pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String { #[cfg(test)] mod tests { use super::*; - use crate::mock_rpc_client_request::{PUBKEY, SIGNATURE}; + use crate::{ + client_error::ClientErrorKind, + mock_rpc_client_request::{PUBKEY, SIGNATURE}, + }; use assert_matches::assert_matches; use jsonrpc_core::{Error, IoHandler, Params}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; @@ -1246,7 +985,7 @@ mod tests { instruction::InstructionError, signature::Keypair, system_transaction, transaction::TransactionError, }; - use std::{sync::mpsc::channel, thread}; + use std::{io, sync::mpsc::channel, thread}; #[test] fn test_send() { @@ -1407,8 +1146,8 @@ mod tests { let rpc_client = RpcClient::new_mock("instruction_error".to_string()); let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]); assert_matches!( - result.unwrap_err(), - ClientError::TransactionError(TransactionError::InstructionError( + result.unwrap_err().kind(), + ClientErrorKind::TransactionError(TransactionError::InstructionError( 0, InstructionError::UninitializedAccount )) @@ -1416,7 +1155,7 @@ mod tests { let rpc_client = RpcClient::new_mock("sig_not_found".to_string()); let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]); - if let ClientError::Io(err) = result.unwrap_err() { + if let ClientErrorKind::Io(err) = result.unwrap_err().kind() { assert_eq!(err.kind(), io::ErrorKind::Other); } } diff --git a/client/src/rpc_client_request.rs b/client/src/rpc_client_request.rs index c076a2c544..a3f53ffb96 100644 --- a/client/src/rpc_client_request.rs +++ b/client/src/rpc_client_request.rs @@ -1,5 +1,5 @@ use crate::{ - client_error::ClientError, + client_error::Result, generic_rpc_client_request::GenericRpcClientRequest, rpc_request::{RpcError, RpcRequest}, }; @@ -34,7 +34,7 @@ impl GenericRpcClientRequest for RpcClientRequest { request: &RpcRequest, params: serde_json::Value, mut retries: usize, - ) -> Result { + ) -> Result { // Concurrent requests are not supported so reuse the same request id for all requests let request_id = 1; diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index cc0564170a..9e21599de2 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -1,5 +1,5 @@ use serde_json::{json, Value}; -use std::{error, fmt}; +use thiserror::Error; #[derive(Debug, PartialEq, Eq, Hash)] pub enum RpcRequest { @@ -95,26 +95,16 @@ impl RpcRequest { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Error)] pub enum RpcError { + #[error("rpc reques error: {0}")] RpcRequestError(String), -} - -impl fmt::Display for RpcError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid") - } -} - -impl error::Error for RpcError { - fn description(&self) -> &str { - "invalid" - } - - fn cause(&self) -> Option<&dyn error::Error> { - // Generic error, underlying cause isn't tracked. - None - } + #[error("parse error: expected {0}")] + ParseError(String), /* "expected" */ + // Anything in a `ForUser` needs to die. The caller should be + // deciding what to tell their user + #[error("{0}")] + ForUser(String), /* "direct-to-user message" */ } #[cfg(test)] diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index d2b0bfc465..212eb3a9f1 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -1,6 +1,5 @@ -use crate::rpc_request::RpcError; +use crate::{client_error, rpc_request::RpcError}; use bincode::serialize; -use jsonrpc_core::Result as JsonResult; use solana_sdk::{ account::Account, clock::{Epoch, Slot}, @@ -9,10 +8,9 @@ use solana_sdk::{ pubkey::Pubkey, transaction::{Result, Transaction}, }; -use std::{collections::HashMap, io, net::SocketAddr, str::FromStr}; +use std::{collections::HashMap, net::SocketAddr, str::FromStr}; -pub type RpcResponseIn = JsonResult>; -pub type RpcResponse = io::Result>; +pub type RpcResult = client_error::Result>; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RpcResponseContext { diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index 652a762107..e3c601bddc 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -188,7 +188,7 @@ impl ThinClient { transaction: &mut Transaction, tries: usize, min_confirmed_blocks: usize, - ) -> io::Result { + ) -> TransportResult { self.send_and_confirm_transaction(&[keypair], transaction, tries, min_confirmed_blocks) } @@ -198,7 +198,7 @@ impl ThinClient { keypair: &Keypair, transaction: &mut Transaction, tries: usize, - ) -> io::Result { + ) -> TransportResult { self.send_and_confirm_transaction(&[keypair], transaction, tries, 0) } @@ -209,7 +209,7 @@ impl ThinClient { transaction: &mut Transaction, tries: usize, pending_confirmations: usize, - ) -> io::Result { + ) -> TransportResult { for x in 0..tries { let now = Instant::now(); let mut buf = vec![0; serialized_size(&transaction).unwrap() as usize]; @@ -243,13 +243,14 @@ impl ThinClient { } } info!("{} tries failed transfer to {}", x, self.tpu_addr()); - let (blockhash, _fee_calculator) = self.rpc_client().get_recent_blockhash()?; + let (blockhash, _fee_calculator) = self.get_recent_blockhash()?; transaction.sign(keypairs, blockhash); } Err(io::Error::new( io::ErrorKind::Other, format!("retry_transfer failed in {} retries", tries), - )) + ) + .into()) } pub fn poll_balance_with_timeout_and_commitment( @@ -258,13 +259,15 @@ impl ThinClient { polling_frequency: &Duration, timeout: &Duration, commitment_config: CommitmentConfig, - ) -> io::Result { - self.rpc_client().poll_balance_with_timeout_and_commitment( - pubkey, - polling_frequency, - timeout, - commitment_config, - ) + ) -> TransportResult { + self.rpc_client() + .poll_balance_with_timeout_and_commitment( + pubkey, + polling_frequency, + timeout, + commitment_config, + ) + .map_err(|e| e.into()) } pub fn poll_balance_with_timeout( @@ -272,8 +275,8 @@ impl ThinClient { pubkey: &Pubkey, polling_frequency: &Duration, timeout: &Duration, - ) -> io::Result { - self.rpc_client().poll_balance_with_timeout_and_commitment( + ) -> TransportResult { + self.poll_balance_with_timeout_and_commitment( pubkey, polling_frequency, timeout, @@ -281,18 +284,18 @@ impl ThinClient { ) } - pub fn poll_get_balance(&self, pubkey: &Pubkey) -> io::Result { - self.rpc_client() - .poll_get_balance_with_commitment(pubkey, CommitmentConfig::default()) + pub fn poll_get_balance(&self, pubkey: &Pubkey) -> TransportResult { + self.poll_get_balance_with_commitment(pubkey, CommitmentConfig::default()) } pub fn poll_get_balance_with_commitment( &self, pubkey: &Pubkey, commitment_config: CommitmentConfig, - ) -> io::Result { + ) -> TransportResult { self.rpc_client() .poll_get_balance_with_commitment(pubkey, commitment_config) + .map_err(|e| e.into()) } pub fn wait_for_balance(&self, pubkey: &Pubkey, expected_balance: Option) -> Option { @@ -321,9 +324,9 @@ impl ThinClient { signature: &Signature, commitment_config: CommitmentConfig, ) -> TransportResult<()> { - Ok(self - .rpc_client() - .poll_for_signature_with_commitment(signature, commitment_config)?) + self.rpc_client() + .poll_for_signature_with_commitment(signature, commitment_config) + .map_err(|e| e.into()) } /// Check a signature in the bank. This method blocks @@ -332,16 +335,17 @@ impl ThinClient { self.rpc_client().check_signature(signature) } - pub fn validator_exit(&self) -> io::Result { - self.rpc_client().validator_exit() + pub fn validator_exit(&self) -> TransportResult { + self.rpc_client().validator_exit().map_err(|e| e.into()) } pub fn get_num_blocks_since_signature_confirmation( &mut self, sig: &Signature, - ) -> io::Result { + ) -> TransportResult { self.rpc_client() .get_num_blocks_since_signature_confirmation(sig) + .map_err(|e| e.into()) } } @@ -400,14 +404,14 @@ impl SyncClient for ThinClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> TransportResult> { - Ok(self - .rpc_client() - .get_account_with_commitment(pubkey, commitment_config)? - .value) + self.rpc_client() + .get_account_with_commitment(pubkey, commitment_config) + .map_err(|e| e.into()) + .map(|r| r.value) } fn get_balance(&self, pubkey: &Pubkey) -> TransportResult { - Ok(self.rpc_client().get_balance(pubkey)?) + self.rpc_client().get_balance(pubkey).map_err(|e| e.into()) } fn get_balance_with_commitment( @@ -415,10 +419,10 @@ impl SyncClient for ThinClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> TransportResult { - let balance = self - .rpc_client() - .get_balance_with_commitment(pubkey, commitment_config)?; - Ok(balance.value) + self.rpc_client() + .get_balance_with_commitment(pubkey, commitment_config) + .map_err(|e| e.into()) + .map(|r| r.value) } fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> { @@ -449,15 +453,16 @@ impl SyncClient for ThinClient { &self, blockhash: &Hash, ) -> TransportResult> { - let fee_calculator = self - .rpc_client() - .get_fee_calculator_for_blockhash(blockhash)?; - Ok(fee_calculator) + self.rpc_client() + .get_fee_calculator_for_blockhash(blockhash) + .map_err(|e| e.into()) } fn get_fee_rate_governor(&self) -> TransportResult { - let fee_rate_governor = self.rpc_client().get_fee_rate_governor()?; - Ok(fee_rate_governor.value) + self.rpc_client() + .get_fee_rate_governor() + .map_err(|e| e.into()) + .map(|r| r.value) } fn get_signature_status( @@ -555,23 +560,26 @@ impl SyncClient for ThinClient { signature: &Signature, min_confirmed_blocks: usize, ) -> TransportResult { - Ok(self - .rpc_client() - .poll_for_signature_confirmation(signature, min_confirmed_blocks)?) + self.rpc_client() + .poll_for_signature_confirmation(signature, min_confirmed_blocks) + .map_err(|e| e.into()) } fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()> { - Ok(self.rpc_client().poll_for_signature(signature)?) + self.rpc_client() + .poll_for_signature(signature) + .map_err(|e| e.into()) } fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> { - let new_blockhash = self.rpc_client().get_new_blockhash(blockhash)?; - Ok(new_blockhash) + self.rpc_client() + .get_new_blockhash(blockhash) + .map_err(|e| e.into()) } } impl AsyncClient for ThinClient { - fn async_send_transaction(&self, transaction: Transaction) -> io::Result { + fn async_send_transaction(&self, transaction: Transaction) -> TransportResult { let mut buf = vec![0; serialized_size(&transaction).unwrap() as usize]; let mut wr = std::io::Cursor::new(&mut buf[..]); serialize_into(&mut wr, &transaction) @@ -586,7 +594,7 @@ impl AsyncClient for ThinClient { keypairs: &T, message: Message, recent_blockhash: Hash, - ) -> io::Result { + ) -> TransportResult { let transaction = Transaction::new(keypairs, message, recent_blockhash); self.async_send_transaction(transaction) } @@ -595,7 +603,7 @@ impl AsyncClient for ThinClient { keypair: &Keypair, instruction: Instruction, recent_blockhash: Hash, - ) -> io::Result { + ) -> TransportResult { let message = Message::new(&[instruction]); self.async_send_message(&[keypair], message, recent_blockhash) } @@ -605,7 +613,7 @@ impl AsyncClient for ThinClient { keypair: &Keypair, pubkey: &Pubkey, recent_blockhash: Hash, - ) -> io::Result { + ) -> TransportResult { let transfer_instruction = system_instruction::transfer(&keypair.pubkey(), pubkey, lamports); self.async_send_instruction(keypair, transfer_instruction, recent_blockhash) diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index d85acfbdef..999fd0ee46 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -11,6 +11,7 @@ use solana_clap_utils::{ keypair_from_seed_phrase, prompt_passphrase, signer_from_path, SKIP_SEED_PHRASE_VALIDATION_ARG, }, + DisplayError, }; use solana_cli_config::{Config, CONFIG_FILE}; use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager}; @@ -378,6 +379,11 @@ fn main() -> Result<(), Box> { ) .get_matches(); + + do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into()) +} + +fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box> { let config = if let Some(config_file) = matches.value_of("config_file") { Config::load(config_file).unwrap_or_default() } else { diff --git a/local-cluster/src/cluster_tests.rs b/local-cluster/src/cluster_tests.rs index b40157c289..ad37320303 100644 --- a/local-cluster/src/cluster_tests.rs +++ b/local-cluster/src/cluster_tests.rs @@ -262,7 +262,7 @@ pub fn kill_entry_and_spend_and_verify_rest( ); match sig { Err(e) => { - result = Err(TransportError::IoError(e)); + result = Err(e); continue; } diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 1fa3732765..020ecf39de 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -23,6 +23,7 @@ use solana_sdk::{ signature::{Keypair, Signer}, system_transaction, transaction::Transaction, + transport::Result as TransportResult, }; use solana_stake_program::{ config as stake_config, stake_instruction, @@ -607,7 +608,7 @@ impl LocalCluster { storage_keypair: &Keypair, from_keypair: &Arc, archiver: bool, - ) -> Result<()> { + ) -> TransportResult<()> { let storage_account_type = if archiver { StorageAccountType::Archiver } else { diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 56c2337a06..1cf80ee36d 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -36,7 +36,7 @@ impl Client for BankClient { } impl AsyncClient for BankClient { - fn async_send_transaction(&self, transaction: Transaction) -> io::Result { + fn async_send_transaction(&self, transaction: Transaction) -> Result { let signature = transaction.signatures.get(0).cloned().unwrap_or_default(); let transaction_sender = self.transaction_sender.lock().unwrap(); transaction_sender.send(transaction).unwrap(); @@ -48,7 +48,7 @@ impl AsyncClient for BankClient { keypairs: &T, message: Message, recent_blockhash: Hash, - ) -> io::Result { + ) -> Result { let transaction = Transaction::new(keypairs, message, recent_blockhash); self.async_send_transaction(transaction) } @@ -58,7 +58,7 @@ impl AsyncClient for BankClient { keypair: &Keypair, instruction: Instruction, recent_blockhash: Hash, - ) -> io::Result { + ) -> Result { let message = Message::new(&[instruction]); self.async_send_message(&[keypair], message, recent_blockhash) } @@ -70,7 +70,7 @@ impl AsyncClient for BankClient { keypair: &Keypair, pubkey: &Pubkey, recent_blockhash: Hash, - ) -> io::Result { + ) -> Result { let transfer_instruction = system_instruction::transfer(&keypair.pubkey(), pubkey, lamports); self.async_send_instruction(keypair, transfer_instruction, recent_blockhash) diff --git a/sdk/src/client.rs b/sdk/src/client.rs index 714b1282f8..e80d39c849 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -21,7 +21,6 @@ use crate::{ transaction, transport::Result, }; -use std::io; pub trait Client: SyncClient + AsyncClient { fn tpu_addr(&self) -> String; @@ -122,10 +121,7 @@ pub trait SyncClient { pub trait AsyncClient { /// Send a signed transaction, but don't wait to see if the server accepted it. - fn async_send_transaction( - &self, - transaction: transaction::Transaction, - ) -> io::Result; + fn async_send_transaction(&self, transaction: transaction::Transaction) -> Result; /// Create a transaction from the given message, and send it to the /// server, but don't wait for to see if the server accepted it. @@ -134,7 +130,7 @@ pub trait AsyncClient { keypairs: &T, message: Message, recent_blockhash: Hash, - ) -> io::Result; + ) -> Result; /// Create a transaction from a single instruction that only requires /// a single signer. Then send it to the server, but don't wait for a reply. @@ -143,7 +139,7 @@ pub trait AsyncClient { keypair: &Keypair, instruction: Instruction, recent_blockhash: Hash, - ) -> io::Result; + ) -> Result; /// Attempt to transfer lamports from `keypair` to `pubkey`, but don't wait to confirm. fn async_transfer( @@ -152,5 +148,5 @@ pub trait AsyncClient { keypair: &Keypair, pubkey: &Pubkey, recent_blockhash: Hash, - ) -> io::Result; + ) -> Result; } diff --git a/sdk/src/hash.rs b/sdk/src/hash.rs index 87c19a0507..45f6f333e8 100644 --- a/sdk/src/hash.rs +++ b/sdk/src/hash.rs @@ -2,6 +2,7 @@ use sha2::{Digest, Sha256}; use std::{convert::TryFrom, fmt, mem, str::FromStr}; +use thiserror::Error; pub const HASH_BYTES: usize = 32; #[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -47,9 +48,11 @@ impl fmt::Display for Hash { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum ParseHashError { + #[error("string decoded to wrong size for hash")] WrongSize, + #[error("failed to decoded string to hash")] Invalid, } diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index 2516ba9e70..8ae3375c07 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -3,96 +3,124 @@ use crate::{pubkey::Pubkey, short_vec, system_instruction::SystemError}; use bincode::serialize; use serde::Serialize; +use thiserror::Error; /// Reasons the runtime might have rejected an instruction. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone)] pub enum InstructionError { /// Deprecated! Use CustomError instead! /// The program instruction returned an error + #[error("generic instruction error")] GenericError, - /// The arguments provided to a program instruction where invalid + /// The arguments provided to a program were invalid + #[error("invalid program argument")] InvalidArgument, - /// An instruction's data contents was invalid + /// An instruction's data contents were invalid + #[error("invalid instruction data")] InvalidInstructionData, /// An account's data contents was invalid + #[error("invalid account data for instruction")] InvalidAccountData, /// An account's data was too small + #[error("account data too small for instruction")] AccountDataTooSmall, /// An account's balance was too small to complete the instruction + #[error("insufficient funds for instruction")] InsufficientFunds, /// The account did not have the expected program id + #[error("incorrect program id for instruction")] IncorrectProgramId, /// A signature was required but not found + #[error("missing required signature for instruction")] MissingRequiredSignature, /// An initialize instruction was sent to an account that has already been initialized. + #[error("instruction requires an uninitialized account")] AccountAlreadyInitialized, /// An attempt to operate on an account that hasn't been initialized. + #[error("instruction requires an initialized account")] UninitializedAccount, /// Program's instruction lamport balance does not equal the balance after the instruction + #[error("sum of account balances before and after instruction do not match")] UnbalancedInstruction, /// Program modified an account's program id + #[error("instruction modified the program id of an account")] ModifiedProgramId, /// Program spent the lamports of an account that doesn't belong to it + #[error("instruction spent from the balance of an account it does not own")] ExternalAccountLamportSpend, /// Program modified the data of an account that doesn't belong to it + #[error("instruction modified data of an account it does not own")] ExternalAccountDataModified, /// Read-only account modified lamports + #[error("instruction changed balance of a read-only account")] ReadonlyLamportChange, /// Read-only account modified data + #[error("instruction modified data of a read-only account")] ReadonlyDataModified, /// An account was referenced more than once in a single instruction // Deprecated, instructions can now contain duplicate accounts + #[error("instruction contains duplicate accounts")] DuplicateAccountIndex, /// Executable bit on account changed, but shouldn't have + #[error("instruction changed executable bit of an account")] ExecutableModified, /// Rent_epoch account changed, but shouldn't have + #[error("instruction modified rent epoch of an account")] RentEpochModified, /// The instruction expected additional account keys + #[error("insufficient account key count for instruction")] NotEnoughAccountKeys, /// A non-system program changed the size of the account data + #[error("non-system instruction changed account size")] AccountDataSizeChanged, /// The instruction expected an executable account + #[error("instruction expected an executable account")] AccountNotExecutable, /// Failed to borrow a reference to account data, already borrowed + #[error("instruction tries to borrow reference for an account which is already borrowed")] AccountBorrowFailed, /// Account data has an outstanding reference after a program's execution + #[error("instruction left account with an outstanding reference borrowed")] AccountBorrowOutstanding, /// The same account was multiply passed to an on-chain program's entrypoint, but the program /// modified them differently. A program can only modify one instance of the account because /// the runtime cannot determine which changes to pick or how to merge them if both are modified + #[error("instruction modifications of multiply-passed account differ")] DuplicateAccountOutOfSync, /// Allows on-chain programs to implement program-specific error types and see them returned /// by the Solana runtime. A program-specific error may be any type that is represented as /// or serialized to a u32 integer. + #[error("program error: {0}")] CustomError(u32), /// The return value from the program was invalid. Valid errors are either a defined builtin /// error value or a user-defined error in the lower 32 bits. + #[error("program returned invalid error code")] InvalidError, } diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index b7efb88b34..80b1a33155 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -107,9 +107,11 @@ impl Into<[u8; 64]> for Signature { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum ParseSignatureError { + #[error("string decoded to wrong size for signature")] WrongSize, + #[error("failed to decode string to signature")] Invalid, } diff --git a/sdk/src/transport.rs b/sdk/src/transport.rs index 6306893548..84c19807fa 100644 --- a/sdk/src/transport.rs +++ b/sdk/src/transport.rs @@ -1,20 +1,15 @@ use crate::transaction::TransactionError; -use std::{error, fmt, io}; +use std::io; +use thiserror::Error; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum TransportError { - IoError(io::Error), - TransactionError(TransactionError), -} - -impl error::Error for TransportError {} -impl fmt::Display for TransportError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - TransportError::IoError(err) => write!(formatter, "{:?}", err), - TransportError::TransactionError(err) => write!(formatter, "{:?}", err), - } - } + #[error("transport io error: {0}")] + IoError(#[from] io::Error), + #[error("transport transaction error: {0}")] + TransactionError(#[from] TransactionError), + #[error("transport custom error: {0}")] + Custom(String), } impl TransportError { @@ -27,16 +22,4 @@ impl TransportError { } } -impl From for TransportError { - fn from(err: io::Error) -> TransportError { - TransportError::IoError(err) - } -} - -impl From for TransportError { - fn from(err: TransactionError) -> TransportError { - TransportError::TransactionError(err) - } -} - pub type Result = std::result::Result; diff --git a/watchtower/src/main.rs b/watchtower/src/main.rs index 7bf327589d..b4fcf7bb95 100644 --- a/watchtower/src/main.rs +++ b/watchtower/src/main.rs @@ -9,10 +9,12 @@ use solana_clap_utils::{ input_parsers::pubkeys_of, input_validators::{is_pubkey_or_keypair, is_url}, }; -use solana_client::{rpc_client::RpcClient, rpc_response::RpcVoteAccountStatus}; +use solana_client::{ + client_error::Result as ClientResult, rpc_client::RpcClient, rpc_response::RpcVoteAccountStatus, +}; use solana_metrics::{datapoint_error, datapoint_info}; use solana_sdk::{hash::Hash, native_token::lamports_to_sol, pubkey::Pubkey}; -use std::{error, io, str::FromStr, thread::sleep, time::Duration}; +use std::{error, str::FromStr, thread::sleep, time::Duration}; struct Config { interval: Duration, @@ -115,7 +117,7 @@ fn get_config() -> Config { config } -fn get_cluster_info(rpc_client: &RpcClient) -> io::Result<(u64, Hash, RpcVoteAccountStatus)> { +fn get_cluster_info(rpc_client: &RpcClient) -> ClientResult<(u64, Hash, RpcVoteAccountStatus)> { let transaction_count = rpc_client.get_transaction_count()?; let recent_blockhash = rpc_client.get_recent_blockhash()?.0; let vote_accounts = rpc_client.get_vote_accounts()?;