Add CLI support for versioned transactions (#23606)

This commit is contained in:
Justin Starry 2022-03-17 11:43:04 +08:00 committed by GitHub
parent 330d6db19a
commit 0eccacbd5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 538 additions and 238 deletions

View File

@ -28,7 +28,7 @@ use {
signature::Signature,
stake::state::{Authorized, Lockup},
stake_history::StakeHistoryEntry,
transaction::{Transaction, TransactionError},
transaction::{Transaction, TransactionError, VersionedTransaction},
},
solana_transaction_status::{
EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
@ -2218,7 +2218,7 @@ pub enum CliSignatureVerificationStatus {
}
impl CliSignatureVerificationStatus {
pub fn verify_transaction(tx: &Transaction) -> Vec<Self> {
pub fn verify_transaction(tx: &VersionedTransaction) -> Vec<Self> {
tx.verify_with_results()
.iter()
.zip(&tx.signatures)
@ -2354,7 +2354,7 @@ pub struct CliTransaction {
#[serde(skip_serializing)]
pub slot: Option<Slot>,
#[serde(skip_serializing)]
pub decoded_transaction: Transaction,
pub decoded_transaction: VersionedTransaction,
#[serde(skip_serializing)]
pub prefix: String,
#[serde(skip_serializing_if = "Vec::is_empty")]

View File

@ -7,12 +7,13 @@ use {
clock::UnixTimestamp,
hash::Hash,
instruction::CompiledInstruction,
message::v0::MessageAddressTableLookup,
native_token::lamports_to_sol,
program_utils::limited_deserialize,
pubkey::Pubkey,
signature::Signature,
stake,
transaction::{Transaction, TransactionError},
transaction::{TransactionError, TransactionVersion, VersionedTransaction},
},
solana_transaction_status::{Rewards, UiTransactionStatusMeta},
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
@ -171,7 +172,7 @@ fn format_account_mode(meta: CliAccountMeta) -> String {
fn write_transaction<W: io::Write>(
w: &mut W,
transaction: &Transaction,
transaction: &VersionedTransaction,
transaction_status: Option<&UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
@ -181,48 +182,65 @@ fn write_transaction<W: io::Write>(
write_block_time(w, block_time, timezone, prefix)?;
let message = &transaction.message;
write_recent_blockhash(w, &message.recent_blockhash, prefix)?;
let account_keys: Vec<AccountKeyType> = {
let static_keys_iter = message
.static_account_keys()
.iter()
.map(AccountKeyType::Known);
let dynamic_keys: Vec<AccountKeyType> = message
.address_table_lookups()
.map(transform_lookups_to_unknown_keys)
.unwrap_or_default();
static_keys_iter.chain(dynamic_keys).collect()
};
write_version(w, transaction.version(), prefix)?;
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() {
for (account_index, account) in 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),
is_writable: message.is_maybe_writable(account_index),
is_invoked: message.is_invoked(account_index),
};
write_account(
w,
account_index,
account,
*account,
format_account_mode(account_meta),
Some(account_index) == fee_payer_index,
prefix,
)?;
}
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)
});
for (instruction_index, instruction) in message.instructions().iter().enumerate() {
let program_pubkey = account_keys[instruction.program_id_index as usize];
let instruction_accounts = instruction
.accounts
.iter()
.map(|account_index| (account_keys[*account_index as usize], *account_index));
write_instruction(
w,
instruction_index,
&program_pubkey,
program_pubkey,
instruction,
instruction_accounts,
prefix,
)?;
}
if let Some(address_table_lookups) = message.address_table_lookups() {
write_address_table_lookups(w, address_table_lookups, prefix)?;
}
if let Some(transaction_status) = transaction_status {
write_status(w, &transaction_status.status, prefix)?;
write_fees(w, transaction_status.fee, prefix)?;
@ -236,6 +254,36 @@ fn write_transaction<W: io::Write>(
Ok(())
}
fn transform_lookups_to_unknown_keys(lookups: &[MessageAddressTableLookup]) -> Vec<AccountKeyType> {
let unknown_writable_keys = lookups
.iter()
.enumerate()
.flat_map(|(lookup_index, lookup)| {
lookup
.writable_indexes
.iter()
.map(move |table_index| AccountKeyType::Unknown {
lookup_index,
table_index: *table_index,
})
});
let unknown_readonly_keys = lookups
.iter()
.enumerate()
.flat_map(|(lookup_index, lookup)| {
lookup
.readonly_indexes
.iter()
.map(move |table_index| AccountKeyType::Unknown {
lookup_index,
table_index: *table_index,
})
});
unknown_writable_keys.chain(unknown_readonly_keys).collect()
}
enum CliTimezone {
Local,
#[allow(dead_code)]
@ -258,6 +306,18 @@ fn write_block_time<W: io::Write>(
Ok(())
}
fn write_version<W: io::Write>(
w: &mut W,
version: TransactionVersion,
prefix: &str,
) -> io::Result<()> {
let version = match version {
TransactionVersion::Legacy(_) => "legacy".to_string(),
TransactionVersion::Number(number) => number.to_string(),
};
writeln!(w, "{}Version: {}", prefix, version)
}
fn write_recent_blockhash<W: io::Write>(
w: &mut W,
recent_blockhash: &Hash,
@ -292,10 +352,37 @@ fn write_signatures<W: io::Write>(
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum AccountKeyType<'a> {
Known(&'a Pubkey),
Unknown {
lookup_index: usize,
table_index: u8,
},
}
impl fmt::Display for AccountKeyType<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Known(address) => write!(f, "{}", address),
Self::Unknown {
lookup_index,
table_index,
} => {
write!(
f,
"Unknown Address (uses lookup {} and index {})",
lookup_index, table_index
)
}
}
}
}
fn write_account<W: io::Write>(
w: &mut W,
account_index: usize,
account_address: &Pubkey,
account_address: AccountKeyType,
account_mode: String,
is_fee_payer: bool,
prefix: &str,
@ -314,9 +401,9 @@ fn write_account<W: io::Write>(
fn write_instruction<'a, W: io::Write>(
w: &mut W,
instruction_index: usize,
program_pubkey: &Pubkey,
program_pubkey: AccountKeyType,
instruction: &CompiledInstruction,
instruction_accounts: impl Iterator<Item = (&'a Pubkey, u8)>,
instruction_accounts: impl Iterator<Item = (AccountKeyType<'a>, u8)>,
prefix: &str,
) -> io::Result<()> {
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
@ -334,33 +421,35 @@ fn write_instruction<'a, W: io::Write>(
}
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 let AccountKeyType::Known(program_pubkey) = program_pubkey {
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;
}
}
}
@ -371,6 +460,30 @@ fn write_instruction<'a, W: io::Write>(
Ok(())
}
fn write_address_table_lookups<W: io::Write>(
w: &mut W,
address_table_lookups: &[MessageAddressTableLookup],
prefix: &str,
) -> io::Result<()> {
for (lookup_index, lookup) in address_table_lookups.iter().enumerate() {
writeln!(w, "{}Address Table Lookup {}", prefix, lookup_index,)?;
writeln!(w, "{} Table Account: {}", prefix, lookup.account_key,)?;
writeln!(
w,
"{} Writable Indexes: {:?}",
prefix,
&lookup.writable_indexes[..],
)?;
writeln!(
w,
"{} Readonly Indexes: {:?}",
prefix,
&lookup.readonly_indexes[..],
)?;
}
Ok(())
}
fn write_rewards<W: io::Write>(
w: &mut W,
rewards: Option<&Rewards>,
@ -480,7 +593,7 @@ fn write_log_messages<W: io::Write>(
}
pub fn println_transaction(
transaction: &Transaction,
transaction: &VersionedTransaction,
transaction_status: Option<&UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
@ -506,7 +619,7 @@ pub fn println_transaction(
pub fn writeln_transaction(
f: &mut dyn fmt::Write,
transaction: &Transaction,
transaction: &VersionedTransaction,
transaction_status: Option<&UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
@ -552,26 +665,59 @@ mod test {
use {
super::*,
solana_sdk::{
message::{v0::LoadedAddresses, Message as LegacyMessage, MessageHeader},
message::{
v0::{self, LoadedAddresses},
Message as LegacyMessage, MessageHeader, VersionedMessage,
},
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
},
solana_transaction_status::{Reward, RewardType, TransactionStatusMeta},
std::io::BufWriter,
};
fn test_keypair() -> Keypair {
fn new_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();
fn new_test_v0_transaction() -> VersionedTransaction {
let keypair = new_test_keypair();
let account_key = Pubkey::new_from_array([1u8; 32]);
let transaction = Transaction::new(
let address_table_key = Pubkey::new_from_array([2u8; 32]);
VersionedTransaction::try_new(
VersionedMessage::V0(v0::Message {
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],
address_table_lookups: vec![MessageAddressTableLookup {
account_key: address_table_key,
writable_indexes: vec![0],
readonly_indexes: vec![1],
}],
instructions: vec![CompiledInstruction::new_from_raw_parts(
3,
vec![],
vec![1, 2],
)],
}),
&[&keypair],
)
.unwrap()
}
#[test]
fn test_write_legacy_transaction() {
let keypair = new_test_keypair();
let account_key = Pubkey::new_from_array([1u8; 32]);
let transaction = VersionedTransaction::from(Transaction::new(
&[&keypair],
LegacyMessage {
header: MessageHeader {
@ -584,7 +730,7 @@ mod test {
instructions: vec![CompiledInstruction::new_from_raw_parts(1, vec![], vec![0])],
},
Hash::default(),
);
));
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&transaction);
let meta = TransactionStatusMeta {
@ -625,6 +771,7 @@ mod test {
assert_eq!(
output,
r#"Block Time: 2021-08-10T22:16:31Z
Version: legacy
Recent Blockhash: 11111111111111111111111111111111
Signature 0: 5pkjrE4VBa3Bu9CMKXgh1U345cT1gGo8QBVRTzHAo6gHeiPae5BTbShP15g6NgqRMNqu8Qrhph1ATmrfC1Ley3rx (pass)
Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
@ -646,6 +793,85 @@ Rewards:
);
}
#[test]
fn test_write_v0_transaction() {
let versioned_tx = new_test_v0_transaction();
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&versioned_tx);
let address_table_entry1 = Pubkey::new_from_array([3u8; 32]);
let address_table_entry2 = Pubkey::new_from_array([4u8; 32]);
let loaded_addresses = LoadedAddresses {
writable: vec![address_table_entry1],
readonly: vec![address_table_entry2],
};
let meta = TransactionStatusMeta {
status: Ok(()),
fee: 5000,
pre_balances: vec![5000, 10_000, 15_000, 20_000],
post_balances: vec![0, 10_000, 14_900, 20_000],
inner_instructions: None,
log_messages: Some(vec!["Test message".to_string()]),
pre_token_balances: None,
post_token_balances: None,
rewards: Some(vec![Reward {
pubkey: address_table_entry1.to_string(),
lamports: -100,
post_balance: 14_900,
reward_type: Some(RewardType::Rent),
commission: None,
}]),
loaded_addresses,
};
let output = {
let mut write_buffer = BufWriter::new(Vec::new());
write_transaction(
&mut write_buffer,
&versioned_tx,
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
Version: 0
Recent Blockhash: 11111111111111111111111111111111
Signature 0: 5iEy3TT3ZhTA1NkuCY8GrQGNVY8d5m1bpjdh5FT3Ca4Py81fMipAZjafDuKJKrkw5q5UAAd8oPcgZ4nyXpHt4Fp7 (pass)
Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
Account 1: -r-- 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
Account 2: -rw- Unknown Address (uses lookup 0 and index 0)
Account 3: -r-x Unknown Address (uses lookup 0 and index 1)
Instruction 0
Program: Unknown Address (uses lookup 0 and index 1) (3)
Account 0: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (1)
Account 1: Unknown Address (uses lookup 0 and index 0) (2)
Data: []
Address Table Lookup 0
Table Account: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
Writable Indexes: [0]
Readonly Indexes: [1]
Status: Ok
Fee: 0.000005
Account 0 balance: 0.000005 -> 0
Account 1 balance: 0.00001
Account 2 balance: 0.000015 -> 0.0000149
Account 3 balance: 0.00002
Log Messages:
Test message
Rewards:
Address Type Amount New Balance \0
CktRuQ2mttgRGkXJtyksdKHjUdc2C4TgDzyB98oEzy8 rent -0.000000100 0.000014900 \0
"#.replace("\\0", "") // replace marker used to subvert trailing whitespace linter on CI
);
}
#[test]
fn test_format_labeled_address() {
let pubkey = Pubkey::default().to_string();

View File

@ -30,7 +30,7 @@ use {
pubkey::Pubkey,
signature::{Signature, Signer, SignerError},
stake::{instruction::LockupArgs, state::Lockup},
transaction::{Transaction, TransactionError},
transaction::{TransactionError, VersionedTransaction},
},
solana_vote_program::vote_state::VoteAuthorize,
std::{collections::HashMap, error, io::stdout, str::FromStr, sync::Arc, time::Duration},
@ -385,7 +385,7 @@ pub enum CliCommand {
seed: String,
program_id: Pubkey,
},
DecodeTransaction(Transaction),
DecodeTransaction(VersionedTransaction),
ResolveSigner(Option<String>),
ShowAccount {
pubkey: Pubkey,

View File

@ -1058,6 +1058,7 @@ pub fn process_get_block(
RpcBlockConfig {
encoding: Some(UiTransactionEncoding::Base64),
commitment: Some(CommitmentConfig::confirmed()),
max_supported_transaction_version: Some(0),
..RpcBlockConfig::default()
},
)?
@ -2042,7 +2043,7 @@ pub fn process_transaction_history(
RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Base64),
commitment: Some(CommitmentConfig::confirmed()),
max_supported_transaction_version: None,
max_supported_transaction_version: Some(0),
},
) {
Ok(confirmed_transaction) => {

View File

@ -37,10 +37,11 @@ use {
stake,
system_instruction::{self, SystemError},
system_program,
transaction::Transaction,
transaction::{Transaction, VersionedTransaction},
},
solana_transaction_status::{
Encodable, EncodedTransaction, TransactionBinaryEncoding, UiTransactionEncoding,
EncodableWithMeta, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
TransactionBinaryEncoding, UiTransactionEncoding,
},
std::{fmt::Write as FmtWrite, fs::File, io::Write, sync::Arc},
};
@ -561,23 +562,25 @@ pub fn process_confirm(
RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Base64),
commitment: Some(CommitmentConfig::confirmed()),
max_supported_transaction_version: None,
max_supported_transaction_version: Some(0),
},
) {
Ok(confirmed_transaction) => {
let decoded_transaction = confirmed_transaction
.transaction
.transaction
.decode()
.expect("Successful decode");
let json_transaction =
decoded_transaction.encode(UiTransactionEncoding::Json);
let EncodedConfirmedTransactionWithStatusMeta {
block_time,
slot,
transaction: transaction_with_meta,
} = confirmed_transaction;
let decoded_transaction =
transaction_with_meta.transaction.decode().unwrap();
let json_transaction = decoded_transaction.json_encode();
transaction = Some(CliTransaction {
transaction: json_transaction,
meta: confirmed_transaction.transaction.meta,
block_time: confirmed_transaction.block_time,
slot: Some(confirmed_transaction.slot),
meta: transaction_with_meta.meta,
block_time,
slot: Some(slot),
decoded_transaction,
prefix: " ".to_string(),
sigverify_status: vec![],
@ -609,11 +612,14 @@ pub fn process_confirm(
}
#[allow(clippy::unnecessary_wraps)]
pub fn process_decode_transaction(config: &CliConfig, transaction: &Transaction) -> ProcessResult {
pub fn process_decode_transaction(
config: &CliConfig,
transaction: &VersionedTransaction,
) -> ProcessResult {
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction);
let decode_transaction = CliTransaction {
decoded_transaction: transaction.clone(),
transaction: transaction.encode(UiTransactionEncoding::Json),
transaction: transaction.json_encode(),
meta: None,
block_time: None,
slot: None,

View File

@ -1,4 +1,4 @@
/// The `bigtable` subcommand
//! The `bigtable` subcommand
use {
crate::ledger_path::canonicalize_ledger_path,
clap::{
@ -17,7 +17,7 @@ use {
solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType},
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
solana_transaction_status::{
BlockEncodingOptions, Encodable, EncodeError, LegacyConfirmedBlock, TransactionDetails,
BlockEncodingOptions, ConfirmedBlock, EncodeError, TransactionDetails,
UiTransactionEncoding,
},
std::{
@ -172,19 +172,17 @@ async fn confirm(
if verbose {
match bigtable.get_confirmed_transaction(signature).await {
Ok(Some(confirmed_tx)) => {
let legacy_confirmed_tx = confirmed_tx
.into_legacy_confirmed_transaction()
.ok_or_else(|| "Failed to read versioned transaction in block".to_string())?;
let decoded_tx = confirmed_tx.get_transaction();
let encoded_tx_with_meta = confirmed_tx
.tx_with_meta
.encode(UiTransactionEncoding::Json, Some(0))
.map_err(|_| "Failed to encode transaction in block".to_string())?;
transaction = Some(CliTransaction {
transaction: legacy_confirmed_tx
.tx_with_meta
.transaction
.encode(UiTransactionEncoding::Json),
meta: legacy_confirmed_tx.tx_with_meta.meta.map(|m| m.into()),
block_time: legacy_confirmed_tx.block_time,
slot: Some(legacy_confirmed_tx.slot),
decoded_transaction: legacy_confirmed_tx.tx_with_meta.transaction,
transaction: encoded_tx_with_meta.transaction,
meta: encoded_tx_with_meta.meta,
block_time: confirmed_tx.block_time,
slot: Some(confirmed_tx.slot),
decoded_transaction: decoded_tx,
prefix: " ".to_string(),
sigverify_status: vec![],
});
@ -216,7 +214,7 @@ pub async fn transaction_history(
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(true, None, None).await?;
let mut loaded_block: Option<(Slot, LegacyConfirmedBlock)> = None;
let mut loaded_block: Option<(Slot, ConfirmedBlock)> = None;
while limit > 0 {
let results = bigtable
.get_confirmed_signatures_for_address(
@ -257,21 +255,22 @@ pub async fn transaction_history(
loop {
if let Some((slot, block)) = &loaded_block {
if *slot == result.slot {
match block.transactions.get(index as usize) {
match block.transactions.get(index as usize).map(|tx_with_meta| {
(
tx_with_meta.get_transaction(),
tx_with_meta.get_status_meta(),
)
}) {
None => {
println!(
" Transaction info for {} is corrupt",
result.signature
);
}
Some(transaction_with_meta) => {
Some((transaction, meta)) => {
println_transaction(
&transaction_with_meta.transaction,
transaction_with_meta
.meta
.clone()
.map(|m| m.into())
.as_ref(),
&transaction,
meta.map(|m| m.into()).as_ref(),
" ",
None,
None,
@ -287,10 +286,7 @@ pub async fn transaction_history(
break;
}
Ok(confirmed_block) => {
let block = confirmed_block.into_legacy_block().ok_or_else(|| {
"Failed to read versioned transaction in block".to_string()
})?;
loaded_block = Some((result.slot, block));
loaded_block = Some((result.slot, confirmed_block));
}
}
}

View File

@ -1,5 +1,6 @@
#![allow(clippy::integer_arithmetic)]
use {
crate::{bigtable::*, ledger_path::*},
clap::{
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App,
AppSettings, Arg, ArgMatches, SubCommand,
@ -63,6 +64,7 @@ use {
transaction::{MessageHash, SanitizedTransaction, SimpleAddressLoader},
},
solana_stake_program::stake_state::{self, PointValue},
solana_transaction_status::VersionedTransactionWithStatusMeta,
solana_vote_program::{
self,
vote_state::{self, VoteState},
@ -83,9 +85,7 @@ use {
};
mod bigtable;
use bigtable::*;
mod ledger_path;
use ledger_path::*;
#[derive(PartialEq)]
enum LedgerOutputMethod {
@ -147,7 +147,7 @@ fn output_entry(
for (transactions_index, transaction) in entry.transactions.into_iter().enumerate() {
println!(" Transaction {}", transactions_index);
let tx_signature = transaction.signatures[0];
let tx_status = blockstore
let tx_with_meta = blockstore
.read_transaction_status((tx_signature, slot))
.unwrap_or_else(|err| {
eprintln!(
@ -156,21 +156,17 @@ fn output_entry(
);
None
})
.map(|transaction_status| transaction_status.into());
.map(|meta| VersionedTransactionWithStatusMeta { transaction, meta });
if let Some(legacy_tx) = transaction.into_legacy_transaction() {
if let Some(tx_with_meta) = tx_with_meta {
let status = tx_with_meta.meta.into();
solana_cli_output::display::println_transaction(
&legacy_tx,
tx_status.as_ref(),
&tx_with_meta.transaction,
Some(&status),
" ",
None,
None,
);
} else {
eprintln!(
"Failed to print unsupported transaction for {} at slot {}",
tx_signature, slot
);
}
}
}

View File

@ -2,7 +2,7 @@ use {
crate::{
hash::Hash,
instruction::CompiledInstruction,
message::{legacy::Message as LegacyMessage, MessageHeader},
message::{legacy::Message as LegacyMessage, v0::MessageAddressTableLookup, MessageHeader},
pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError},
short_vec,
@ -50,6 +50,55 @@ impl VersionedMessage {
}
}
pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
match self {
Self::Legacy(_) => None,
Self::V0(message) => Some(&message.address_table_lookups),
}
}
/// Returns true if the account at the specified index signed this
/// message.
pub fn is_signer(&self, index: usize) -> bool {
index < usize::from(self.header().num_required_signatures)
}
/// Returns true if the account at the specified index is writable by the
/// instructions in this message. Since dynamically loaded addresses can't
/// have write locks demoted without loading addresses, this shouldn't be
/// used in the runtime.
pub fn is_maybe_writable(&self, index: usize) -> bool {
match self {
Self::Legacy(message) => message.is_writable(index),
Self::V0(message) => message.is_maybe_writable(index),
}
}
/// Returns true if the account at the specified index is an input to some
/// program instruction in this message.
fn is_key_passed_to_program(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
self.instructions()
.iter()
.any(|ix| ix.accounts.contains(&key_index))
} else {
false
}
}
pub fn is_invoked(&self, key_index: usize) -> bool {
match self {
Self::Legacy(message) => message.is_key_called_as_program(key_index),
Self::V0(message) => message.is_key_called_as_program(key_index),
}
}
/// Returns true if the account at the specified index is not invoked as a
/// program or, if invoked, is passed to a program.
pub fn is_non_loader_key(&self, key_index: usize) -> bool {
!self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
}
pub fn recent_blockhash(&self) -> &Hash {
match self {
Self::Legacy(message) => &message.recent_blockhash,
@ -64,6 +113,8 @@ impl VersionedMessage {
}
}
/// Program instructions that will be executed in sequence and committed in
/// one atomic transaction if all succeed.
pub fn instructions(&self) -> &[CompiledInstruction] {
match self {
Self::Legacy(message) => &message.instructions,

View File

@ -10,12 +10,13 @@
//! [future message format]: https://docs.solana.com/proposals/transactions-v2
use crate::{
bpf_loader_upgradeable,
hash::Hash,
instruction::CompiledInstruction,
message::{MessageHeader, MESSAGE_VERSION_PREFIX},
message::{legacy::BUILTIN_PROGRAMS_KEYS, MessageHeader, MESSAGE_VERSION_PREFIX},
pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError},
short_vec,
short_vec, sysvar,
};
mod loaded;
@ -138,6 +139,70 @@ impl Message {
pub fn serialize(&self) -> Vec<u8> {
bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
}
/// Returns true if the account at the specified index is called as a program by an instruction
pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
self.instructions
.iter()
.any(|ix| ix.program_id_index == key_index)
} else {
false
}
}
/// Returns true if the account at the specified index was requested to be
/// writable. This method should not be used directly.
fn is_writable_index(&self, key_index: usize) -> bool {
let header = &self.header;
let num_account_keys = self.account_keys.len();
let num_signed_accounts = usize::from(header.num_required_signatures);
if key_index >= num_account_keys {
let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
let num_writable_dynamic_addresses = self
.address_table_lookups
.iter()
.map(|lookup| lookup.writable_indexes.len())
.sum();
loaded_addresses_index < num_writable_dynamic_addresses
} else if key_index >= num_signed_accounts {
let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
let num_writable_unsigned_accounts = num_unsigned_accounts
.saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
unsigned_account_index < num_writable_unsigned_accounts
} else {
let num_writable_signed_accounts = num_signed_accounts
.saturating_sub(usize::from(header.num_readonly_signed_accounts));
key_index < num_writable_signed_accounts
}
}
/// Returns true if any static account key is the bpf upgradeable loader
fn is_upgradeable_loader_in_static_keys(&self) -> bool {
self.account_keys
.iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
/// Returns true if the account at the specified index was requested as writable.
/// Before loading addresses, we can't demote write locks for dynamically loaded
/// addresses so this should not be used by the runtime.
pub fn is_maybe_writable(&self, key_index: usize) -> bool {
self.is_writable_index(key_index)
&& !{
// demote reserved ids
self.account_keys
.get(key_index)
.map(|key| sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key))
.unwrap_or_default()
}
&& !{
// demote program ids
self.is_key_called_as_program(key_index)
&& !self.is_upgradeable_loader_in_static_keys()
}
}
}
#[cfg(test)]

View File

@ -1,19 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
pub mod extract_memos;
pub mod parse_accounts;
pub mod parse_associated_token;
pub mod parse_bpf_loader;
pub mod parse_instruction;
pub mod parse_stake;
pub mod parse_system;
pub mod parse_token;
pub mod parse_vote;
pub mod token_balances;
pub use {crate::extract_memos::extract_and_fmt_memos, solana_runtime::bank::RewardType};
use {
@ -42,6 +27,22 @@ use {
thiserror::Error,
};
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
pub mod extract_memos;
pub mod parse_accounts;
pub mod parse_associated_token;
pub mod parse_bpf_loader;
pub mod parse_instruction;
pub mod parse_stake;
pub mod parse_system;
pub mod parse_token;
pub mod parse_vote;
pub mod token_balances;
pub struct BlockEncodingOptions {
pub transaction_details: TransactionDetails,
pub show_rewards: bool,
@ -68,6 +69,7 @@ pub trait EncodableWithMeta {
encoding: UiTransactionEncoding,
meta: &TransactionStatusMeta,
) -> Self::Encoded;
fn json_encode(&self) -> Self::Encoded;
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
@ -488,38 +490,6 @@ pub struct VersionedConfirmedBlock {
pub block_height: Option<u64>,
}
// Confirmed block which only supports legacy transactions. Used
// until migration to versioned transactions is completed.
pub struct LegacyConfirmedBlock {
pub previous_blockhash: String,
pub blockhash: String,
pub parent_slot: Slot,
pub transactions: Vec<LegacyTransactionWithStatusMeta>,
pub rewards: Rewards,
pub block_time: Option<UnixTimestamp>,
pub block_height: Option<u64>,
}
impl ConfirmedBlock {
/// Downgrades a versioned block into a legacy block type
/// if it only contains legacy transactions
pub fn into_legacy_block(self) -> Option<LegacyConfirmedBlock> {
Some(LegacyConfirmedBlock {
previous_blockhash: self.previous_blockhash,
blockhash: self.blockhash,
parent_slot: self.parent_slot,
transactions: self
.transactions
.into_iter()
.map(|tx_with_meta| tx_with_meta.into_legacy_transaction_with_meta())
.collect::<Option<Vec<_>>>()?,
rewards: self.rewards,
block_time: self.block_time,
block_height: self.block_height,
})
}
}
impl From<VersionedConfirmedBlock> for ConfirmedBlock {
fn from(block: VersionedConfirmedBlock) -> Self {
Self {
@ -641,12 +611,21 @@ pub struct VersionedTransactionWithStatusMeta {
pub meta: TransactionStatusMeta,
}
pub struct LegacyTransactionWithStatusMeta {
pub transaction: Transaction,
pub meta: Option<TransactionStatusMeta>,
}
impl TransactionWithStatusMeta {
pub fn get_status_meta(&self) -> Option<TransactionStatusMeta> {
match self {
Self::MissingMetadata(_) => None,
Self::Complete(tx_with_meta) => Some(tx_with_meta.meta.clone()),
}
}
pub fn get_transaction(&self) -> VersionedTransaction {
match self {
Self::MissingMetadata(transaction) => VersionedTransaction::from(transaction.clone()),
Self::Complete(tx_with_meta) => tx_with_meta.transaction.clone(),
}
}
pub fn transaction_signature(&self) -> &Signature {
match self {
Self::MissingMetadata(transaction) => &transaction.signatures[0],
@ -673,20 +652,6 @@ impl TransactionWithStatusMeta {
}
}
pub fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
match self {
TransactionWithStatusMeta::MissingMetadata(transaction) => {
Some(LegacyTransactionWithStatusMeta {
transaction,
meta: None,
})
}
TransactionWithStatusMeta::Complete(tx_with_meta) => {
tx_with_meta.into_legacy_transaction_with_meta()
}
}
}
pub fn account_keys(&self) -> AccountKeys {
match self {
Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
@ -739,13 +704,6 @@ impl VersionedTransactionWithStatusMeta {
Some(&self.meta.loaded_addresses),
)
}
fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
Some(LegacyTransactionWithStatusMeta {
transaction: self.transaction.into_legacy_transaction()?,
meta: Some(self.meta),
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -771,25 +729,7 @@ pub struct VersionedConfirmedTransactionWithStatusMeta {
pub block_time: Option<UnixTimestamp>,
}
pub struct LegacyConfirmedTransactionWithStatusMeta {
pub slot: Slot,
pub tx_with_meta: LegacyTransactionWithStatusMeta,
pub block_time: Option<UnixTimestamp>,
}
impl ConfirmedTransactionWithStatusMeta {
/// Downgrades a versioned confirmed transaction into a legacy
/// confirmed transaction if it contains a legacy transaction.
pub fn into_legacy_confirmed_transaction(
self,
) -> Option<LegacyConfirmedTransactionWithStatusMeta> {
Some(LegacyConfirmedTransactionWithStatusMeta {
tx_with_meta: self.tx_with_meta.into_legacy_transaction_with_meta()?,
block_time: self.block_time,
slot: self.slot,
})
}
pub fn encode(
self,
encoding: UiTransactionEncoding,
@ -803,6 +743,10 @@ impl ConfirmedTransactionWithStatusMeta {
block_time: self.block_time,
})
}
pub fn get_transaction(&self) -> VersionedTransaction {
self.tx_with_meta.get_transaction()
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
@ -841,17 +785,29 @@ impl EncodableWithMeta for VersionedTransaction {
base64::encode(bincode::serialize(self).unwrap()),
TransactionBinaryEncoding::Base64,
),
UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
EncodedTransaction::Json(UiTransaction {
signatures: self.signatures.iter().map(ToString::to_string).collect(),
message: match &self.message {
VersionedMessage::Legacy(message) => message.encode(encoding),
VersionedMessage::V0(message) => message.encode_with_meta(encoding, meta),
},
})
}
UiTransactionEncoding::Json => self.json_encode(),
UiTransactionEncoding::JsonParsed => EncodedTransaction::Json(UiTransaction {
signatures: self.signatures.iter().map(ToString::to_string).collect(),
message: match &self.message {
VersionedMessage::Legacy(message) => {
message.encode(UiTransactionEncoding::JsonParsed)
}
VersionedMessage::V0(message) => {
message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
}
},
}),
}
}
fn json_encode(&self) -> Self::Encoded {
EncodedTransaction::Json(UiTransaction {
signatures: self.signatures.iter().map(ToString::to_string).collect(),
message: match &self.message {
VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
VersionedMessage::V0(message) => message.json_encode(),
},
})
}
}
impl Encodable for Transaction {
@ -880,23 +836,23 @@ impl Encodable for Transaction {
}
impl EncodedTransaction {
pub fn decode(&self) -> Option<Transaction> {
let transaction: Option<Transaction> = match self {
EncodedTransaction::Json(_) => None,
EncodedTransaction::LegacyBinary(blob) => bs58::decode(blob)
pub fn decode(&self) -> Option<VersionedTransaction> {
let (blob, encoding) = match self {
Self::Json(_) => return None,
Self::LegacyBinary(blob) => (blob, TransactionBinaryEncoding::Base58),
Self::Binary(blob, encoding) => (blob, *encoding),
};
let transaction: Option<VersionedTransaction> = match encoding {
TransactionBinaryEncoding::Base58 => bs58::decode(blob)
.into_vec()
.ok()
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
EncodedTransaction::Binary(blob, encoding) => match *encoding {
TransactionBinaryEncoding::Base58 => bs58::decode(blob)
.into_vec()
.ok()
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
TransactionBinaryEncoding::Base64 => base64::decode(blob)
.ok()
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
},
TransactionBinaryEncoding::Base64 => base64::decode(blob)
.ok()
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
};
transaction.filter(|transaction| transaction.sanitize().is_ok())
}
}
@ -966,17 +922,20 @@ impl EncodableWithMeta for v0::Message {
),
})
} else {
UiMessage::Raw(UiRawMessage {
header: self.header,
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
recent_blockhash: self.recent_blockhash.to_string(),
instructions: self.instructions.iter().map(Into::into).collect(),
address_table_lookups: Some(
self.address_table_lookups.iter().map(Into::into).collect(),
),
})
self.json_encode()
}
}
fn json_encode(&self) -> Self::Encoded {
UiMessage::Raw(UiRawMessage {
header: self.header,
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
recent_blockhash: self.recent_blockhash.to_string(),
instructions: self.instructions.iter().map(Into::into).collect(),
address_table_lookups: Some(
self.address_table_lookups.iter().map(Into::into).collect(),
),
})
}
}
/// A duplicate representation of a Message, in raw format, for pretty JSON serialization