Add CLI support for versioned transactions (#23606)
This commit is contained in:
parent
330d6db19a
commit
0eccacbd5b
|
@ -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")]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue