agave_transaction_view/
transaction_view.rsuse {
crate::{
address_table_lookup_frame::AddressTableLookupIterator,
instructions_frame::InstructionsIterator, message_header_frame::TransactionVersion,
result::Result, sanitize::sanitize, transaction_data::TransactionData,
transaction_frame::TransactionFrame,
},
core::fmt::{Debug, Formatter},
solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature},
solana_svm_transaction::instruction::SVMInstruction,
};
pub type UnsanitizedTransactionView<D> = TransactionView<false, D>;
pub type SanitizedTransactionView<D> = TransactionView<true, D>;
pub struct TransactionView<const SANITIZED: bool, D: TransactionData> {
data: D,
frame: TransactionFrame,
}
impl<D: TransactionData> TransactionView<false, D> {
pub fn try_new_unsanitized(data: D) -> Result<Self> {
let frame = TransactionFrame::try_new(data.data())?;
Ok(Self { data, frame })
}
pub fn sanitize(self) -> Result<SanitizedTransactionView<D>> {
sanitize(&self)?;
Ok(SanitizedTransactionView {
data: self.data,
frame: self.frame,
})
}
}
impl<D: TransactionData> TransactionView<true, D> {
pub fn try_new_sanitized(data: D) -> Result<Self> {
let unsanitized_view = TransactionView::try_new_unsanitized(data)?;
unsanitized_view.sanitize()
}
}
impl<const SANITIZED: bool, D: TransactionData> TransactionView<SANITIZED, D> {
#[inline]
pub fn num_signatures(&self) -> u8 {
self.frame.num_signatures()
}
#[inline]
pub fn version(&self) -> TransactionVersion {
self.frame.version()
}
#[inline]
pub fn num_required_signatures(&self) -> u8 {
self.frame.num_required_signatures()
}
#[inline]
pub fn num_readonly_signed_static_accounts(&self) -> u8 {
self.frame.num_readonly_signed_static_accounts()
}
#[inline]
pub fn num_readonly_unsigned_static_accounts(&self) -> u8 {
self.frame.num_readonly_unsigned_static_accounts()
}
#[inline]
pub fn num_static_account_keys(&self) -> u8 {
self.frame.num_static_account_keys()
}
#[inline]
pub fn num_instructions(&self) -> u16 {
self.frame.num_instructions()
}
#[inline]
pub fn num_address_table_lookups(&self) -> u8 {
self.frame.num_address_table_lookups()
}
#[inline]
pub fn total_writable_lookup_accounts(&self) -> u16 {
self.frame.total_writable_lookup_accounts()
}
#[inline]
pub fn total_readonly_lookup_accounts(&self) -> u16 {
self.frame.total_readonly_lookup_accounts()
}
#[inline]
pub fn signatures(&self) -> &[Signature] {
let data = self.data();
unsafe { self.frame.signatures(data) }
}
#[inline]
pub fn static_account_keys(&self) -> &[Pubkey] {
let data = self.data();
unsafe { self.frame.static_account_keys(data) }
}
#[inline]
pub fn recent_blockhash(&self) -> &Hash {
let data = self.data();
unsafe { self.frame.recent_blockhash(data) }
}
#[inline]
pub fn instructions_iter(&self) -> InstructionsIterator {
let data = self.data();
unsafe { self.frame.instructions_iter(data) }
}
#[inline]
pub fn address_table_lookup_iter(&self) -> AddressTableLookupIterator {
let data = self.data();
unsafe { self.frame.address_table_lookup_iter(data) }
}
#[inline]
pub fn data(&self) -> &[u8] {
self.data.data()
}
#[inline]
pub fn message_data(&self) -> &[u8] {
&self.data()[usize::from(self.frame.message_offset())..]
}
}
impl<D: TransactionData> TransactionView<true, D> {
pub fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> {
self.instructions_iter().map(|ix| {
let program_id_index = usize::from(ix.program_id_index);
let program_id = &self.static_account_keys()[program_id_index];
(program_id, ix)
})
}
#[inline]
pub(crate) fn num_static_unsigned_static_accounts(&self) -> u8 {
self.num_static_account_keys()
.wrapping_sub(self.num_required_signatures())
}
#[inline]
pub(crate) fn num_writable_unsigned_static_accounts(&self) -> u8 {
self.num_static_unsigned_static_accounts()
.wrapping_sub(self.num_readonly_unsigned_static_accounts())
}
#[inline]
pub(crate) fn num_writable_signed_static_accounts(&self) -> u8 {
self.num_required_signatures()
.wrapping_sub(self.num_readonly_signed_static_accounts())
}
}
impl<const SANITIZED: bool, D: TransactionData> Debug for TransactionView<SANITIZED, D> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TransactionView")
.field("frame", &self.frame)
.field("signatures", &self.signatures())
.field("static_account_keys", &self.static_account_keys())
.field("recent_blockhash", &self.recent_blockhash())
.field("instructions", &self.instructions_iter())
.field("address_table_lookups", &self.address_table_lookup_iter())
.finish()
}
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_sdk::{
message::{Message, VersionedMessage},
pubkey::Pubkey,
signature::Signature,
system_instruction::{self},
transaction::VersionedTransaction,
},
};
fn verify_transaction_view_frame(tx: &VersionedTransaction) {
let bytes = bincode::serialize(tx).unwrap();
let view = TransactionView::try_new_unsanitized(bytes.as_ref()).unwrap();
assert_eq!(view.num_signatures(), tx.signatures.len() as u8);
assert_eq!(
view.num_required_signatures(),
tx.message.header().num_required_signatures
);
assert_eq!(
view.num_readonly_signed_static_accounts(),
tx.message.header().num_readonly_signed_accounts
);
assert_eq!(
view.num_readonly_unsigned_static_accounts(),
tx.message.header().num_readonly_unsigned_accounts
);
assert_eq!(
view.num_static_account_keys(),
tx.message.static_account_keys().len() as u8
);
assert_eq!(
view.num_instructions(),
tx.message.instructions().len() as u16
);
assert_eq!(
view.num_address_table_lookups(),
tx.message
.address_table_lookups()
.map(|x| x.len() as u8)
.unwrap_or(0)
);
}
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),
)),
}
}
#[test]
fn test_multiple_transfers() {
verify_transaction_view_frame(&multiple_transfers());
}
}