Cli error cleanup 1.0 (#8834)
* Don't use move semantics if not needed (#8793) * SDK: Deboilerplate `TransportError` with thiserror * Enable interchange between `TransportError` and `ClientError` * SDK: Retval consistency between `Client` and `AsyncClient` traits * Client: Introduce/use `Result` type * Client: Remove unused `RpcResponseIn` type * Client: Rename `RpcResponse` to more appropriate `RpcResult` * Client: Death to `io::Result` return types * Client: Struct-ify `ClientError` * Client: Add optional `command` parameter to `ClientError` * RpcClient: Stop abusing `io::Error` (low-fruit) * ClientError: Use `thiserror`'s `Display` impl * Extend `RpcError`'s utility * RpcClient: Stop abusing `io::Error` (the rest) * CLI: Shim `main()` so we can `Display` format errors * claputils: format input validator errors with `Display` They are intended to be displayed to users * SDK: `thiserror` for hash and sig parse erros * Keygen: Shim main to format errors with `Display` * SDK: `thiserror` for `InstructionError` * CLI: `thiserror` for `CliError` * CLI: Format user messages with `Display` * Client: Tweak `Display` for `ClientError` * RpcClient: Improve messaging when TX cannot be confirmed * fu death io res retval * CLI/Keygen - fix shell return value on error * Tweak `InstructionError` `Display` messages as per review * Cleanup hackjob return code fix * Embrace that which you hate most * Too much... Co-authored-by: Jack May <jack@solana.com>
This commit is contained in:
@@ -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<Signature>;
|
||||
fn async_send_transaction(&self, transaction: transaction::Transaction) -> Result<Signature>;
|
||||
|
||||
/// 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<Signature>;
|
||||
) -> Result<Signature>;
|
||||
|
||||
/// 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<Signature>;
|
||||
) -> Result<Signature>;
|
||||
|
||||
/// 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<Signature>;
|
||||
) -> Result<Signature>;
|
||||
}
|
||||
|
@@ -183,7 +183,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fee_calculator_calculate_fee() {
|
||||
// Default: no fee.
|
||||
let message = Message::new(vec![]);
|
||||
let message = Message::new(&[]);
|
||||
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
|
||||
|
||||
// No signature, no fee.
|
||||
@@ -193,13 +193,13 @@ mod tests {
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let message = Message::new(vec![ix0]);
|
||||
let message = Message::new(&[ix0]);
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
|
||||
|
||||
// Two signatures, double the fee.
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
|
||||
let message = Message::new(vec![ix0, ix1]);
|
||||
let message = Message::new(&[ix0, ix1]);
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@ fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
|
||||
keys.iter().position(|k| k == key).unwrap() as u8
|
||||
}
|
||||
|
||||
fn compile_instruction(ix: Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
||||
fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
||||
let accounts: Vec<_> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
@@ -27,10 +27,8 @@ fn compile_instruction(ix: Instruction, keys: &[Pubkey]) -> CompiledInstruction
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_instructions(ixs: Vec<Instruction>, keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
||||
ixs.into_iter()
|
||||
.map(|ix| compile_instruction(ix, keys))
|
||||
.collect()
|
||||
fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
||||
ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
|
||||
}
|
||||
|
||||
/// A helper struct to collect pubkeys referenced by a set of instructions and read-only counts
|
||||
@@ -185,17 +183,17 @@ impl Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(instructions: Vec<Instruction>) -> Self {
|
||||
pub fn new(instructions: &[Instruction]) -> Self {
|
||||
Self::new_with_payer(instructions, None)
|
||||
}
|
||||
|
||||
pub fn new_with_payer(instructions: Vec<Instruction>, payer: Option<&Pubkey>) -> Self {
|
||||
pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
|
||||
let InstructionKeys {
|
||||
mut signed_keys,
|
||||
unsigned_keys,
|
||||
num_readonly_signed_accounts,
|
||||
num_readonly_unsigned_accounts,
|
||||
} = get_keys(&instructions, payer);
|
||||
} = get_keys(instructions, payer);
|
||||
let num_required_signatures = signed_keys.len() as u8;
|
||||
signed_keys.extend(&unsigned_keys);
|
||||
let instructions = compile_instructions(instructions, &signed_keys);
|
||||
@@ -220,7 +218,7 @@ impl Message {
|
||||
&nonce_authority_pubkey,
|
||||
);
|
||||
instructions.insert(0, nonce_ix);
|
||||
Self::new_with_payer(instructions, payer)
|
||||
Self::new_with_payer(&instructions, payer)
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
@@ -426,11 +424,11 @@ mod tests {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||
let message = Message::new(vec![ix]);
|
||||
let message = Message::new(&[ix]);
|
||||
assert_eq!(message.header.num_required_signatures, 0);
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||
let message = Message::new(vec![ix]);
|
||||
let message = Message::new(&[ix]);
|
||||
assert_eq!(message.header.num_required_signatures, 1);
|
||||
}
|
||||
|
||||
@@ -463,7 +461,7 @@ mod tests {
|
||||
let id0 = Pubkey::default();
|
||||
let keypair1 = Keypair::new();
|
||||
let id1 = keypair1.pubkey();
|
||||
let message = Message::new(vec![
|
||||
let message = Message::new(&[
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new(id1, true)]),
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, false)]),
|
||||
@@ -489,11 +487,11 @@ mod tests {
|
||||
let id0 = Pubkey::default();
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||
let message = Message::new_with_payer(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 1);
|
||||
|
||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||
let message = Message::new_with_payer(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 2);
|
||||
|
||||
let ix = Instruction::new(
|
||||
@@ -501,7 +499,7 @@ mod tests {
|
||||
&0,
|
||||
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
||||
);
|
||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||
let message = Message::new_with_payer(&[ix], Some(&payer));
|
||||
assert_eq!(message.header.num_required_signatures, 2);
|
||||
}
|
||||
|
||||
@@ -528,7 +526,7 @@ mod tests {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id1 = Pubkey::new_rand();
|
||||
let id = Pubkey::new_rand();
|
||||
let message = Message::new(vec![
|
||||
let message = Message::new(&[
|
||||
Instruction::new(program_id0, &0, vec![AccountMeta::new(id, false)]),
|
||||
Instruction::new(program_id1, &0, vec![AccountMeta::new(id, true)]),
|
||||
]);
|
||||
@@ -571,7 +569,7 @@ mod tests {
|
||||
let id1 = Pubkey::new_rand();
|
||||
let id2 = Pubkey::new_rand();
|
||||
let id3 = Pubkey::new_rand();
|
||||
let message = Message::new(vec![
|
||||
let message = Message::new(&[
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
|
||||
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id2, false)]),
|
||||
|
@@ -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,
|
||||
}
|
||||
|
||||
|
@@ -92,7 +92,7 @@ impl Transaction {
|
||||
}
|
||||
|
||||
pub fn new_with_payer(instructions: Vec<Instruction>, payer: Option<&Pubkey>) -> Self {
|
||||
let message = Message::new_with_payer(instructions, payer);
|
||||
let message = Message::new_with_payer(&instructions, payer);
|
||||
Self::new_unsigned(message)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ impl Transaction {
|
||||
signing_keypairs: &T,
|
||||
recent_blockhash: Hash,
|
||||
) -> Self {
|
||||
let message = Message::new_with_payer(instructions, payer);
|
||||
let message = Message::new_with_payer(&instructions, payer);
|
||||
Self::new(signing_keypairs, message, recent_blockhash)
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ impl Transaction {
|
||||
}
|
||||
|
||||
pub fn new_unsigned_instructions(instructions: Vec<Instruction>) -> Self {
|
||||
let message = Message::new(instructions);
|
||||
let message = Message::new(&instructions);
|
||||
Self::new_unsigned(message)
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ impl Transaction {
|
||||
instructions: Vec<Instruction>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Transaction {
|
||||
let message = Message::new(instructions);
|
||||
let message = Message::new(&instructions);
|
||||
Self::new(from_keypairs, message, recent_blockhash)
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ mod tests {
|
||||
AccountMeta::new(to, false),
|
||||
];
|
||||
let instruction = Instruction::new(program_id, &(1u8, 2u8, 3u8), account_metas);
|
||||
let message = Message::new(vec![instruction]);
|
||||
let message = Message::new(&[instruction]);
|
||||
Transaction::new(&[&keypair], message, Hash::default())
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ mod tests {
|
||||
let expected_instruction_size = 1 + 1 + ix.accounts.len() + 1 + expected_data_size;
|
||||
assert_eq!(expected_instruction_size, 17);
|
||||
|
||||
let message = Message::new(vec![ix]);
|
||||
let message = Message::new(&[ix]);
|
||||
assert_eq!(
|
||||
serialized_size(&message.instructions[0]).unwrap() as usize,
|
||||
expected_instruction_size,
|
||||
|
@@ -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<io::Error> for TransportError {
|
||||
fn from(err: io::Error) -> TransportError {
|
||||
TransportError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionError> for TransportError {
|
||||
fn from(err: TransactionError) -> TransportError {
|
||||
TransportError::TransactionError(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TransportError>;
|
||||
|
Reference in New Issue
Block a user