From 7e7cbec8a18596a031a2c8907387cbe12c1f17ce Mon Sep 17 00:00:00 2001
From: Michael Vines <mvines@gmail.com>
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 1d0abeb6ed4185..e6febdeba7c8c0 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 5318ca6f397956..b747e4d3bf6f3a 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 e4a5fdfc05351c..76f679f5fe532c 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 2fcc24ac7e03a5..a5b386e2953014 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<RpcTransactionStatusMeta>,
+    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 6c0f34fec12260..2f3feef2ab873a 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<Option<transaction::Result<()>>> {
+        let signature_status = self.client.send(
+            &RpcRequest::GetSignatureStatuses,
+            json!([[signature.to_string()], {
+                "searchTransactionHistory": search_transaction_history
+            }]),
+            5,
+        )?;
+        let result: Response<Vec<Option<TransactionStatus>>> =
+            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<Slot> {
         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<Vec<Signature>> {
+        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<ConfirmedTransaction> {
+        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<UnixTimestamp> {
         let response = self
             .client
diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs
index bb1e578b1fd2c1..c9e7e9b3545298 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 814c7c463dd164..6e659ec0751e3d 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 00c57591d21b27..9fbc5ff22e9be6 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
                             );
-                        }
-
-                        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;
-                            }
-                        }
+                            None
+                        })
+                        .map(|transaction_status| transaction_status.into());
 
-                        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 fd066141585a72..5ff0dde8670101 100644
--- a/transaction-status/src/lib.rs
+++ b/transaction-status/src/lib.rs
@@ -64,7 +64,7 @@ impl From<TransactionStatusMeta> for RpcTransactionStatusMeta {
 #[serde(rename_all = "camelCase")]
 pub struct TransactionStatus {
     pub slot: Slot,
-    pub confirmations: Option<usize>,
+    pub confirmations: Option<usize>, // None = rooted
     pub status: Result<()>,
     pub err: Option<TransactionError>,
 }