Refactor: Split up cli transaction display methods (#23547)
This commit is contained in:
parent
b444836a97
commit
021135978d
|
@ -4540,6 +4540,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 2.33.3",
|
"clap 2.33.3",
|
||||||
"console",
|
"console",
|
||||||
|
"ed25519-dalek",
|
||||||
"humantime",
|
"humantime",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -27,5 +27,8 @@ solana-transaction-status = { path = "../transaction-status", version = "=1.10.2
|
||||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
||||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ed25519-dalek = "=1.0.1"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
|
|
|
@ -2335,7 +2335,7 @@ impl fmt::Display for CliBlock {
|
||||||
writeln_transaction(
|
writeln_transaction(
|
||||||
f,
|
f,
|
||||||
&transaction_with_meta.transaction.decode().unwrap(),
|
&transaction_with_meta.transaction.decode().unwrap(),
|
||||||
&transaction_with_meta.meta,
|
transaction_with_meta.meta.as_ref(),
|
||||||
" ",
|
" ",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
@ -2369,7 +2369,7 @@ impl fmt::Display for CliTransaction {
|
||||||
writeln_transaction(
|
writeln_transaction(
|
||||||
f,
|
f,
|
||||||
&self.decoded_transaction,
|
&self.decoded_transaction,
|
||||||
&self.meta,
|
self.meta.as_ref(),
|
||||||
&self.prefix,
|
&self.prefix,
|
||||||
if !self.sigverify_status.is_empty() {
|
if !self.sigverify_status.is_empty() {
|
||||||
Some(&self.sigverify_status)
|
Some(&self.sigverify_status)
|
||||||
|
|
|
@ -4,10 +4,17 @@ use {
|
||||||
console::style,
|
console::style,
|
||||||
indicatif::{ProgressBar, ProgressStyle},
|
indicatif::{ProgressBar, ProgressStyle},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol,
|
clock::UnixTimestamp,
|
||||||
program_utils::limited_deserialize, pubkey::Pubkey, stake, transaction::Transaction,
|
hash::Hash,
|
||||||
|
instruction::CompiledInstruction,
|
||||||
|
native_token::lamports_to_sol,
|
||||||
|
program_utils::limited_deserialize,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::Signature,
|
||||||
|
stake,
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
},
|
},
|
||||||
solana_transaction_status::UiTransactionStatusMeta,
|
solana_transaction_status::{Rewards, UiTransactionStatusMeta},
|
||||||
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
|
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
|
||||||
std::{collections::HashMap, fmt, io},
|
std::{collections::HashMap, fmt, io},
|
||||||
};
|
};
|
||||||
|
@ -131,22 +138,28 @@ pub fn println_signers(
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_account_mode(message: &Message, index: usize) -> String {
|
struct CliAccountMeta {
|
||||||
|
is_signer: bool,
|
||||||
|
is_writable: bool,
|
||||||
|
is_invoked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_account_mode(meta: CliAccountMeta) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}r{}{}", // accounts are always readable...
|
"{}r{}{}", // accounts are always readable...
|
||||||
if message.is_signer(index) {
|
if meta.is_signer {
|
||||||
"s" // stands for signer
|
"s" // stands for signer
|
||||||
} else {
|
} else {
|
||||||
"-"
|
"-"
|
||||||
},
|
},
|
||||||
if message.is_writable(index) {
|
if meta.is_writable {
|
||||||
"w" // comment for consistent rust fmt (no joking; lol)
|
"w" // comment for consistent rust fmt (no joking; lol)
|
||||||
} else {
|
} else {
|
||||||
"-"
|
"-"
|
||||||
},
|
},
|
||||||
// account may be executable on-chain while not being
|
// account may be executable on-chain while not being
|
||||||
// designated as a program-id in the message
|
// designated as a program-id in the message
|
||||||
if message.maybe_executable(index) {
|
if meta.is_invoked {
|
||||||
"x"
|
"x"
|
||||||
} else {
|
} else {
|
||||||
// programs to be executed via CPI cannot be identified as
|
// programs to be executed via CPI cannot be identified as
|
||||||
|
@ -156,202 +169,66 @@ fn format_account_mode(message: &Message, index: usize) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_transaction<W: io::Write>(
|
fn write_transaction<W: io::Write>(
|
||||||
w: &mut W,
|
w: &mut W,
|
||||||
transaction: &Transaction,
|
transaction: &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>,
|
block_time: Option<UnixTimestamp>,
|
||||||
|
timezone: CliTimezone,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
|
write_block_time(w, block_time, timezone, prefix)?;
|
||||||
|
|
||||||
let message = &transaction.message;
|
let message = &transaction.message;
|
||||||
if let Some(block_time) = block_time {
|
write_recent_blockhash(w, &message.recent_blockhash, prefix)?;
|
||||||
writeln!(
|
write_signatures(w, &transaction.signatures, sigverify_status, prefix)?;
|
||||||
w,
|
|
||||||
"{}Block Time: {:?}",
|
|
||||||
prefix,
|
|
||||||
Local.timestamp(block_time, 0)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{}Recent Blockhash: {:?}",
|
|
||||||
prefix, message.recent_blockhash
|
|
||||||
)?;
|
|
||||||
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
|
||||||
sigverify_status
|
|
||||||
.iter()
|
|
||||||
.map(|s| format!(" ({})", s))
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
vec!["".to_string(); transaction.signatures.len()]
|
|
||||||
};
|
|
||||||
for (signature_index, (signature, sigverify_status)) in transaction
|
|
||||||
.signatures
|
|
||||||
.iter()
|
|
||||||
.zip(&sigverify_statuses)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{}Signature {}: {:?}{}",
|
|
||||||
prefix, signature_index, signature, sigverify_status,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
let mut fee_payer_index = None;
|
let mut fee_payer_index = None;
|
||||||
for (account_index, account) in message.account_keys.iter().enumerate() {
|
for (account_index, account) in message.account_keys.iter().enumerate() {
|
||||||
if fee_payer_index.is_none() && message.is_non_loader_key(account_index) {
|
if fee_payer_index.is_none() && message.is_non_loader_key(account_index) {
|
||||||
fee_payer_index = Some(account_index)
|
fee_payer_index = Some(account_index)
|
||||||
}
|
}
|
||||||
writeln!(
|
|
||||||
|
let account_meta = CliAccountMeta {
|
||||||
|
is_signer: message.is_signer(account_index),
|
||||||
|
is_writable: message.is_writable(account_index),
|
||||||
|
is_invoked: message.maybe_executable(account_index),
|
||||||
|
};
|
||||||
|
|
||||||
|
write_account(
|
||||||
w,
|
w,
|
||||||
"{}Account {}: {} {}{}",
|
|
||||||
prefix,
|
|
||||||
account_index,
|
account_index,
|
||||||
format_account_mode(message, account_index),
|
|
||||||
account,
|
account,
|
||||||
if Some(account_index) == fee_payer_index {
|
format_account_mode(account_meta),
|
||||||
" (fee payer)"
|
Some(account_index) == fee_payer_index,
|
||||||
} else {
|
prefix,
|
||||||
""
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||||
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
||||||
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
let instruction_accounts = instruction.accounts.iter().map(|account_index| {
|
||||||
writeln!(
|
let account_pubkey = &message.account_keys[*account_index as usize];
|
||||||
|
(account_pubkey, *account_index)
|
||||||
|
});
|
||||||
|
|
||||||
|
write_instruction(
|
||||||
w,
|
w,
|
||||||
"{} Program: {} ({})",
|
instruction_index,
|
||||||
prefix, program_pubkey, instruction.program_id_index
|
&program_pubkey,
|
||||||
|
instruction,
|
||||||
|
instruction_accounts,
|
||||||
|
prefix,
|
||||||
)?;
|
)?;
|
||||||
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
|
||||||
let account_pubkey = message.account_keys[*account as usize];
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} 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)
|
|
||||||
{
|
|
||||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
|
||||||
raw = false;
|
|
||||||
}
|
|
||||||
} else if program_pubkey == stake::program::id() {
|
|
||||||
if let Ok(stake_instruction) =
|
|
||||||
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
|
||||||
{
|
|
||||||
writeln!(w, "{} {:?}", 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)
|
|
||||||
{
|
|
||||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
|
||||||
raw = false;
|
|
||||||
}
|
|
||||||
} else if is_memo_program(&program_pubkey) {
|
|
||||||
if let Ok(s) = std::str::from_utf8(&instruction.data) {
|
|
||||||
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
|
|
||||||
raw = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw {
|
|
||||||
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(transaction_status) = transaction_status {
|
if let Some(transaction_status) = transaction_status {
|
||||||
writeln!(
|
write_status(w, &transaction_status.status, prefix)?;
|
||||||
w,
|
write_fees(w, transaction_status.fee, prefix)?;
|
||||||
"{}Status: {}",
|
write_balances(w, transaction_status, prefix)?;
|
||||||
prefix,
|
write_log_messages(w, transaction_status.log_messages.as_ref(), prefix)?;
|
||||||
match &transaction_status.status {
|
write_rewards(w, transaction_status.rewards.as_ref(), prefix)?;
|
||||||
Ok(_) => "Ok".into(),
|
|
||||||
Err(err) => err.to_string(),
|
|
||||||
}
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} Fee: ◎{}",
|
|
||||||
prefix,
|
|
||||||
lamports_to_sol(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 {
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} Account {} balance: ◎{}",
|
|
||||||
prefix,
|
|
||||||
i,
|
|
||||||
lamports_to_sol(*pre)
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} Account {} balance: ◎{} -> ◎{}",
|
|
||||||
prefix,
|
|
||||||
i,
|
|
||||||
lamports_to_sol(*pre),
|
|
||||||
lamports_to_sol(*post)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(log_messages) = &transaction_status.log_messages {
|
|
||||||
if !log_messages.is_empty() {
|
|
||||||
writeln!(w, "{}Log Messages:", prefix,)?;
|
|
||||||
for log_message in log_messages {
|
|
||||||
writeln!(w, "{} {}", prefix, log_message)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rewards) = &transaction_status.rewards {
|
|
||||||
if !rewards.is_empty() {
|
|
||||||
writeln!(w, "{}Rewards:", prefix,)?;
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} {:<44} {:^15} {:<15} {:<20}",
|
|
||||||
prefix, "Address", "Type", "Amount", "New Balance"
|
|
||||||
)?;
|
|
||||||
for reward in rewards {
|
|
||||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
|
||||||
writeln!(
|
|
||||||
w,
|
|
||||||
"{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
|
|
||||||
prefix,
|
|
||||||
reward.pubkey,
|
|
||||||
if let Some(reward_type) = reward.reward_type {
|
|
||||||
format!("{}", reward_type)
|
|
||||||
} else {
|
|
||||||
"-".to_string()
|
|
||||||
},
|
|
||||||
sign,
|
|
||||||
lamports_to_sol(reward.lamports.abs() as u64),
|
|
||||||
lamports_to_sol(reward.post_balance)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||||
}
|
}
|
||||||
|
@ -359,9 +236,252 @@ pub fn write_transaction<W: io::Write>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CliTimezone {
|
||||||
|
Local,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Utc,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_block_time<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
block_time: Option<UnixTimestamp>,
|
||||||
|
timezone: CliTimezone,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
if let Some(block_time) = block_time {
|
||||||
|
let block_time_output = match timezone {
|
||||||
|
CliTimezone::Local => format!("{:?}", Local.timestamp(block_time, 0)),
|
||||||
|
CliTimezone::Utc => format!("{:?}", Utc.timestamp(block_time, 0)),
|
||||||
|
};
|
||||||
|
writeln!(w, "{}Block Time: {}", prefix, block_time_output,)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_recent_blockhash<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
recent_blockhash: &Hash,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
writeln!(w, "{}Recent Blockhash: {:?}", prefix, recent_blockhash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_signatures<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
signatures: &[Signature],
|
||||||
|
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
||||||
|
sigverify_status
|
||||||
|
.iter()
|
||||||
|
.map(|s| format!(" ({})", s))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec!["".to_string(); signatures.len()]
|
||||||
|
};
|
||||||
|
for (signature_index, (signature, sigverify_status)) in
|
||||||
|
signatures.iter().zip(&sigverify_statuses).enumerate()
|
||||||
|
{
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{}Signature {}: {:?}{}",
|
||||||
|
prefix, signature_index, signature, sigverify_status,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_account<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
account_index: usize,
|
||||||
|
account_address: &Pubkey,
|
||||||
|
account_mode: String,
|
||||||
|
is_fee_payer: bool,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{}Account {}: {} {}{}",
|
||||||
|
prefix,
|
||||||
|
account_index,
|
||||||
|
account_mode,
|
||||||
|
account_address,
|
||||||
|
if is_fee_payer { " (fee payer)" } else { "" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_instruction<'a, W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
instruction_index: usize,
|
||||||
|
program_pubkey: &Pubkey,
|
||||||
|
instruction: &CompiledInstruction,
|
||||||
|
instruction_accounts: impl Iterator<Item = (&'a Pubkey, u8)>,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} Program: {} ({})",
|
||||||
|
prefix, program_pubkey, instruction.program_id_index
|
||||||
|
)?;
|
||||||
|
for (index, (account_address, account_index)) in instruction_accounts.enumerate() {
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} Account {}: {} ({})",
|
||||||
|
prefix, index, account_address, account_index
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||||
|
raw = false;
|
||||||
|
}
|
||||||
|
} else if program_pubkey == &stake::program::id() {
|
||||||
|
if let Ok(stake_instruction) =
|
||||||
|
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
||||||
|
{
|
||||||
|
writeln!(w, "{} {:?}", 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)
|
||||||
|
{
|
||||||
|
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||||
|
raw = false;
|
||||||
|
}
|
||||||
|
} else if is_memo_program(program_pubkey) {
|
||||||
|
if let Ok(s) = std::str::from_utf8(&instruction.data) {
|
||||||
|
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
|
||||||
|
raw = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw {
|
||||||
|
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_rewards<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
rewards: Option<&Rewards>,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
if let Some(rewards) = rewards {
|
||||||
|
if !rewards.is_empty() {
|
||||||
|
writeln!(w, "{}Rewards:", prefix,)?;
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} {:<44} {:^15} {:<16} {:<20}",
|
||||||
|
prefix, "Address", "Type", "Amount", "New Balance"
|
||||||
|
)?;
|
||||||
|
for reward in rewards {
|
||||||
|
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
|
||||||
|
prefix,
|
||||||
|
reward.pubkey,
|
||||||
|
if let Some(reward_type) = reward.reward_type {
|
||||||
|
format!("{}", reward_type)
|
||||||
|
} else {
|
||||||
|
"-".to_string()
|
||||||
|
},
|
||||||
|
sign,
|
||||||
|
lamports_to_sol(reward.lamports.abs() as u64),
|
||||||
|
lamports_to_sol(reward.post_balance)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_status<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
transaction_status: &Result<(), TransactionError>,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{}Status: {}",
|
||||||
|
prefix,
|
||||||
|
match transaction_status {
|
||||||
|
Ok(_) => "Ok".into(),
|
||||||
|
Err(err) => err.to_string(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_fees<W: io::Write>(w: &mut W, transaction_fee: u64, prefix: &str) -> io::Result<()> {
|
||||||
|
writeln!(w, "{} Fee: ◎{}", prefix, lamports_to_sol(transaction_fee))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_balances<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
transaction_status: &UiTransactionStatusMeta,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
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 {
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} Account {} balance: ◎{}",
|
||||||
|
prefix,
|
||||||
|
i,
|
||||||
|
lamports_to_sol(*pre)
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"{} Account {} balance: ◎{} -> ◎{}",
|
||||||
|
prefix,
|
||||||
|
i,
|
||||||
|
lamports_to_sol(*pre),
|
||||||
|
lamports_to_sol(*post)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_log_messages<W: io::Write>(
|
||||||
|
w: &mut W,
|
||||||
|
log_messages: Option<&Vec<String>>,
|
||||||
|
prefix: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
if let Some(log_messages) = log_messages {
|
||||||
|
if !log_messages.is_empty() {
|
||||||
|
writeln!(w, "{}Log Messages:", prefix,)?;
|
||||||
|
for log_message in log_messages {
|
||||||
|
writeln!(w, "{} {}", prefix, log_message)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn println_transaction(
|
pub fn println_transaction(
|
||||||
transaction: &Transaction,
|
transaction: &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>,
|
block_time: Option<UnixTimestamp>,
|
||||||
|
@ -374,6 +494,7 @@ pub fn println_transaction(
|
||||||
prefix,
|
prefix,
|
||||||
sigverify_status,
|
sigverify_status,
|
||||||
block_time,
|
block_time,
|
||||||
|
CliTimezone::Local,
|
||||||
)
|
)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
|
@ -386,22 +507,23 @@ pub fn println_transaction(
|
||||||
pub fn writeln_transaction(
|
pub fn writeln_transaction(
|
||||||
f: &mut dyn fmt::Write,
|
f: &mut dyn fmt::Write,
|
||||||
transaction: &Transaction,
|
transaction: &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>,
|
block_time: Option<UnixTimestamp>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let mut w = Vec::new();
|
let mut w = Vec::new();
|
||||||
if write_transaction(
|
let write_result = write_transaction(
|
||||||
&mut w,
|
&mut w,
|
||||||
transaction,
|
transaction,
|
||||||
transaction_status,
|
transaction_status,
|
||||||
prefix,
|
prefix,
|
||||||
sigverify_status,
|
sigverify_status,
|
||||||
block_time,
|
block_time,
|
||||||
)
|
CliTimezone::Local,
|
||||||
.is_ok()
|
);
|
||||||
{
|
|
||||||
|
if write_result.is_ok() {
|
||||||
if let Ok(s) = String::from_utf8(w) {
|
if let Ok(s) = String::from_utf8(w) {
|
||||||
write!(f, "{}", s)?;
|
write!(f, "{}", s)?;
|
||||||
}
|
}
|
||||||
|
@ -427,7 +549,102 @@ pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {super::*, solana_sdk::pubkey::Pubkey};
|
use {
|
||||||
|
super::*,
|
||||||
|
solana_sdk::{
|
||||||
|
message::{v0::LoadedAddresses, Message as LegacyMessage, MessageHeader},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::{Keypair, Signer},
|
||||||
|
},
|
||||||
|
solana_transaction_status::{Reward, RewardType, TransactionStatusMeta},
|
||||||
|
std::io::BufWriter,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_keypair() -> Keypair {
|
||||||
|
let secret = ed25519_dalek::SecretKey::from_bytes(&[0u8; 32]).unwrap();
|
||||||
|
let public = ed25519_dalek::PublicKey::from(&secret);
|
||||||
|
let keypair = ed25519_dalek::Keypair { secret, public };
|
||||||
|
Keypair::from_bytes(&keypair.to_bytes()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_transaction() {
|
||||||
|
let keypair = test_keypair();
|
||||||
|
let account_key = Pubkey::new_from_array([1u8; 32]);
|
||||||
|
let transaction = Transaction::new(
|
||||||
|
&[&keypair],
|
||||||
|
LegacyMessage {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
num_readonly_signed_accounts: 0,
|
||||||
|
num_readonly_unsigned_accounts: 1,
|
||||||
|
},
|
||||||
|
recent_blockhash: Hash::default(),
|
||||||
|
account_keys: vec![keypair.pubkey(), account_key],
|
||||||
|
instructions: vec![CompiledInstruction::new_from_raw_parts(1, vec![], vec![0])],
|
||||||
|
},
|
||||||
|
Hash::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&transaction);
|
||||||
|
let meta = TransactionStatusMeta {
|
||||||
|
status: Ok(()),
|
||||||
|
fee: 5000,
|
||||||
|
pre_balances: vec![5000, 10_000],
|
||||||
|
post_balances: vec![0, 9_900],
|
||||||
|
inner_instructions: None,
|
||||||
|
log_messages: Some(vec!["Test message".to_string()]),
|
||||||
|
pre_token_balances: None,
|
||||||
|
post_token_balances: None,
|
||||||
|
rewards: Some(vec![Reward {
|
||||||
|
pubkey: account_key.to_string(),
|
||||||
|
lamports: -100,
|
||||||
|
post_balance: 9_900,
|
||||||
|
reward_type: Some(RewardType::Rent),
|
||||||
|
commission: None,
|
||||||
|
}]),
|
||||||
|
loaded_addresses: LoadedAddresses::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = {
|
||||||
|
let mut write_buffer = BufWriter::new(Vec::new());
|
||||||
|
write_transaction(
|
||||||
|
&mut write_buffer,
|
||||||
|
&transaction,
|
||||||
|
Some(&meta.into()),
|
||||||
|
"",
|
||||||
|
Some(&sigverify_status),
|
||||||
|
Some(1628633791),
|
||||||
|
CliTimezone::Utc,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let bytes = write_buffer.into_inner().unwrap();
|
||||||
|
String::from_utf8(bytes).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
output,
|
||||||
|
r#"Block Time: 2021-08-10T22:16:31Z
|
||||||
|
Recent Blockhash: 11111111111111111111111111111111
|
||||||
|
Signature 0: 5pkjrE4VBa3Bu9CMKXgh1U345cT1gGo8QBVRTzHAo6gHeiPae5BTbShP15g6NgqRMNqu8Qrhph1ATmrfC1Ley3rx (pass)
|
||||||
|
Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
|
||||||
|
Account 1: -r-x 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
|
||||||
|
Instruction 0
|
||||||
|
Program: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (1)
|
||||||
|
Account 0: 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (0)
|
||||||
|
Data: []
|
||||||
|
Status: Ok
|
||||||
|
Fee: ◎0.000005
|
||||||
|
Account 0 balance: ◎0.000005 -> ◎0
|
||||||
|
Account 1 balance: ◎0.00001 -> ◎0.0000099
|
||||||
|
Log Messages:
|
||||||
|
Test message
|
||||||
|
Rewards:
|
||||||
|
Address Type Amount New Balance \0
|
||||||
|
4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi rent -◎0.000000100 ◎0.000009900 \0
|
||||||
|
"#.replace("\\0", "") // replace marker used to subvert trailing whitespace linter on CI
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_labeled_address() {
|
fn test_format_labeled_address() {
|
||||||
|
|
|
@ -2052,7 +2052,7 @@ pub fn process_transaction_history(
|
||||||
.transaction
|
.transaction
|
||||||
.decode()
|
.decode()
|
||||||
.expect("Successful decode"),
|
.expect("Successful decode"),
|
||||||
&confirmed_transaction.transaction.meta,
|
confirmed_transaction.transaction.meta.as_ref(),
|
||||||
" ",
|
" ",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -267,7 +267,11 @@ pub async fn transaction_history(
|
||||||
Some(transaction_with_meta) => {
|
Some(transaction_with_meta) => {
|
||||||
println_transaction(
|
println_transaction(
|
||||||
&transaction_with_meta.transaction,
|
&transaction_with_meta.transaction,
|
||||||
&transaction_with_meta.meta.clone().map(|m| m.into()),
|
transaction_with_meta
|
||||||
|
.meta
|
||||||
|
.clone()
|
||||||
|
.map(|m| m.into())
|
||||||
|
.as_ref(),
|
||||||
" ",
|
" ",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -158,7 +158,11 @@ fn output_entry(
|
||||||
|
|
||||||
if let Some(legacy_tx) = transaction.into_legacy_transaction() {
|
if let Some(legacy_tx) = transaction.into_legacy_transaction() {
|
||||||
solana_cli_output::display::println_transaction(
|
solana_cli_output::display::println_transaction(
|
||||||
&legacy_tx, &tx_status, " ", None, None,
|
&legacy_tx,
|
||||||
|
tx_status.as_ref(),
|
||||||
|
" ",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|
Loading…
Reference in New Issue