diff --git a/Cargo.lock b/Cargo.lock index f63aedc2bc..2255d6356d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2338,6 +2338,8 @@ dependencies = [ "bincode 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "solana-runtime 0.14.0", @@ -2650,6 +2652,8 @@ dependencies = [ "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2713,6 +2717,8 @@ version = "0.14.0" dependencies = [ "bincode 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.14.0", @@ -2787,6 +2793,7 @@ dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "solana 0.14.0", "solana-budget-api 0.14.0", diff --git a/client/src/client_error.rs b/client/src/client_error.rs new file mode 100644 index 0000000000..8eb6ca7432 --- /dev/null +++ b/client/src/client_error.rs @@ -0,0 +1,50 @@ +use crate::rpc_request; +use solana_sdk::transaction::TransactionError; +use std::{fmt, io}; + +#[derive(Debug)] +pub enum ClientError { + Io(io::Error), + Reqwest(reqwest::Error), + RpcError(rpc_request::RpcError), + SerdeJson(serde_json::error::Error), + TransactionError(TransactionError), +} + +impl fmt::Display for ClientError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "solana client error") + } +} + +impl std::error::Error for ClientError {} + +impl From for ClientError { + fn from(err: io::Error) -> ClientError { + ClientError::Io(err) + } +} + +impl From for ClientError { + fn from(err: reqwest::Error) -> ClientError { + ClientError::Reqwest(err) + } +} + +impl From for ClientError { + fn from(err: rpc_request::RpcError) -> ClientError { + ClientError::RpcError(err) + } +} + +impl From for ClientError { + fn from(err: serde_json::error::Error) -> ClientError { + ClientError::SerdeJson(err) + } +} + +impl From for ClientError { + fn from(err: TransactionError) -> ClientError { + ClientError::TransactionError(err) + } +} diff --git a/client/src/generic_rpc_client_request.rs b/client/src/generic_rpc_client_request.rs index fad8cef426..590c30aa3b 100644 --- a/client/src/generic_rpc_client_request.rs +++ b/client/src/generic_rpc_client_request.rs @@ -1,3 +1,4 @@ +use crate::client_error::ClientError; use crate::rpc_request::RpcRequest; pub(crate) trait GenericRpcClientRequest { @@ -6,5 +7,5 @@ pub(crate) trait GenericRpcClientRequest { request: &RpcRequest, params: Option, retries: usize, - ) -> Result>; + ) -> Result; } diff --git a/client/src/lib.rs b/client/src/lib.rs index 813c69ef1e..151c52f88a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,3 +1,4 @@ +pub mod client_error; mod generic_rpc_client_request; pub mod mock_rpc_client_request; pub mod rpc_client; diff --git a/client/src/mock_rpc_client_request.rs b/client/src/mock_rpc_client_request.rs index e245749950..51c0d97147 100644 --- a/client/src/mock_rpc_client_request.rs +++ b/client/src/mock_rpc_client_request.rs @@ -1,3 +1,4 @@ +use crate::client_error::ClientError; use crate::generic_rpc_client_request::GenericRpcClientRequest; use crate::rpc_request::RpcRequest; use serde_json::{Number, Value}; @@ -23,7 +24,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest { request: &RpcRequest, params: Option, _retries: usize, - ) -> Result> { + ) -> Result { if self.url == "fails" { return Ok(Value::Null); } diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 0cd1785771..cf27c4d4d2 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1,3 +1,4 @@ +use crate::client_error::ClientError; use crate::generic_rpc_client_request::GenericRpcClientRequest; use crate::mock_rpc_client_request::MockRpcClientRequest; use crate::rpc_client_request::RpcClientRequest; @@ -46,10 +47,7 @@ impl RpcClient { } } - pub fn send_transaction( - &self, - transaction: &Transaction, - ) -> Result> { + pub fn send_transaction(&self, transaction: &Transaction) -> Result { let serialized = serialize(transaction).unwrap(); let params = json!([serialized]); let signature = self @@ -67,7 +65,7 @@ impl RpcClient { pub fn get_signature_status( &self, signature: &str, - ) -> Result>, Box> { + ) -> Result>, ClientError> { let params = json!([signature.to_string()]); let signature_status = self.client @@ -81,7 +79,7 @@ impl RpcClient { &self, transaction: &mut Transaction, signer: &T, - ) -> Result> { + ) -> Result { let mut send_retries = 5; loop { let mut status_retries = 4; @@ -117,10 +115,14 @@ impl RpcClient { send_retries - 1 }; if send_retries == 0 { - Err(io::Error::new( - io::ErrorKind::Other, - format!("Transaction {:?} failed: {:?}", signature_str, status), - ))?; + if status.is_some() { + status.unwrap()? + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!("Transaction {:?} failed: {:?}", signature_str, status), + ))?; + } } } } @@ -201,7 +203,7 @@ impl RpcClient { &self, tx: &mut Transaction, signer_key: &T, - ) -> Result<(), Box> { + ) -> Result<(), ClientError> { let blockhash = self.get_new_blockhash(&tx.message().recent_blockhash)?; tx.sign(&[signer_key], blockhash); Ok(()) @@ -482,7 +484,7 @@ impl RpcClient { ) .map_err(|error| { debug!( - "Response get_num_blocks_since_signature_confirmation: {}", + "Response get_num_blocks_since_signature_confirmation: {:?}", error ); io::Error::new( @@ -526,7 +528,7 @@ impl RpcClient { request: &RpcRequest, params: Option, retries: usize, - ) -> Result> { + ) -> Result { self.client.send(request, params, retries) } } diff --git a/client/src/rpc_client_request.rs b/client/src/rpc_client_request.rs index 012e7d66b7..82ebdf190f 100644 --- a/client/src/rpc_client_request.rs +++ b/client/src/rpc_client_request.rs @@ -1,3 +1,4 @@ +use crate::client_error::ClientError; use crate::generic_rpc_client_request::GenericRpcClientRequest; use crate::rpc_request::{RpcError, RpcRequest}; use log::*; @@ -36,7 +37,7 @@ impl GenericRpcClientRequest for RpcClientRequest { request: &RpcRequest, params: Option, 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/thin_client.rs b/client/src/thin_client.rs index 5db51aabdb..5aebedb62d 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -221,7 +221,7 @@ impl SyncClient for ThinClient { .map_err(|err| { io::Error::new( io::ErrorKind::Other, - format!("send_transaction failed with error {}", err), + format!("send_transaction failed with error {:?}", err), ) })?; Ok(status) diff --git a/programs/budget_api/Cargo.toml b/programs/budget_api/Cargo.toml index eab6045cd2..0f339db772 100644 --- a/programs/budget_api/Cargo.toml +++ b/programs/budget_api/Cargo.toml @@ -12,6 +12,8 @@ edition = "2018" bincode = "1.1.3" chrono = { version = "0.4.0", features = ["serde"] } log = "0.4.2" +num-derive = "0.2" +num-traits = "0.2" serde = "1.0.90" serde_derive = "1.0.90" solana-sdk = { path = "../../sdk", version = "0.14.0" } diff --git a/programs/budget_api/src/budget_state.rs b/programs/budget_api/src/budget_state.rs index cf840e85f5..c7c0390497 100644 --- a/programs/budget_api/src/budget_state.rs +++ b/programs/budget_api/src/budget_state.rs @@ -1,14 +1,29 @@ //! budget state use crate::budget_expr::BudgetExpr; use bincode::{self, deserialize, serialize_into}; +use num_derive::FromPrimitive; use serde_derive::{Deserialize, Serialize}; use solana_sdk::instruction::InstructionError; +use solana_sdk::instruction_processor_utils::DecodeError; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive)] pub enum BudgetError { DestinationMissing, } +impl DecodeError for BudgetError { + fn type_of(&self) -> &'static str { + "BudgetError" + } +} + +impl std::fmt::Display for BudgetError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error") + } +} +impl std::error::Error for BudgetError {} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct BudgetState { pub initialized: bool, diff --git a/programs/token_api/Cargo.toml b/programs/token_api/Cargo.toml index 1e8b0c7250..faa9ade154 100644 --- a/programs/token_api/Cargo.toml +++ b/programs/token_api/Cargo.toml @@ -11,6 +11,8 @@ edition = "2018" [dependencies] bincode = "1.1.3" log = "0.4.2" +num-derive = "0.2" +num-traits = "0.2" serde = "1.0.90" serde_derive = "1.0.90" solana-logger = { path = "../../logger", version = "0.14.0" } diff --git a/programs/token_api/src/token_state.rs b/programs/token_api/src/token_state.rs index 7a205c27cd..9b2a3ee4a7 100644 --- a/programs/token_api/src/token_state.rs +++ b/programs/token_api/src/token_state.rs @@ -1,15 +1,23 @@ use log::*; +use num_derive::FromPrimitive; use serde_derive::{Deserialize, Serialize}; use solana_sdk::account::KeyedAccount; +use solana_sdk::instruction_processor_utils::DecodeError; use solana_sdk::pubkey::Pubkey; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Serialize, Debug, PartialEq, FromPrimitive)] pub enum TokenError { InvalidArgument, InsufficentFunds, NotOwner, } +impl DecodeError for TokenError { + fn type_of(&self) -> &'static str { + "TokenError" + } +} + impl std::fmt::Display for TokenError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "error") diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 9821161752..f2dff97608 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -18,6 +18,8 @@ chrono = { version = "0.4.0", features = ["serde"] } generic-array = { version = "0.13.0", default-features = false, features = ["serde"] } itertools = "0.8.0" log = "0.4.2" +num-derive = "0.2" +num-traits = "0.2" rand = "0.6.5" rayon = "1.0.0" sha2 = "0.8.0" diff --git a/sdk/src/instruction_processor_utils.rs b/sdk/src/instruction_processor_utils.rs index cc57ac6901..c160efb339 100644 --- a/sdk/src/instruction_processor_utils.rs +++ b/sdk/src/instruction_processor_utils.rs @@ -2,6 +2,7 @@ use crate::account::{Account, KeyedAccount}; use crate::instruction::InstructionError; use crate::pubkey::Pubkey; use bincode::ErrorKind; +use num_traits::FromPrimitive; // All native programs export a symbol named process() pub const ENTRYPOINT: &str = "process"; @@ -64,3 +65,39 @@ where self.account.set_state(state) } } + +pub trait DecodeError { + fn decode_custom_error_to_enum(int: u32) -> Option + where + E: FromPrimitive, + { + E::from_u32(int) + } + fn type_of(&self) -> &'static str; +} + +#[cfg(test)] +mod tests { + use super::*; + use num_derive::FromPrimitive; + + #[test] + fn test_decode_custom_error_to_enum() { + #[derive(Debug, FromPrimitive, PartialEq)] + enum TestEnum { + A, + B, + C, + } + impl DecodeError for TestEnum { + fn type_of(&self) -> &'static str { + "TestEnum" + } + } + assert_eq!(TestEnum::decode_custom_error_to_enum(0), Some(TestEnum::A)); + assert_eq!(TestEnum::decode_custom_error_to_enum(1), Some(TestEnum::B)); + assert_eq!(TestEnum::decode_custom_error_to_enum(2), Some(TestEnum::C)); + let option: Option = TestEnum::decode_custom_error_to_enum(3); + assert_eq!(option, None); + } +} diff --git a/sdk/src/system_instruction.rs b/sdk/src/system_instruction.rs index d9ef0ba5b7..cf6e0f90fd 100644 --- a/sdk/src/system_instruction.rs +++ b/sdk/src/system_instruction.rs @@ -1,14 +1,29 @@ use crate::instruction::{AccountMeta, Instruction}; +use crate::instruction_processor_utils::DecodeError; use crate::pubkey::Pubkey; use crate::system_program; +use num_derive::FromPrimitive; -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, FromPrimitive)] pub enum SystemError { AccountAlreadyInUse, ResultWithNegativeLamports, SourceNotSystemAccount, } +impl DecodeError for SystemError { + fn type_of(&self) -> &'static str { + "SystemError" + } +} + +impl std::fmt::Display for SystemError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error") + } +} +impl std::error::Error for SystemError {} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum SystemInstruction { /// Create a new account diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index a7c116703e..08f2aa0110 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -15,6 +15,7 @@ clap = "2.33.0" chrono = { version = "0.4.0", features = ["serde"] } dirs = "1.0.5" log = "0.4.2" +num-traits = "0.2" serde_json = "1.0.39" solana-budget-api = { path = "../programs/budget_api", version = "0.14.0" } solana-client = { path = "../client", version = "0.14.0" } diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index e264e85d3c..30ba235b51 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -2,10 +2,13 @@ use bs58; use chrono::prelude::*; use clap::ArgMatches; use log::*; +use num_traits::FromPrimitive; use serde_json; use serde_json::json; use solana_budget_api; use solana_budget_api::budget_instruction; +use solana_budget_api::budget_state::BudgetError; +use solana_client::client_error::ClientError; use solana_client::rpc_client::{get_rpc_request_str, RpcClient}; #[cfg(not(test))] use solana_drone::drone::request_airdrop_transaction; @@ -14,12 +17,15 @@ use solana_drone::drone::DRONE_PORT; use solana_drone::drone_mock::request_airdrop_transaction; use solana_sdk::bpf_loader; use solana_sdk::hash::Hash; +use solana_sdk::instruction::InstructionError; +use solana_sdk::instruction_processor_utils::DecodeError; use solana_sdk::loader_instruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::rpc_port::DEFAULT_RPC_PORT; use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; +use solana_sdk::system_instruction::SystemError; use solana_sdk::system_transaction; -use solana_sdk::transaction::Transaction; +use solana_sdk::transaction::{Transaction, TransactionError}; use solana_vote_api::vote_instruction; use std::fs::File; use std::io::Read; @@ -448,11 +454,10 @@ fn process_deploy( 0, ); trace!("Creating program account"); - rpc_client - .send_and_confirm_transaction(&mut tx, &config.keypair) - .map_err(|_| { - WalletError::DynamicProgramError("Program allocate space failed".to_string()) - })?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + log_instruction_custom_error::(result).map_err(|_| { + WalletError::DynamicProgramError("Program allocate space failed".to_string()) + })?; trace!("Writing program data"); let write_transactions: Vec<_> = program_data @@ -499,7 +504,8 @@ fn process_pay( if timestamp == None && *witnesses == None { let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash, 0); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } else if *witnesses == None { let dt = timestamp.unwrap(); @@ -521,7 +527,8 @@ fn process_pay( lamports, ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ "signature": signature_str, @@ -551,7 +558,8 @@ fn process_pay( lamports, ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ "signature": signature_str, @@ -571,7 +579,8 @@ fn process_cancel(rpc_client: &RpcClient, config: &WalletConfig, pubkey: &Pubkey &config.keypair.pubkey(), ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } @@ -598,7 +607,8 @@ fn process_time_elapsed( let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } @@ -619,7 +629,8 @@ fn process_witness( let blockhash = rpc_client.get_recent_blockhash()?; let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } @@ -766,10 +777,39 @@ pub fn request_and_confirm_airdrop( let blockhash = rpc_client.get_recent_blockhash()?; let keypair = DroneKeypair::new_keypair(drone_addr, to_pubkey, lamports, blockhash)?; let mut tx = keypair.airdrop_transaction(); - rpc_client.send_and_confirm_transaction(&mut tx, &keypair)?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &keypair); + log_instruction_custom_error::(result)?; Ok(()) } +fn log_instruction_custom_error(result: Result) -> ProcessResult +where + E: 'static + std::error::Error + DecodeError + FromPrimitive, +{ + if result.is_err() { + let err = result.unwrap_err(); + if let ClientError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::CustomError(code), + )) = err + { + if let Some(specific_error) = E::decode_custom_error_to_enum(code) { + error!( + "{:?}: {}::{:?}", + err, + specific_error.type_of(), + specific_error + ); + Err(specific_error)? + } + } + error!("{:?}", err); + Err(err)? + } else { + Ok(result.unwrap()) + } +} + #[cfg(test)] mod tests { use super::*;