Custom error decoder (#3783)

automerge
This commit is contained in:
Tyera Eulberg
2019-04-25 11:29:44 -06:00
committed by Grimes
parent b67b0bff05
commit 5a79676b8a
17 changed files with 218 additions and 33 deletions

7
Cargo.lock generated
View File

@ -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",

View File

@ -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<io::Error> for ClientError {
fn from(err: io::Error) -> ClientError {
ClientError::Io(err)
}
}
impl From<reqwest::Error> for ClientError {
fn from(err: reqwest::Error) -> ClientError {
ClientError::Reqwest(err)
}
}
impl From<rpc_request::RpcError> for ClientError {
fn from(err: rpc_request::RpcError) -> ClientError {
ClientError::RpcError(err)
}
}
impl From<serde_json::error::Error> for ClientError {
fn from(err: serde_json::error::Error) -> ClientError {
ClientError::SerdeJson(err)
}
}
impl From<TransactionError> for ClientError {
fn from(err: TransactionError) -> ClientError {
ClientError::TransactionError(err)
}
}

View File

@ -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<serde_json::Value>,
retries: usize,
) -> Result<serde_json::Value, Box<dyn std::error::Error>>;
) -> Result<serde_json::Value, ClientError>;
}

View File

@ -1,3 +1,4 @@
pub mod client_error;
mod generic_rpc_client_request;
pub mod mock_rpc_client_request;
pub mod rpc_client;

View File

@ -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<serde_json::Value>,
_retries: usize,
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
) -> Result<serde_json::Value, ClientError> {
if self.url == "fails" {
return Ok(Value::Null);
}

View File

@ -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<String, Box<dyn error::Error>> {
pub fn send_transaction(&self, transaction: &Transaction) -> Result<String, ClientError> {
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<Option<transaction::Result<()>>, Box<dyn error::Error>> {
) -> Result<Option<transaction::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<String, Box<dyn error::Error>> {
) -> Result<String, ClientError> {
let mut send_retries = 5;
loop {
let mut status_retries = 4;
@ -117,6 +115,9 @@ impl RpcClient {
send_retries - 1
};
if send_retries == 0 {
if status.is_some() {
status.unwrap()?
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Transaction {:?} failed: {:?}", signature_str, status),
@ -124,6 +125,7 @@ impl RpcClient {
}
}
}
}
pub fn send_and_confirm_transactions(
&self,
@ -201,7 +203,7 @@ impl RpcClient {
&self,
tx: &mut Transaction,
signer_key: &T,
) -> Result<(), Box<dyn error::Error>> {
) -> 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<Value>,
retries: usize,
) -> Result<Value, Box<dyn error::Error>> {
) -> Result<Value, ClientError> {
self.client.send(request, params, retries)
}
}

View File

@ -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<serde_json::Value>,
mut retries: usize,
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
) -> Result<serde_json::Value, ClientError> {
// Concurrent requests are not supported so reuse the same request id for all requests
let request_id = 1;

View File

@ -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)

View File

@ -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" }

View File

@ -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<T> DecodeError<T> 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,

View File

@ -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" }

View File

@ -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<T> DecodeError<T> 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")

View File

@ -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"

View File

@ -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<E> {
fn decode_custom_error_to_enum(int: u32) -> Option<E>
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<T> DecodeError<T> 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> = TestEnum::decode_custom_error_to_enum(3);
assert_eq!(option, None);
}
}

View File

@ -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<T> DecodeError<T> 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

View File

@ -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" }

View File

@ -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,9 +454,8 @@ fn process_deploy(
0,
);
trace!("Creating program account");
rpc_client
.send_and_confirm_transaction(&mut tx, &config.keypair)
.map_err(|_| {
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair);
log_instruction_custom_error::<SystemError>(result).map_err(|_| {
WalletError::DynamicProgramError("Program allocate space failed".to_string())
})?;
@ -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::<SystemError>(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::<BudgetError>(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::<BudgetError>(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::<BudgetError>(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::<BudgetError>(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::<BudgetError>(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::<SystemError>(result)?;
Ok(())
}
fn log_instruction_custom_error<E>(result: Result<String, ClientError>) -> ProcessResult
where
E: 'static + std::error::Error + DecodeError<E> + 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::*;