2021-02-01 18:03:07 -08:00
|
|
|
use {
|
|
|
|
crate::cli_output::CliSignatureVerificationStatus,
|
2021-02-25 14:15:52 -07:00
|
|
|
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
|
2021-02-01 18:03:07 -08:00
|
|
|
console::style,
|
|
|
|
indicatif::{ProgressBar, ProgressStyle},
|
|
|
|
solana_sdk::{
|
2022-03-11 10:49:53 +08:00
|
|
|
clock::UnixTimestamp,
|
|
|
|
hash::Hash,
|
|
|
|
instruction::CompiledInstruction,
|
|
|
|
native_token::lamports_to_sol,
|
|
|
|
program_utils::limited_deserialize,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::Signature,
|
|
|
|
stake,
|
|
|
|
transaction::{Transaction, TransactionError},
|
2021-02-01 18:03:07 -08:00
|
|
|
},
|
2022-03-11 10:49:53 +08:00
|
|
|
solana_transaction_status::{Rewards, UiTransactionStatusMeta},
|
2021-12-03 09:00:31 -08:00
|
|
|
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
|
2021-02-01 18:03:07 -08:00
|
|
|
std::{collections::HashMap, fmt, io},
|
2020-04-15 20:51:05 -07:00
|
|
|
};
|
2019-08-08 11:13:06 -06:00
|
|
|
|
2021-01-31 22:53:45 -07:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct BuildBalanceMessageConfig {
|
|
|
|
pub use_lamports_unit: bool,
|
|
|
|
pub show_unit: bool,
|
|
|
|
pub trim_trailing_zeros: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for BuildBalanceMessageConfig {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
use_lamports_unit: false,
|
|
|
|
show_unit: true,
|
|
|
|
trim_trailing_zeros: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-05 13:53:50 -07:00
|
|
|
fn is_memo_program(k: &Pubkey) -> bool {
|
|
|
|
let k_str = k.to_string();
|
|
|
|
(k_str == spl_memo_v1_id().to_string()) || (k_str == spl_memo_id().to_string())
|
|
|
|
}
|
|
|
|
|
2021-01-31 22:53:45 -07:00
|
|
|
pub fn build_balance_message_with_config(
|
|
|
|
lamports: u64,
|
|
|
|
config: &BuildBalanceMessageConfig,
|
|
|
|
) -> String {
|
|
|
|
let value = if config.use_lamports_unit {
|
|
|
|
lamports.to_string()
|
2020-09-22 18:29:11 -06:00
|
|
|
} else {
|
|
|
|
let sol = lamports_to_sol(lamports);
|
|
|
|
let sol_str = format!("{:.9}", sol);
|
2021-01-31 22:53:45 -07:00
|
|
|
if config.trim_trailing_zeros {
|
|
|
|
sol_str
|
|
|
|
.trim_end_matches('0')
|
|
|
|
.trim_end_matches('.')
|
|
|
|
.to_string()
|
|
|
|
} else {
|
|
|
|
sol_str
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let unit = if config.show_unit {
|
|
|
|
if config.use_lamports_unit {
|
|
|
|
let ess = if lamports == 1 { "" } else { "s" };
|
|
|
|
format!(" lamport{}", ess)
|
|
|
|
} else {
|
|
|
|
" SOL".to_string()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
};
|
|
|
|
format!("{}{}", value, unit)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
|
|
|
build_balance_message_with_config(
|
|
|
|
lamports,
|
|
|
|
&BuildBalanceMessageConfig {
|
|
|
|
use_lamports_unit,
|
|
|
|
show_unit,
|
|
|
|
..BuildBalanceMessageConfig::default()
|
|
|
|
},
|
|
|
|
)
|
2020-09-22 18:29:11 -06:00
|
|
|
}
|
|
|
|
|
2019-08-08 11:13:06 -06:00
|
|
|
// Pretty print a "name value"
|
|
|
|
pub fn println_name_value(name: &str, value: &str) {
|
2020-12-13 17:26:34 -08:00
|
|
|
let styled_value = if value.is_empty() {
|
2019-08-08 11:13:06 -06:00
|
|
|
style("(not set)").italic()
|
|
|
|
} else {
|
|
|
|
style(value)
|
|
|
|
};
|
|
|
|
println!("{} {}", style(name).bold(), styled_value);
|
|
|
|
}
|
2019-09-05 10:14:23 -07:00
|
|
|
|
2021-01-21 12:15:35 -07:00
|
|
|
pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
|
2020-12-13 17:26:34 -08:00
|
|
|
let styled_value = if value.is_empty() {
|
2020-04-14 13:10:25 -06:00
|
|
|
style("(not set)").italic()
|
|
|
|
} else {
|
|
|
|
style(value)
|
|
|
|
};
|
|
|
|
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
|
|
|
}
|
|
|
|
|
2020-06-17 23:09:33 -07:00
|
|
|
pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
|
|
|
|
let label = address_labels.get(pubkey);
|
|
|
|
match label {
|
|
|
|
Some(label) => format!(
|
|
|
|
"{:.31} ({:.4}..{})",
|
|
|
|
label,
|
|
|
|
pubkey,
|
|
|
|
pubkey.split_at(pubkey.len() - 4).1
|
|
|
|
),
|
|
|
|
None => pubkey.to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 21:49:38 -06:00
|
|
|
pub fn println_signers(
|
|
|
|
blockhash: &Hash,
|
|
|
|
signers: &[String],
|
|
|
|
absent: &[String],
|
|
|
|
bad_sig: &[String],
|
|
|
|
) {
|
2019-11-25 21:09:57 -08:00
|
|
|
println!();
|
2020-03-18 21:49:38 -06:00
|
|
|
println!("Blockhash: {}", blockhash);
|
|
|
|
if !signers.is_empty() {
|
|
|
|
println!("Signers (Pubkey=Signature):");
|
|
|
|
signers.iter().for_each(|signer| println!(" {}", signer))
|
|
|
|
}
|
|
|
|
if !absent.is_empty() {
|
|
|
|
println!("Absent Signers (Pubkey):");
|
|
|
|
absent.iter().for_each(|pubkey| println!(" {}", pubkey))
|
|
|
|
}
|
|
|
|
if !bad_sig.is_empty() {
|
|
|
|
println!("Bad Signatures (Pubkey):");
|
|
|
|
bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
|
|
|
|
}
|
2019-11-25 21:09:57 -08:00
|
|
|
println!();
|
|
|
|
}
|
2020-04-15 20:51:05 -07:00
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
struct CliAccountMeta {
|
|
|
|
is_signer: bool,
|
|
|
|
is_writable: bool,
|
|
|
|
is_invoked: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_account_mode(meta: CliAccountMeta) -> String {
|
2021-03-16 14:44:48 +09:00
|
|
|
format!(
|
|
|
|
"{}r{}{}", // accounts are always readable...
|
2022-03-11 10:49:53 +08:00
|
|
|
if meta.is_signer {
|
2021-03-16 14:44:48 +09:00
|
|
|
"s" // stands for signer
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
},
|
2022-03-11 10:49:53 +08:00
|
|
|
if meta.is_writable {
|
2021-03-16 14:44:48 +09:00
|
|
|
"w" // comment for consistent rust fmt (no joking; lol)
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
},
|
|
|
|
// account may be executable on-chain while not being
|
|
|
|
// designated as a program-id in the message
|
2022-03-11 10:49:53 +08:00
|
|
|
if meta.is_invoked {
|
2021-03-16 14:44:48 +09:00
|
|
|
"x"
|
|
|
|
} else {
|
|
|
|
// programs to be executed via CPI cannot be identified as
|
|
|
|
// executable from the message
|
|
|
|
"-"
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
fn write_transaction<W: io::Write>(
|
2020-05-01 17:48:22 -07:00
|
|
|
w: &mut W,
|
2020-04-15 20:51:05 -07:00
|
|
|
transaction: &Transaction,
|
2022-03-11 10:49:53 +08:00
|
|
|
transaction_status: Option<&UiTransactionStatusMeta>,
|
2020-04-15 20:51:05 -07:00
|
|
|
prefix: &str,
|
2021-01-31 23:39:26 -07:00
|
|
|
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
2021-02-25 14:15:52 -07:00
|
|
|
block_time: Option<UnixTimestamp>,
|
2022-03-11 10:49:53 +08:00
|
|
|
timezone: CliTimezone,
|
2020-05-01 17:48:22 -07:00
|
|
|
) -> io::Result<()> {
|
2022-03-11 10:49:53 +08:00
|
|
|
write_block_time(w, block_time, timezone, prefix)?;
|
|
|
|
|
2020-04-15 20:51:05 -07:00
|
|
|
let message = &transaction.message;
|
2022-03-11 10:49:53 +08:00
|
|
|
write_recent_blockhash(w, &message.recent_blockhash, prefix)?;
|
|
|
|
write_signatures(w, &transaction.signatures, sigverify_status, prefix)?;
|
|
|
|
|
|
|
|
let mut fee_payer_index = None;
|
|
|
|
for (account_index, account) in message.account_keys.iter().enumerate() {
|
|
|
|
if fee_payer_index.is_none() && message.is_non_loader_key(account_index) {
|
|
|
|
fee_payer_index = Some(account_index)
|
|
|
|
}
|
|
|
|
|
|
|
|
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(
|
2021-02-25 14:15:52 -07:00
|
|
|
w,
|
2022-03-11 10:49:53 +08:00
|
|
|
account_index,
|
|
|
|
account,
|
|
|
|
format_account_mode(account_meta),
|
|
|
|
Some(account_index) == fee_payer_index,
|
2021-02-25 14:15:52 -07:00
|
|
|
prefix,
|
|
|
|
)?;
|
|
|
|
}
|
2022-03-11 10:49:53 +08:00
|
|
|
|
|
|
|
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
|
|
|
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
|
|
|
let instruction_accounts = instruction.accounts.iter().map(|account_index| {
|
|
|
|
let account_pubkey = &message.account_keys[*account_index as usize];
|
|
|
|
(account_pubkey, *account_index)
|
|
|
|
});
|
|
|
|
|
|
|
|
write_instruction(
|
|
|
|
w,
|
|
|
|
instruction_index,
|
|
|
|
&program_pubkey,
|
|
|
|
instruction,
|
|
|
|
instruction_accounts,
|
|
|
|
prefix,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(transaction_status) = transaction_status {
|
|
|
|
write_status(w, &transaction_status.status, prefix)?;
|
|
|
|
write_fees(w, transaction_status.fee, prefix)?;
|
|
|
|
write_balances(w, transaction_status, prefix)?;
|
|
|
|
write_log_messages(w, transaction_status.log_messages.as_ref(), prefix)?;
|
|
|
|
write_rewards(w, transaction_status.rewards.as_ref(), prefix)?;
|
|
|
|
} else {
|
|
|
|
writeln!(w, "{}Status: Unavailable", prefix)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
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<()> {
|
2021-01-31 23:39:26 -07:00
|
|
|
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
|
|
|
sigverify_status
|
|
|
|
.iter()
|
|
|
|
.map(|s| format!(" ({})", s))
|
|
|
|
.collect()
|
|
|
|
} else {
|
2022-03-11 10:49:53 +08:00
|
|
|
vec!["".to_string(); signatures.len()]
|
2021-01-31 23:39:26 -07:00
|
|
|
};
|
2022-03-11 10:49:53 +08:00
|
|
|
for (signature_index, (signature, sigverify_status)) in
|
|
|
|
signatures.iter().zip(&sigverify_statuses).enumerate()
|
2021-01-31 23:39:26 -07:00
|
|
|
{
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2021-01-31 23:39:26 -07:00
|
|
|
"{}Signature {}: {:?}{}",
|
|
|
|
prefix, signature_index, signature, sigverify_status,
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
2022-03-11 10:49:53 +08:00
|
|
|
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() {
|
2021-03-16 14:44:48 +09:00
|
|
|
writeln!(
|
|
|
|
w,
|
2022-03-11 10:49:53 +08:00
|
|
|
"{} Account {}: {} ({})",
|
|
|
|
prefix, index, account_address, account_index
|
2021-03-16 14:44:48 +09:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
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;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
2022-03-11 10:49:53 +08:00
|
|
|
} 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;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
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 { "" };
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2022-03-11 10:49:53 +08:00
|
|
|
"{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
|
2020-04-15 20:51:05 -07:00
|
|
|
prefix,
|
2022-03-11 10:49:53 +08:00
|
|
|
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)
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
2022-03-11 10:49:53 +08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-12 21:37:29 -07:00
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
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)
|
|
|
|
)?;
|
2021-05-26 14:43:15 -07:00
|
|
|
}
|
2022-03-11 10:49:53 +08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-05-26 14:43:15 -07:00
|
|
|
|
2022-03-11 10:49:53 +08:00
|
|
|
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)?;
|
2020-10-12 21:37:29 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-01 17:48:22 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn println_transaction(
|
|
|
|
transaction: &Transaction,
|
2022-03-11 10:49:53 +08:00
|
|
|
transaction_status: Option<&UiTransactionStatusMeta>,
|
2020-05-01 17:48:22 -07:00
|
|
|
prefix: &str,
|
2021-01-31 23:39:26 -07:00
|
|
|
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
2021-02-25 14:15:52 -07:00
|
|
|
block_time: Option<UnixTimestamp>,
|
2020-05-01 17:48:22 -07:00
|
|
|
) {
|
|
|
|
let mut w = Vec::new();
|
2021-01-31 23:39:26 -07:00
|
|
|
if write_transaction(
|
|
|
|
&mut w,
|
|
|
|
transaction,
|
|
|
|
transaction_status,
|
|
|
|
prefix,
|
|
|
|
sigverify_status,
|
2021-02-25 14:15:52 -07:00
|
|
|
block_time,
|
2022-03-11 10:49:53 +08:00
|
|
|
CliTimezone::Local,
|
2021-01-31 23:39:26 -07:00
|
|
|
)
|
|
|
|
.is_ok()
|
|
|
|
{
|
2020-05-01 17:48:22 -07:00
|
|
|
if let Ok(s) = String::from_utf8(w) {
|
|
|
|
print!("{}", s);
|
|
|
|
}
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-31 23:00:51 -07:00
|
|
|
|
2021-02-24 16:14:34 -07:00
|
|
|
pub fn writeln_transaction(
|
|
|
|
f: &mut dyn fmt::Write,
|
|
|
|
transaction: &Transaction,
|
2022-03-11 10:49:53 +08:00
|
|
|
transaction_status: Option<&UiTransactionStatusMeta>,
|
2021-02-24 16:14:34 -07:00
|
|
|
prefix: &str,
|
|
|
|
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
2021-02-25 14:15:52 -07:00
|
|
|
block_time: Option<UnixTimestamp>,
|
2021-02-24 16:14:34 -07:00
|
|
|
) -> fmt::Result {
|
|
|
|
let mut w = Vec::new();
|
2022-03-11 10:49:53 +08:00
|
|
|
let write_result = write_transaction(
|
2021-02-24 16:14:34 -07:00
|
|
|
&mut w,
|
|
|
|
transaction,
|
|
|
|
transaction_status,
|
|
|
|
prefix,
|
|
|
|
sigverify_status,
|
2021-02-25 14:15:52 -07:00
|
|
|
block_time,
|
2022-03-11 10:49:53 +08:00
|
|
|
CliTimezone::Local,
|
|
|
|
);
|
|
|
|
|
|
|
|
if write_result.is_ok() {
|
2021-02-24 16:14:34 -07:00
|
|
|
if let Ok(s) = String::from_utf8(w) {
|
|
|
|
write!(f, "{}", s)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-05-31 23:00:51 -07:00
|
|
|
/// Creates a new process bar for processing that will take an unknown amount of time
|
|
|
|
pub fn new_spinner_progress_bar() -> ProgressBar {
|
|
|
|
let progress_bar = ProgressBar::new(42);
|
|
|
|
progress_bar
|
|
|
|
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
|
|
|
|
progress_bar.enable_steady_tick(100);
|
|
|
|
progress_bar
|
|
|
|
}
|
2020-06-17 23:09:33 -07:00
|
|
|
|
2021-02-01 18:03:07 -08:00
|
|
|
pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
|
|
|
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
|
|
|
Some(ndt) => DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
|
|
|
|
None => format!("UnixTimestamp {}", unix_timestamp),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 23:09:33 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2022-03-11 10:49:53 +08:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
2020-06-17 23:09:33 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_labeled_address() {
|
|
|
|
let pubkey = Pubkey::default().to_string();
|
|
|
|
let mut address_labels = HashMap::new();
|
|
|
|
|
|
|
|
assert_eq!(format_labeled_address(&pubkey, &address_labels), pubkey);
|
|
|
|
|
|
|
|
address_labels.insert(pubkey.to_string(), "Default Address".to_string());
|
|
|
|
assert_eq!(
|
|
|
|
&format_labeled_address(&pubkey, &address_labels),
|
|
|
|
"Default Address (1111..1111)"
|
|
|
|
);
|
|
|
|
|
|
|
|
address_labels.insert(
|
|
|
|
pubkey.to_string(),
|
|
|
|
"abcdefghijklmnopqrstuvwxyz1234567890".to_string(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
&format_labeled_address(&pubkey, &address_labels),
|
|
|
|
"abcdefghijklmnopqrstuvwxyz12345 (1111..1111)"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|