use {
crate::{
address_table_lookup_frame::{AddressTableLookupFrame, AddressTableLookupIterator},
bytes::advance_offset_for_type,
instructions_frame::{InstructionsFrame, InstructionsIterator},
message_header_frame::MessageHeaderFrame,
result::{Result, TransactionViewError},
signature_frame::SignatureFrame,
static_account_keys_frame::StaticAccountKeysFrame,
transaction_version::TransactionVersion,
},
solana_hash::Hash,
solana_pubkey::Pubkey,
solana_signature::Signature,
};
#[derive(Debug)]
pub(crate) struct TransactionFrame {
signature: SignatureFrame,
message_header: MessageHeaderFrame,
static_account_keys: StaticAccountKeysFrame,
recent_blockhash_offset: u16,
instructions: InstructionsFrame,
address_table_lookup: AddressTableLookupFrame,
}
impl TransactionFrame {
pub(crate) fn try_new(bytes: &[u8]) -> Result<Self> {
let mut offset = 0;
let signature = SignatureFrame::try_new(bytes, &mut offset)?;
let message_header = MessageHeaderFrame::try_new(bytes, &mut offset)?;
let static_account_keys = StaticAccountKeysFrame::try_new(bytes, &mut offset)?;
let recent_blockhash_offset = offset as u16;
advance_offset_for_type::<Hash>(bytes, &mut offset)?;
let instructions = InstructionsFrame::try_new(bytes, &mut offset)?;
let address_table_lookup = match message_header.version {
TransactionVersion::Legacy => AddressTableLookupFrame {
num_address_table_lookups: 0,
offset: 0,
total_writable_lookup_accounts: 0,
total_readonly_lookup_accounts: 0,
},
TransactionVersion::V0 => AddressTableLookupFrame::try_new(bytes, &mut offset)?,
};
if offset != bytes.len() {
return Err(TransactionViewError::ParseError);
}
Ok(Self {
signature,
message_header,
static_account_keys,
recent_blockhash_offset,
instructions,
address_table_lookup,
})
}
#[inline]
pub(crate) fn num_signatures(&self) -> u8 {
self.signature.num_signatures
}
#[inline]
pub(crate) fn version(&self) -> TransactionVersion {
self.message_header.version
}
#[inline]
pub(crate) fn num_required_signatures(&self) -> u8 {
self.message_header.num_required_signatures
}
#[inline]
pub(crate) fn num_readonly_signed_static_accounts(&self) -> u8 {
self.message_header.num_readonly_signed_accounts
}
#[inline]
pub(crate) fn num_readonly_unsigned_static_accounts(&self) -> u8 {
self.message_header.num_readonly_unsigned_accounts
}
#[inline]
pub(crate) fn num_static_account_keys(&self) -> u8 {
self.static_account_keys.num_static_accounts
}
#[inline]
pub(crate) fn num_instructions(&self) -> u16 {
self.instructions.num_instructions
}
#[inline]
pub(crate) fn num_address_table_lookups(&self) -> u8 {
self.address_table_lookup.num_address_table_lookups
}
#[inline]
pub(crate) fn total_writable_lookup_accounts(&self) -> u16 {
self.address_table_lookup.total_writable_lookup_accounts
}
#[inline]
pub(crate) fn total_readonly_lookup_accounts(&self) -> u16 {
self.address_table_lookup.total_readonly_lookup_accounts
}
#[inline]
pub(crate) fn message_offset(&self) -> u16 {
self.message_header.offset
}
}
impl TransactionFrame {
#[inline]
pub(crate) unsafe fn signatures<'a>(&self, bytes: &'a [u8]) -> &'a [Signature] {
const _: () = assert!(
core::mem::align_of::<Signature>() == 1,
"Signature alignment"
);
const _: () =
assert!(u8::MAX as usize * core::mem::size_of::<Signature>() <= isize::MAX as usize);
core::slice::from_raw_parts(
bytes.as_ptr().add(usize::from(self.signature.offset)) as *const Signature,
usize::from(self.signature.num_signatures),
)
}
#[inline]
pub(crate) unsafe fn static_account_keys<'a>(&self, bytes: &'a [u8]) -> &'a [Pubkey] {
const _: () = assert!(core::mem::align_of::<Pubkey>() == 1, "Pubkey alignment");
const _: () =
assert!(u8::MAX as usize * core::mem::size_of::<Pubkey>() <= isize::MAX as usize);
core::slice::from_raw_parts(
bytes
.as_ptr()
.add(usize::from(self.static_account_keys.offset)) as *const Pubkey,
usize::from(self.static_account_keys.num_static_accounts),
)
}
#[inline]
pub(crate) unsafe fn recent_blockhash<'a>(&self, bytes: &'a [u8]) -> &'a Hash {
const _: () = assert!(core::mem::align_of::<Hash>() == 1, "Hash alignment");
&*(bytes
.as_ptr()
.add(usize::from(self.recent_blockhash_offset)) as *const Hash)
}
#[inline]
pub(crate) unsafe fn instructions_iter<'a>(
&'a self,
bytes: &'a [u8],
) -> InstructionsIterator<'a> {
InstructionsIterator {
bytes,
offset: usize::from(self.instructions.offset),
num_instructions: self.instructions.num_instructions,
index: 0,
frames: &self.instructions.frames,
}
}
#[inline]
pub(crate) unsafe fn address_table_lookup_iter<'a>(
&self,
bytes: &'a [u8],
) -> AddressTableLookupIterator<'a> {
AddressTableLookupIterator {
bytes,
offset: usize::from(self.address_table_lookup.offset),
num_address_table_lookups: self.address_table_lookup.num_address_table_lookups,
index: 0,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_message::{v0, AddressLookupTableAccount, Message, MessageHeader, VersionedMessage},
solana_pubkey::Pubkey,
solana_signature::Signature,
solana_system_interface::instruction::{self as system_instruction, SystemInstruction},
solana_transaction::versioned::VersionedTransaction,
};
fn verify_transaction_view_frame(tx: &VersionedTransaction) {
let bytes = bincode::serialize(tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
assert_eq!(frame.signature.num_signatures, tx.signatures.len() as u8);
assert_eq!(frame.signature.offset as usize, 1);
assert_eq!(
frame.message_header.num_required_signatures,
tx.message.header().num_required_signatures
);
assert_eq!(
frame.message_header.num_readonly_signed_accounts,
tx.message.header().num_readonly_signed_accounts
);
assert_eq!(
frame.message_header.num_readonly_unsigned_accounts,
tx.message.header().num_readonly_unsigned_accounts
);
assert_eq!(
frame.static_account_keys.num_static_accounts,
tx.message.static_account_keys().len() as u8
);
assert_eq!(
frame.instructions.num_instructions,
tx.message.instructions().len() as u16
);
assert_eq!(
frame.address_table_lookup.num_address_table_lookups,
tx.message
.address_table_lookups()
.map(|x| x.len() as u8)
.unwrap_or(0)
);
}
fn minimally_sized_transaction() -> VersionedTransaction {
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::Legacy(Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
account_keys: vec![Pubkey::default()],
recent_blockhash: Hash::default(),
instructions: vec![],
}),
}
}
fn simple_transfer() -> VersionedTransaction {
let payer = Pubkey::new_unique();
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::Legacy(Message::new(
&[system_instruction::transfer(
&payer,
&Pubkey::new_unique(),
1,
)],
Some(&payer),
)),
}
}
fn simple_transfer_v0() -> VersionedTransaction {
let payer = Pubkey::new_unique();
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::V0(
v0::Message::try_compile(
&payer,
&[system_instruction::transfer(
&payer,
&Pubkey::new_unique(),
1,
)],
&[],
Hash::default(),
)
.unwrap(),
),
}
}
fn multiple_transfers() -> VersionedTransaction {
let payer = Pubkey::new_unique();
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::Legacy(Message::new(
&[
system_instruction::transfer(&payer, &Pubkey::new_unique(), 1),
system_instruction::transfer(&payer, &Pubkey::new_unique(), 1),
],
Some(&payer),
)),
}
}
fn v0_with_single_lookup() -> VersionedTransaction {
let payer = Pubkey::new_unique();
let to = Pubkey::new_unique();
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::V0(
v0::Message::try_compile(
&payer,
&[system_instruction::transfer(&payer, &to, 1)],
&[AddressLookupTableAccount {
key: Pubkey::new_unique(),
addresses: vec![to],
}],
Hash::default(),
)
.unwrap(),
),
}
}
fn v0_with_multiple_lookups() -> VersionedTransaction {
let payer = Pubkey::new_unique();
let to1 = Pubkey::new_unique();
let to2 = Pubkey::new_unique();
VersionedTransaction {
signatures: vec![Signature::default()], message: VersionedMessage::V0(
v0::Message::try_compile(
&payer,
&[
system_instruction::transfer(&payer, &to1, 1),
system_instruction::transfer(&payer, &to2, 1),
],
&[
AddressLookupTableAccount {
key: Pubkey::new_unique(),
addresses: vec![to1],
},
AddressLookupTableAccount {
key: Pubkey::new_unique(),
addresses: vec![to2],
},
],
Hash::default(),
)
.unwrap(),
),
}
}
#[test]
fn test_minimal_sized_transaction() {
verify_transaction_view_frame(&minimally_sized_transaction());
}
#[test]
fn test_simple_transfer() {
verify_transaction_view_frame(&simple_transfer());
}
#[test]
fn test_simple_transfer_v0() {
verify_transaction_view_frame(&simple_transfer_v0());
}
#[test]
fn test_v0_with_lookup() {
verify_transaction_view_frame(&v0_with_single_lookup());
}
#[test]
fn test_trailing_byte() {
let tx = simple_transfer();
let mut bytes = bincode::serialize(&tx).unwrap();
bytes.push(0);
assert!(TransactionFrame::try_new(&bytes).is_err());
}
#[test]
fn test_insufficient_bytes() {
let tx = simple_transfer();
let bytes = bincode::serialize(&tx).unwrap();
assert!(TransactionFrame::try_new(&bytes[..bytes.len().wrapping_sub(1)]).is_err());
}
#[test]
fn test_signature_overflow() {
let tx = simple_transfer();
let mut bytes = bincode::serialize(&tx).unwrap();
bytes[0] = 0xff;
bytes[1] = 0xff;
bytes[2] = 0xff;
assert!(TransactionFrame::try_new(&bytes).is_err());
}
#[test]
fn test_account_key_overflow() {
let tx = simple_transfer();
let mut bytes = bincode::serialize(&tx).unwrap();
let offset = 1 + core::mem::size_of::<Signature>() + 3;
bytes[offset] = 0xff;
bytes[offset + 1] = 0xff;
bytes[offset + 2] = 0xff;
assert!(TransactionFrame::try_new(&bytes).is_err());
}
#[test]
fn test_instructions_overflow() {
let tx = simple_transfer();
let mut bytes = bincode::serialize(&tx).unwrap();
let offset = 1
+ core::mem::size_of::<Signature>()
+ 3
+ 1
+ 3 * core::mem::size_of::<Pubkey>()
+ core::mem::size_of::<Hash>();
bytes[offset] = 0xff;
bytes[offset + 1] = 0xff;
bytes[offset + 2] = 0xff;
assert!(TransactionFrame::try_new(&bytes).is_err());
}
#[test]
fn test_alt_overflow() {
let tx = simple_transfer_v0();
let ix_bytes = tx.message.instructions()[0].data.len();
let mut bytes = bincode::serialize(&tx).unwrap();
let offset = 1 + core::mem::size_of::<Signature>() + 1 + 3 + 1 + 3 * core::mem::size_of::<Pubkey>() + core::mem::size_of::<Hash>() + 1 + 1 + 1 + 2 + 1 + ix_bytes;
bytes[offset] = 0x01;
assert!(TransactionFrame::try_new(&bytes).is_err());
}
#[test]
fn test_basic_accessors() {
let tx = simple_transfer();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
assert_eq!(frame.num_signatures(), 1);
assert!(matches!(frame.version(), TransactionVersion::Legacy));
assert_eq!(frame.num_required_signatures(), 1);
assert_eq!(frame.num_readonly_signed_static_accounts(), 0);
assert_eq!(frame.num_readonly_unsigned_static_accounts(), 1);
assert_eq!(frame.num_static_account_keys(), 3);
assert_eq!(frame.num_instructions(), 1);
assert_eq!(frame.num_address_table_lookups(), 0);
unsafe {
let signatures = frame.signatures(&bytes);
assert_eq!(signatures, &tx.signatures);
let static_account_keys = frame.static_account_keys(&bytes);
assert_eq!(static_account_keys, tx.message.static_account_keys());
let recent_blockhash = frame.recent_blockhash(&bytes);
assert_eq!(recent_blockhash, tx.message.recent_blockhash());
}
}
#[test]
fn test_instructions_iter_empty() {
let tx = minimally_sized_transaction();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
unsafe {
let mut iter = frame.instructions_iter(&bytes);
assert!(iter.next().is_none());
}
}
#[test]
fn test_instructions_iter_single() {
let tx = simple_transfer();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
unsafe {
let mut iter = frame.instructions_iter(&bytes);
let ix = iter.next().unwrap();
assert_eq!(ix.program_id_index, 2);
assert_eq!(ix.accounts, &[0, 1]);
assert_eq!(
ix.data,
&bincode::serialize(&SystemInstruction::Transfer { lamports: 1 }).unwrap()
);
assert!(iter.next().is_none());
}
}
#[test]
fn test_instructions_iter_multiple() {
let tx = multiple_transfers();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
unsafe {
let mut iter = frame.instructions_iter(&bytes);
let ix = iter.next().unwrap();
assert_eq!(ix.program_id_index, 3);
assert_eq!(ix.accounts, &[0, 1]);
assert_eq!(
ix.data,
&bincode::serialize(&SystemInstruction::Transfer { lamports: 1 }).unwrap()
);
let ix = iter.next().unwrap();
assert_eq!(ix.program_id_index, 3);
assert_eq!(ix.accounts, &[0, 2]);
assert_eq!(
ix.data,
&bincode::serialize(&SystemInstruction::Transfer { lamports: 1 }).unwrap()
);
assert!(iter.next().is_none());
}
}
#[test]
fn test_address_table_lookup_iter_empty() {
let tx = simple_transfer();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
unsafe {
let mut iter = frame.address_table_lookup_iter(&bytes);
assert!(iter.next().is_none());
}
}
#[test]
fn test_address_table_lookup_iter_single() {
let tx = v0_with_single_lookup();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
let atls_actual = tx.message.address_table_lookups().unwrap();
unsafe {
let mut iter = frame.address_table_lookup_iter(&bytes);
let lookup = iter.next().unwrap();
assert_eq!(lookup.account_key, &atls_actual[0].account_key);
assert_eq!(lookup.writable_indexes, atls_actual[0].writable_indexes);
assert_eq!(lookup.readonly_indexes, atls_actual[0].readonly_indexes);
assert!(iter.next().is_none());
}
}
#[test]
fn test_address_table_lookup_iter_multiple() {
let tx = v0_with_multiple_lookups();
let bytes = bincode::serialize(&tx).unwrap();
let frame = TransactionFrame::try_new(&bytes).unwrap();
let atls_actual = tx.message.address_table_lookups().unwrap();
unsafe {
let mut iter = frame.address_table_lookup_iter(&bytes);
let lookup = iter.next().unwrap();
assert_eq!(lookup.account_key, &atls_actual[0].account_key);
assert_eq!(lookup.writable_indexes, atls_actual[0].writable_indexes);
assert_eq!(lookup.readonly_indexes, atls_actual[0].readonly_indexes);
let lookup = iter.next().unwrap();
assert_eq!(lookup.account_key, &atls_actual[1].account_key);
assert_eq!(lookup.writable_indexes, atls_actual[1].writable_indexes);
assert_eq!(lookup.readonly_indexes, atls_actual[1].readonly_indexes);
assert!(iter.next().is_none());
}
}
}