Implement OutputFormat for confirm in Cli and ledger-tool bigtable (#15528)

* Add CliTransaction struct

* Impl DisplayFormat for decode-transaction

* Add block-time to transaction println, writeln

* Impl DisplayFormat for confirm

* Use DisplayFormat in ledger-tool bigtable confirm
This commit is contained in:
Tyera Eulberg
2021-02-25 14:15:52 -07:00
committed by GitHub
parent f59ec3d1a7
commit d521dfe63c
7 changed files with 225 additions and 94 deletions

View File

@ -26,10 +26,13 @@ use {
pubkey::Pubkey, pubkey::Pubkey,
signature::Signature, signature::Signature,
stake_history::StakeHistoryEntry, stake_history::StakeHistoryEntry,
transaction::Transaction, transaction::{Transaction, TransactionError},
}, },
solana_stake_program::stake_state::{Authorized, Lockup}, solana_stake_program::stake_state::{Authorized, Lockup},
solana_transaction_status::EncodedConfirmedBlock, solana_transaction_status::{
EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
UiTransactionStatusMeta,
},
solana_vote_program::{ solana_vote_program::{
authorized_voters::AuthorizedVoters, authorized_voters::AuthorizedVoters,
vote_state::{BlockTimestamp, Lockout, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY}, vote_state::{BlockTimestamp, Lockout, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY},
@ -1711,7 +1714,8 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum CliSignatureVerificationStatus { pub enum CliSignatureVerificationStatus {
None, None,
Pass, Pass,
@ -1743,6 +1747,7 @@ impl fmt::Display for CliSignatureVerificationStatus {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliBlock { pub struct CliBlock {
#[serde(flatten)] #[serde(flatten)]
pub encoded_confirmed_block: EncodedConfirmedBlock, pub encoded_confirmed_block: EncodedConfirmedBlock,
@ -1830,12 +1835,103 @@ impl fmt::Display for CliBlock {
&transaction_with_meta.meta, &transaction_with_meta.meta,
" ", " ",
None, None,
None,
)?; )?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliTransaction {
pub transaction: EncodedTransaction,
pub meta: Option<UiTransactionStatusMeta>,
pub block_time: Option<UnixTimestamp>,
#[serde(skip_serializing)]
pub slot: Option<Slot>,
#[serde(skip_serializing)]
pub decoded_transaction: Transaction,
#[serde(skip_serializing)]
pub prefix: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub sigverify_status: Vec<CliSignatureVerificationStatus>,
}
impl QuietDisplay for CliTransaction {}
impl VerboseDisplay for CliTransaction {}
impl fmt::Display for CliTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln_transaction(
f,
&self.decoded_transaction,
&self.meta,
&self.prefix,
if !self.sigverify_status.is_empty() {
Some(&self.sigverify_status)
} else {
None
},
self.block_time,
)
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliTransactionConfirmation {
pub confirmation_status: Option<TransactionConfirmationStatus>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub transaction: Option<CliTransaction>,
#[serde(skip_serializing)]
pub get_transaction_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub err: Option<TransactionError>,
}
impl QuietDisplay for CliTransactionConfirmation {}
impl VerboseDisplay for CliTransactionConfirmation {
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
if let Some(transaction) = &self.transaction {
writeln!(
w,
"\nTransaction executed in slot {}:",
transaction.slot.expect("slot should exist")
)?;
write!(w, "{}", transaction)?;
} else if let Some(confirmation_status) = &self.confirmation_status {
if confirmation_status != &TransactionConfirmationStatus::Finalized {
writeln!(w)?;
writeln!(
w,
"Unable to get finalized transaction details: not yet finalized"
)?;
} else if let Some(err) = &self.get_transaction_error {
writeln!(w)?;
writeln!(w, "Unable to get finalized transaction details: {}", err)?;
}
}
writeln!(w)?;
write!(w, "{}", self)
}
}
impl fmt::Display for CliTransactionConfirmation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.confirmation_status {
None => write!(f, "Not found"),
Some(confirmation_status) => {
if let Some(err) = &self.err {
write!(f, "Transaction failed: {}", err)
} else {
write!(f, "{:?}", confirmation_status)
}
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,6 +1,6 @@
use { use {
crate::cli_output::CliSignatureVerificationStatus, crate::cli_output::CliSignatureVerificationStatus,
chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc}, chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
console::style, console::style,
indicatif::{ProgressBar, ProgressStyle}, indicatif::{ProgressBar, ProgressStyle},
solana_sdk::{ solana_sdk::{
@ -131,8 +131,17 @@ pub fn write_transaction<W: io::Write>(
transaction_status: &Option<UiTransactionStatusMeta>, transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str, prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>, sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) -> io::Result<()> { ) -> io::Result<()> {
let message = &transaction.message; let message = &transaction.message;
if let Some(block_time) = block_time {
writeln!(
w,
"{}Block Time: {:?}",
prefix,
Local.timestamp(block_time, 0)
)?;
}
writeln!( writeln!(
w, w,
"{}Recent Blockhash: {:?}", "{}Recent Blockhash: {:?}",
@ -277,6 +286,7 @@ pub fn println_transaction(
transaction_status: &Option<UiTransactionStatusMeta>, transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str, prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>, sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) { ) {
let mut w = Vec::new(); let mut w = Vec::new();
if write_transaction( if write_transaction(
@ -285,6 +295,7 @@ pub fn println_transaction(
transaction_status, transaction_status,
prefix, prefix,
sigverify_status, sigverify_status,
block_time,
) )
.is_ok() .is_ok()
{ {
@ -300,6 +311,7 @@ pub fn writeln_transaction(
transaction_status: &Option<UiTransactionStatusMeta>, transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str, prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>, sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) -> fmt::Result { ) -> fmt::Result {
let mut w = Vec::new(); let mut w = Vec::new();
if write_transaction( if write_transaction(
@ -308,6 +320,7 @@ pub fn writeln_transaction(
transaction_status, transaction_status,
prefix, prefix,
sigverify_status, sigverify_status,
block_time,
) )
.is_ok() .is_ok()
{ {

View File

@ -17,8 +17,9 @@ use solana_clap_utils::{
offline::*, offline::*,
}; };
use solana_cli_output::{ use solana_cli_output::{
display::{build_balance_message, println_name_value, println_transaction}, display::{build_balance_message, println_name_value},
return_signers, CliAccount, CliSignature, CliSignatureVerificationStatus, OutputFormat, return_signers, CliAccount, CliSignature, CliSignatureVerificationStatus, CliTransaction,
CliTransactionConfirmation, OutputFormat,
}; };
use solana_client::{ use solana_client::{
blockhash_query::BlockhashQuery, blockhash_query::BlockhashQuery,
@ -50,9 +51,7 @@ use solana_stake_program::{
stake_instruction::LockupArgs, stake_instruction::LockupArgs,
stake_state::{Lockup, StakeAuthorize}, stake_state::{Lockup, StakeAuthorize},
}; };
use solana_transaction_status::{ use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding};
EncodedTransaction, TransactionConfirmationStatus, UiTransactionEncoding,
};
use solana_vote_program::vote_state::VoteAuthorize; use solana_vote_program::vote_state::VoteAuthorize;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -1005,60 +1004,72 @@ fn process_confirm(
) -> ProcessResult { ) -> ProcessResult {
match rpc_client.get_signature_statuses_with_history(&[*signature]) { match rpc_client.get_signature_statuses_with_history(&[*signature]) {
Ok(status) => { Ok(status) => {
if let Some(transaction_status) = &status.value[0] { let cli_transaction = if let Some(transaction_status) = &status.value[0] {
let mut transaction = None;
let mut get_transaction_error = None;
if config.verbose { if config.verbose {
match rpc_client match rpc_client
.get_confirmed_transaction(signature, UiTransactionEncoding::Base64) .get_confirmed_transaction(signature, UiTransactionEncoding::Base64)
{ {
Ok(confirmed_transaction) => { Ok(confirmed_transaction) => {
println!( let decoded_transaction = confirmed_transaction
"\nTransaction executed in slot {}:", .transaction
confirmed_transaction.slot .transaction
); .decode()
println_transaction( .expect("Successful decode");
&confirmed_transaction let json_transaction = EncodedTransaction::encode(
.transaction decoded_transaction.clone(),
.transaction UiTransactionEncoding::Json,
.decode()
.expect("Successful decode"),
&confirmed_transaction.transaction.meta,
" ",
None,
); );
transaction = Some(CliTransaction {
transaction: json_transaction,
meta: confirmed_transaction.transaction.meta,
block_time: confirmed_transaction.block_time,
slot: Some(confirmed_transaction.slot),
decoded_transaction,
prefix: " ".to_string(),
sigverify_status: vec![],
});
} }
Err(err) => { Err(err) => {
if transaction_status.confirmation_status() get_transaction_error = Some(format!("{:?}", err));
!= TransactionConfirmationStatus::Finalized
{
println!();
println!("Unable to get finalized transaction details: not yet finalized")
} else {
println!();
println!("Unable to get finalized transaction details: {}", err)
}
} }
} }
println!();
} }
CliTransactionConfirmation {
if let Some(err) = &transaction_status.err { confirmation_status: Some(transaction_status.confirmation_status()),
Ok(format!("Transaction failed: {}", err)) transaction,
} else { get_transaction_error,
Ok(format!("{:?}", transaction_status.confirmation_status())) err: transaction_status.err.clone(),
} }
} else { } else {
Ok("Not found".to_string()) CliTransactionConfirmation {
} confirmation_status: None,
transaction: None,
get_transaction_error: None,
err: None,
}
};
Ok(config.output_format.formatted_string(&cli_transaction))
} }
Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {}", err)).into()), Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {}", err)).into()),
} }
} }
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn process_decode_transaction(transaction: &Transaction) -> ProcessResult { fn process_decode_transaction(config: &CliConfig, transaction: &Transaction) -> ProcessResult {
let sig_stats = CliSignatureVerificationStatus::verify_transaction(&transaction); let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&transaction);
println_transaction(transaction, &None, "", Some(&sig_stats)); let decode_transaction = CliTransaction {
Ok("".to_string()) decoded_transaction: transaction.clone(),
transaction: EncodedTransaction::encode(transaction.clone(), UiTransactionEncoding::Json),
meta: None,
block_time: None,
slot: None,
prefix: "".to_string(),
sigverify_status,
};
Ok(config.output_format.formatted_string(&decode_transaction))
} }
fn process_show_account( fn process_show_account(
@ -1745,7 +1756,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
} => process_balance(&rpc_client, config, &pubkey, *use_lamports_unit), } => process_balance(&rpc_client, config, &pubkey, *use_lamports_unit),
// Confirm the last client transaction by signature // Confirm the last client transaction by signature
CliCommand::Confirm(signature) => process_confirm(&rpc_client, config, signature), CliCommand::Confirm(signature) => process_confirm(&rpc_client, config, signature),
CliCommand::DecodeTransaction(transaction) => process_decode_transaction(transaction), CliCommand::DecodeTransaction(transaction) => {
process_decode_transaction(config, transaction)
}
CliCommand::ResolveSigner(path) => { CliCommand::ResolveSigner(path) => {
if let Some(path) = path { if let Some(path) = path {
Ok(path.to_string()) Ok(path.to_string())
@ -2189,6 +2202,7 @@ mod tests {
signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Keypair, Presigner}, signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Keypair, Presigner},
transaction::TransactionError, transaction::TransactionError,
}; };
use solana_transaction_status::TransactionConfirmationStatus;
use std::path::PathBuf; use std::path::PathBuf;
fn make_tmp_path(name: &str) -> String { fn make_tmp_path(name: &str) -> String {

View File

@ -1785,6 +1785,7 @@ pub fn process_transaction_history(
&confirmed_transaction.transaction.meta, &confirmed_transaction.transaction.meta,
" ", " ",
None, None,
None,
); );
} }
Err(err) => println!(" Unable to get confirmed transaction details: {}", err), Err(err) => println!(" Unable to get confirmed transaction details: {}", err),

View File

@ -4,10 +4,13 @@ use solana_clap_utils::{
input_parsers::pubkey_of, input_parsers::pubkey_of,
input_validators::{is_slot, is_valid_pubkey}, input_validators::{is_slot, is_valid_pubkey},
}; };
use solana_cli_output::{display::println_transaction, CliBlock, OutputFormat}; use solana_cli_output::{
display::println_transaction, CliBlock, CliTransaction, CliTransactionConfirmation,
OutputFormat,
};
use solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType}; use solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType};
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature}; use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature};
use solana_transaction_status::{ConfirmedBlock, UiTransactionEncoding}; use solana_transaction_status::{ConfirmedBlock, EncodedTransaction, UiTransactionEncoding};
use std::{ use std::{
path::Path, path::Path,
process::exit, process::exit,
@ -75,37 +78,48 @@ async fn blocks(starting_slot: Slot, limit: usize) -> Result<(), Box<dyn std::er
Ok(()) Ok(())
} }
async fn confirm(signature: &Signature, verbose: bool) -> Result<(), Box<dyn std::error::Error>> { async fn confirm(
signature: &Signature,
verbose: bool,
output_format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None) let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None)
.await .await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?; .map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
let transaction_status = bigtable.get_signature_status(signature).await?; let transaction_status = bigtable.get_signature_status(signature).await?;
let mut transaction = None;
let mut get_transaction_error = None;
if verbose { if verbose {
match bigtable.get_confirmed_transaction(signature).await { match bigtable.get_confirmed_transaction(signature).await {
Ok(Some(confirmed_transaction)) => { Ok(Some(confirmed_transaction)) => {
println!( transaction = Some(CliTransaction {
"\nTransaction executed in slot {}:", transaction: EncodedTransaction::encode(
confirmed_transaction.slot confirmed_transaction.transaction.transaction.clone(),
); UiTransactionEncoding::Json,
println_transaction( ),
&confirmed_transaction.transaction.transaction, meta: confirmed_transaction.transaction.meta.map(|m| m.into()),
&confirmed_transaction.transaction.meta.map(|m| m.into()), block_time: confirmed_transaction.block_time,
" ", slot: Some(confirmed_transaction.slot),
None, decoded_transaction: confirmed_transaction.transaction.transaction,
); prefix: " ".to_string(),
sigverify_status: vec![],
});
}
Ok(None) => {}
Err(err) => {
get_transaction_error = Some(format!("{:?}", err));
} }
Ok(None) => println!("Finalized transaction details not available"),
Err(err) => println!("Unable to get finalized transaction details: {}", err),
} }
println!();
}
if let Some(err) = &transaction_status.err {
println!("Transaction failed: {}", err);
} else {
println!("{:?}", transaction_status.confirmation_status());
} }
let cli_transaction = CliTransactionConfirmation {
confirmation_status: Some(transaction_status.confirmation_status()),
transaction,
get_transaction_error,
err: transaction_status.err.clone(),
};
println!("{}", output_format.formatted_string(&cli_transaction));
Ok(()) Ok(())
} }
@ -174,6 +188,7 @@ pub async fn transaction_history(
&transaction_with_meta.meta.clone().map(|m| m.into()), &transaction_with_meta.meta.clone().map(|m| m.into()),
" ", " ",
None, None,
None,
); );
} }
} }
@ -301,13 +316,6 @@ impl BigTableSubCommand for App<'_, '_> {
.required(true) .required(true)
.index(1) .index(1)
.help("The transaction signature to confirm"), .help("The transaction signature to confirm"),
)
.arg(
Arg::with_name("verbose")
.short("v")
.long("verbose")
.takes_value(false)
.help("Show additional information"),
), ),
) )
.subcommand( .subcommand(
@ -366,13 +374,6 @@ impl BigTableSubCommand for App<'_, '_> {
.long("show-transactions") .long("show-transactions")
.takes_value(false) .takes_value(false)
.help("Display the full transactions"), .help("Display the full transactions"),
)
.arg(
Arg::with_name("verbose")
.short("v")
.long("verbose")
.takes_value(false)
.help("Show additional information"),
), ),
), ),
) )
@ -382,6 +383,7 @@ impl BigTableSubCommand for App<'_, '_> {
pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) { pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
let runtime = tokio::runtime::Runtime::new().unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap();
let verbose = matches.is_present("verbose");
let output_format = matches let output_format = matches
.value_of("output_format") .value_of("output_format")
.map(|value| match value { .map(|value| match value {
@ -389,7 +391,11 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
"json-compact" => OutputFormat::JsonCompact, "json-compact" => OutputFormat::JsonCompact,
_ => unreachable!(), _ => unreachable!(),
}) })
.unwrap_or(OutputFormat::Display); .unwrap_or(if verbose {
OutputFormat::DisplayVerbose
} else {
OutputFormat::Display
});
let future = match matches.subcommand() { let future = match matches.subcommand() {
("upload", Some(arg_matches)) => { ("upload", Some(arg_matches)) => {
@ -425,9 +431,8 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
.unwrap() .unwrap()
.parse() .parse()
.expect("Invalid signature"); .expect("Invalid signature");
let verbose = arg_matches.is_present("verbose");
runtime.block_on(confirm(&signature, verbose)) runtime.block_on(confirm(&signature, verbose, output_format))
} }
("transaction-history", Some(arg_matches)) => { ("transaction-history", Some(arg_matches)) => {
let address = pubkey_of(arg_matches, "address").unwrap(); let address = pubkey_of(arg_matches, "address").unwrap();
@ -439,7 +444,6 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
let until = arg_matches let until = arg_matches
.value_of("until") .value_of("until")
.map(|signature| signature.parse().expect("Invalid signature")); .map(|signature| signature.parse().expect("Invalid signature"));
let verbose = arg_matches.is_present("verbose");
let show_transactions = arg_matches.is_present("show_transactions"); let show_transactions = arg_matches.is_present("show_transactions");
runtime.block_on(transaction_history( runtime.block_on(transaction_history(

View File

@ -123,6 +123,7 @@ fn output_entry(
&transaction_status, &transaction_status,
" ", " ",
None, None,
None,
); );
} }
} }
@ -852,6 +853,15 @@ fn main() {
.possible_values(&["json", "json-compact"]) .possible_values(&["json", "json-compact"])
.help("Return information in specified output format, currently only available for bigtable subcommands"), .help("Return information in specified output format, currently only available for bigtable subcommands"),
) )
.arg(
Arg::with_name("verbose")
.short("v")
.long("verbose")
.global(true)
.multiple(true)
.takes_value(false)
.help("Show additional information where supported"),
)
.bigtable_subcommand() .bigtable_subcommand()
.subcommand( .subcommand(
SubCommand::with_name("print") SubCommand::with_name("print")
@ -873,14 +883,6 @@ fn main() {
.takes_value(false) .takes_value(false)
.help("Only print root slots"), .help("Only print root slots"),
) )
.arg(
Arg::with_name("verbose")
.long("verbose")
.short("v")
.multiple(true)
.takes_value(false)
.help("How verbose to print the ledger contents."),
)
) )
.subcommand( .subcommand(
SubCommand::with_name("copy") SubCommand::with_name("copy")
@ -1349,7 +1351,7 @@ fn main() {
let num_slots = value_t!(arg_matches, "num_slots", Slot).ok(); let num_slots = value_t!(arg_matches, "num_slots", Slot).ok();
let allow_dead_slots = arg_matches.is_present("allow_dead_slots"); let allow_dead_slots = arg_matches.is_present("allow_dead_slots");
let only_rooted = arg_matches.is_present("only_rooted"); let only_rooted = arg_matches.is_present("only_rooted");
let verbose = arg_matches.occurrences_of("verbose"); let verbose = matches.occurrences_of("verbose");
output_ledger( output_ledger(
open_blockstore( open_blockstore(
&ledger_path, &ledger_path,

View File

@ -384,10 +384,11 @@ fn execute_transactions(bank: &Bank, txs: &[Transaction]) -> Vec<ConfirmedTransa
} }
fn print_confirmed_tx(name: &str, confirmed_tx: ConfirmedTransaction) { fn print_confirmed_tx(name: &str, confirmed_tx: ConfirmedTransaction) {
let block_time = confirmed_tx.block_time;
let tx = confirmed_tx.transaction.transaction.clone(); let tx = confirmed_tx.transaction.transaction.clone();
let encoded = confirmed_tx.encode(UiTransactionEncoding::JsonParsed); let encoded = confirmed_tx.encode(UiTransactionEncoding::JsonParsed);
println!("EXECUTE {} (slot {})", name, encoded.slot); println!("EXECUTE {} (slot {})", name, encoded.slot);
println_transaction(&tx, &encoded.transaction.meta, " ", None); println_transaction(&tx, &encoded.transaction.meta, " ", None, block_time);
} }
#[test] #[test]