From 7e7cbec8a18596a031a2c8907387cbe12c1f17ce Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Wed, 15 Apr 2020 20:51:05 -0700 Subject: [PATCH] Passing -v/--verbose to `solana confirm` now displays the full transaction --- Cargo.lock | 3 + cli/Cargo.toml | 1 + cli/src/cli.rs | 44 +++++++++++-- cli/src/display.rs | 109 +++++++++++++++++++++++++++++- client/src/rpc_client.rs | 64 +++++++++++++++++- client/src/rpc_request.rs | 4 ++ ledger-tool/Cargo.toml | 4 +- ledger-tool/src/main.rs | 121 +++++----------------------------- transaction-status/src/lib.rs | 2 +- 9 files changed, 239 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d0abeb6e..e6febdeba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3868,6 +3868,7 @@ dependencies = [ "solana-sdk 1.2.0", "solana-stake-program 1.2.0", "solana-storage-program 1.2.0", + "solana-transaction-status 1.2.0", "solana-vote-program 1.2.0", "solana-vote-signer 1.2.0", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4249,11 +4250,13 @@ dependencies = [ "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "solana-clap-utils 1.2.0", + "solana-cli 1.2.0", "solana-ledger 1.2.0", "solana-logger 1.2.0", "solana-runtime 1.2.0", "solana-sdk 1.2.0", "solana-stake-program 1.2.0", + "solana-transaction-status 1.2.0", "solana-vote-program 1.2.0", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5318ca6f3..b747e4d3b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -40,6 +40,7 @@ solana-runtime = { path = "../runtime", version = "1.2.0" } solana-sdk = { path = "../sdk", version = "1.2.0" } solana-stake-program = { path = "../programs/stake", version = "1.2.0" } solana-storage-program = { path = "../programs/storage", version = "1.2.0" } +solana-transaction-status = { path = "../transaction-status", version = "1.2.0" } solana-vote-program = { path = "../programs/vote", version = "1.2.0" } solana-vote-signer = { path = "../vote-signer", version = "1.2.0" } thiserror = "1.0.15" diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e4a5fdfc0..76f679f5f 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1165,12 +1165,48 @@ fn process_balance( } } -fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResult { - match rpc_client.get_signature_status(&signature) { +fn process_confirm( + rpc_client: &RpcClient, + config: &CliConfig, + signature: &Signature, +) -> ProcessResult { + match rpc_client.get_signature_status_with_commitment_and_history( + &signature, + CommitmentConfig::max(), + true, + ) { Ok(status) => { if let Some(result) = status { match result { - Ok(_) => Ok("Confirmed".to_string()), + Ok(_) => { + if config.verbose { + match rpc_client.get_confirmed_transaction( + signature, + solana_transaction_status::TransactionEncoding::Binary, + ) { + Ok(confirmed_transaction) => { + println!("\nTransaction:"); + crate::display::println_transaction( + &confirmed_transaction + .transaction + .transaction + .decode() + .expect("Successful decode"), + &confirmed_transaction.transaction.meta, + " ", + ); + println!(); + Ok(format!("Confirmed in slot {}", confirmed_transaction.slot)) + } + Err(err) => Ok(format!( + "Confirmed. Unable to get confirmed transaction details: {}", + err + )), + } + } else { + Ok("Confirmed".to_string()) + } + } Err(err) => Ok(format!("Transaction failed with error: {}", err)), } } else { @@ -2063,7 +2099,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Cancel a contract by contract Pubkey CliCommand::Cancel(pubkey) => process_cancel(&rpc_client, config, &pubkey), // Confirm the last client transaction by signature - CliCommand::Confirm(signature) => process_confirm(&rpc_client, signature), + CliCommand::Confirm(signature) => process_confirm(&rpc_client, config, signature), // If client has positive balance, pay lamports to another address CliCommand::Pay(PayCommand { lamports, diff --git a/cli/src/display.rs b/cli/src/display.rs index 2fcc24ac7..a5b386e29 100644 --- a/cli/src/display.rs +++ b/cli/src/display.rs @@ -1,6 +1,10 @@ use crate::cli::SettingType; use console::style; -use solana_sdk::hash::Hash; +use solana_sdk::{ + hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize, + transaction::Transaction, +}; +use solana_transaction_status::RpcTransactionStatusMeta; use std::fmt; // Pretty print a "name value" @@ -59,3 +63,106 @@ pub fn println_signers( } println!(); } + +pub fn println_transaction( + transaction: &Transaction, + transaction_status: &Option, + prefix: &str, +) { + let message = &transaction.message; + println!("{}Recent Blockhash: {:?}", prefix, message.recent_blockhash); + for (signature_index, signature) in transaction.signatures.iter().enumerate() { + println!("{}Signature {}: {:?}", prefix, signature_index, signature); + } + println!("{}{:?}", prefix, message.header); + for (account_index, account) in message.account_keys.iter().enumerate() { + println!("{}Account {}: {:?}", prefix, account_index, account); + } + for (instruction_index, instruction) in message.instructions.iter().enumerate() { + let program_pubkey = message.account_keys[instruction.program_id_index as usize]; + println!("{}Instruction {}", prefix, instruction_index); + println!( + "{} Program: {} ({})", + prefix, program_pubkey, instruction.program_id_index + ); + for (account_index, account) in instruction.accounts.iter().enumerate() { + let account_pubkey = message.account_keys[*account as usize]; + println!( + "{} Account {}: {} ({})", + prefix, account_index, account_pubkey, account + ); + } + + let mut raw = true; + if program_pubkey == solana_vote_program::id() { + if let Ok(vote_instruction) = limited_deserialize::< + solana_vote_program::vote_instruction::VoteInstruction, + >(&instruction.data) + { + println!("{} {:?}", prefix, vote_instruction); + raw = false; + } + } else if program_pubkey == solana_stake_program::id() { + if let Ok(stake_instruction) = limited_deserialize::< + solana_stake_program::stake_instruction::StakeInstruction, + >(&instruction.data) + { + println!("{} {:?}", prefix, stake_instruction); + raw = false; + } + } else if program_pubkey == solana_sdk::system_program::id() { + if let Ok(system_instruction) = limited_deserialize::< + solana_sdk::system_instruction::SystemInstruction, + >(&instruction.data) + { + println!("{} {:?}", prefix, system_instruction); + raw = false; + } + } + + if raw { + println!("{} Data: {:?}", prefix, instruction.data); + } + } + + if let Some(transaction_status) = transaction_status { + println!( + "{}Status: {}", + prefix, + match &transaction_status.status { + Ok(_) => "Ok".into(), + Err(err) => err.to_string(), + } + ); + println!("{} Fee: {}", prefix, transaction_status.fee); + assert_eq!( + transaction_status.pre_balances.len(), + transaction_status.post_balances.len() + ); + for (i, (pre, post)) in transaction_status + .pre_balances + .iter() + .zip(transaction_status.post_balances.iter()) + .enumerate() + { + if pre == post { + println!( + "{} Account {} balance: {} SOL", + prefix, + i, + lamports_to_sol(*pre) + ); + } else { + println!( + "{} Account {} balance: {} SOL -> {} SOL", + prefix, + i, + lamports_to_sol(*pre), + lamports_to_sol(*post) + ); + } + } + } else { + println!("{}Status: Unavailable", prefix); + } +} diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 6c0f34fec..2f3feef2a 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -23,7 +23,9 @@ use solana_sdk::{ signers::Signers, transaction::{self, Transaction, TransactionError}, }; -use solana_transaction_status::{ConfirmedBlock, TransactionEncoding, TransactionStatus}; +use solana_transaction_status::{ + ConfirmedBlock, ConfirmedTransaction, TransactionEncoding, TransactionStatus, +}; use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; use std::{ error, @@ -158,6 +160,28 @@ impl RpcClient { .map(|status_meta| status_meta.status)) } + pub fn get_signature_status_with_commitment_and_history( + &self, + signature: &Signature, + commitment_config: CommitmentConfig, + search_transaction_history: bool, + ) -> ClientResult>> { + let signature_status = self.client.send( + &RpcRequest::GetSignatureStatuses, + json!([[signature.to_string()], { + "searchTransactionHistory": search_transaction_history + }]), + 5, + )?; + let result: Response>> = + serde_json::from_value(signature_status) + .map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatuses"))?; + Ok(result.value[0] + .clone() + .filter(|result| result.satisfies_commitment(commitment_config)) + .map(|status_meta| status_meta.status)) + } + pub fn get_slot(&self) -> ClientResult { self.get_slot_with_commitment(CommitmentConfig::default()) } @@ -255,6 +279,44 @@ impl RpcClient { .map_err(|err| ClientError::new_with_command(err.into(), "GetConfirmedBlocks")) } + pub fn get_confirmed_signatures_for_address( + &self, + address: &Pubkey, + start_slot: Slot, + end_slot: Slot, + ) -> ClientResult> { + let response = self + .client + .send( + &RpcRequest::GetConfirmedSignaturesForAddress, + json!([address, start_slot, end_slot]), + 0, + ) + .map_err(|err| err.into_with_command("GetConfirmedSignaturesForAddress"))?; + + serde_json::from_value(response).map_err(|err| { + ClientError::new_with_command(err.into(), "GetConfirmedSignaturesForAddress") + }) + } + + pub fn get_confirmed_transaction( + &self, + signature: &Signature, + encoding: TransactionEncoding, + ) -> ClientResult { + let response = self + .client + .send( + &RpcRequest::GetConfirmedTransaction, + json!([signature.to_string(), encoding]), + 0, + ) + .map_err(|err| err.into_with_command("GetConfirmedTransaction"))?; + + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_command(err.into(), "GetConfirmedTransaction")) + } + pub fn get_block_time(&self, slot: Slot) -> ClientResult { let response = self .client diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index bb1e578b1..c9e7e9b35 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -11,6 +11,8 @@ pub enum RpcRequest { GetClusterNodes, GetConfirmedBlock, GetConfirmedBlocks, + GetConfirmedSignaturesForAddress, + GetConfirmedTransaction, GetEpochInfo, GetEpochSchedule, GetGenesisHash, @@ -52,6 +54,8 @@ impl RpcRequest { RpcRequest::GetClusterNodes => "getClusterNodes", RpcRequest::GetConfirmedBlock => "getConfirmedBlock", RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks", + RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress", + RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction", RpcRequest::GetEpochInfo => "getEpochInfo", RpcRequest::GetEpochSchedule => "getEpochSchedule", RpcRequest::GetGenesisHash => "getGenesisHash", diff --git a/ledger-tool/Cargo.toml b/ledger-tool/Cargo.toml index 814c7c463..6e659ec07 100644 --- a/ledger-tool/Cargo.toml +++ b/ledger-tool/Cargo.toml @@ -15,12 +15,14 @@ histogram = "*" serde_json = "1.0.51" serde_yaml = "0.8.11" solana-clap-utils = { path = "../clap-utils", version = "1.2.0" } +solana-cli = { path = "../cli", version = "1.2.0" } solana-ledger = { path = "../ledger", version = "1.2.0" } solana-logger = { path = "../logger", version = "1.2.0" } solana-runtime = { path = "../runtime", version = "1.2.0" } solana-sdk = { path = "../sdk", version = "1.2.0" } -solana-vote-program = { path = "../programs/vote", version = "1.2.0" } solana-stake-program = { path = "../programs/stake", version = "1.2.0" } +solana-transaction-status = { path = "../transaction-status", version = "1.2.0" } +solana-vote-program = { path = "../programs/vote", version = "1.2.0" } tempfile = "3.1.0" [dev-dependencies] diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 00c57591d..9fbc5ff22 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -17,8 +17,8 @@ use solana_ledger::{ snapshot_utils, }; use solana_sdk::{ - clock::Slot, genesis_config::GenesisConfig, native_token::lamports_to_sol, - program_utils::limited_deserialize, pubkey::Pubkey, shred_version::compute_shred_version, + clock::Slot, genesis_config::GenesisConfig, native_token::lamports_to_sol, pubkey::Pubkey, + shred_version::compute_shred_version, }; use solana_vote_program::vote_state::VoteState; use std::{ @@ -100,112 +100,23 @@ fn output_slot( entry.transactions.len() ); for (transactions_index, transaction) in entry.transactions.iter().enumerate() { - let message = &transaction.message; println!(" Transaction {}", transactions_index); - println!(" Recent Blockhash: {:?}", message.recent_blockhash); - for (signature_index, signature) in transaction.signatures.iter().enumerate() { - println!(" Signature {}: {:?}", signature_index, signature); - } - println!(" {:?}", message.header); - for (account_index, account) in message.account_keys.iter().enumerate() { - println!(" Account {}: {:?}", account_index, account); - } - for (instruction_index, instruction) in message.instructions.iter().enumerate() - { - let program_pubkey = - message.account_keys[instruction.program_id_index as usize]; - println!(" Instruction {}", instruction_index); - println!( - " Program: {} ({})", - program_pubkey, instruction.program_id_index - ); - for (account_index, account) in instruction.accounts.iter().enumerate() { - let account_pubkey = message.account_keys[*account as usize]; - println!( - " Account {}: {} ({})", - account_index, account_pubkey, account + let transaction_status = blockstore + .read_transaction_status((transaction.signatures[0], slot)) + .unwrap_or_else(|err| { + eprintln!( + "Failed to read transaction status for {} at slot {}: {}", + transaction.signatures[0], slot, err ); - } + None + }) + .map(|transaction_status| transaction_status.into()); - let mut raw = true; - if program_pubkey == solana_vote_program::id() { - if let Ok(vote_instruction) = - limited_deserialize::< - solana_vote_program::vote_instruction::VoteInstruction, - >(&instruction.data) - { - println!(" {:?}", vote_instruction); - raw = false; - } - } else if program_pubkey == solana_stake_program::id() { - if let Ok(stake_instruction) = - limited_deserialize::< - solana_stake_program::stake_instruction::StakeInstruction, - >(&instruction.data) - { - println!(" {:?}", stake_instruction); - raw = false; - } - } else if program_pubkey == solana_sdk::system_program::id() { - if let Ok(system_instruction) = - limited_deserialize::< - solana_sdk::system_instruction::SystemInstruction, - >(&instruction.data) - { - println!(" {:?}", system_instruction); - raw = false; - } - } - - if raw { - println!(" Data: {:?}", instruction.data); - } - } - match blockstore.read_transaction_status((transaction.signatures[0], slot)) { - Ok(transaction_status) => { - if let Some(transaction_status) = transaction_status { - println!( - " Status: {}", - if transaction_status.status.is_ok() { - "Ok".into() - } else { - transaction_status.status.unwrap_err().to_string() - } - ); - println!(" Fee: {}", transaction_status.fee); - assert_eq!( - transaction_status.pre_balances.len(), - transaction_status.post_balances.len() - ); - for (i, (pre, post)) in transaction_status - .pre_balances - .iter() - .zip(transaction_status.post_balances.iter()) - .enumerate() - { - if pre == post { - println!( - " Account {} balance: {} SOL", - i, - lamports_to_sol(*pre) - ); - } else { - println!( - " Account {} balance: {} SOL -> {} SOL", - i, - lamports_to_sol(*pre), - lamports_to_sol(*post) - ); - } - } - } else { - println!(" Status: Unavailable"); - } - } - Err(err) => { - println!(" Status: {:?}", err); - } - } + solana_cli::display::println_transaction( + &transaction, + &transaction_status, + " ", + ); } } LedgerOutputMethod::Json => { diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index fd0661415..5ff0dde86 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -64,7 +64,7 @@ impl From for RpcTransactionStatusMeta { #[serde(rename_all = "camelCase")] pub struct TransactionStatus { pub slot: Slot, - pub confirmations: Option, + pub confirmations: Option, // None = rooted pub status: Result<()>, pub err: Option, }