Skip to content

Commit

Permalink
TransactionView: InstructionsIterator (#2580)
Browse files Browse the repository at this point in the history
  • Loading branch information
apfitzge authored Aug 14, 2024
1 parent b8de432 commit 472d848
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions transaction-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ edition = { workspace = true }

[dependencies]
solana-sdk = { workspace = true }
solana-svm-transaction = { workspace = true }

[dev-dependencies]
# See order-crates-for-publishing.py for using this unusual `path = "."`
Expand Down
73 changes: 70 additions & 3 deletions transaction-view/src/instructions_meta.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::{
bytes::{advance_offset_for_array, check_remaining, optimized_read_compressed_u16, read_byte},
result::Result,
use {
crate::{
bytes::{
advance_offset_for_array, check_remaining, optimized_read_compressed_u16, read_byte,
},
result::Result,
},
solana_svm_transaction::instruction::SVMInstruction,
};

/// Contains metadata about the instructions in a transaction packet.
Expand Down Expand Up @@ -64,6 +69,68 @@ impl InstructionsMeta {
}
}

pub struct InstructionsIterator<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) offset: usize,
pub(crate) num_instructions: u16,
pub(crate) index: u16,
}

impl<'a> Iterator for InstructionsIterator<'a> {
type Item = SVMInstruction<'a>;

fn next(&mut self) -> Option<Self::Item> {
if self.index < self.num_instructions {
// Each instruction has 3 pieces:
// 1. Program ID index (u8)
// 2. Accounts indexes ([u8])
// 3. Data ([u8])

// Read the program ID index.
let program_id_index = read_byte(self.bytes, &mut self.offset).ok()?;

// Read the number of account indexes, and then update the offset
// to skip over the account indexes.
let num_accounts = optimized_read_compressed_u16(self.bytes, &mut self.offset).ok()?;
// SAFETY: Only returned after we check that there are enough bytes.
let accounts = unsafe {
core::slice::from_raw_parts(
self.bytes.as_ptr().add(self.offset),
usize::from(num_accounts),
)
};
advance_offset_for_array::<u8>(self.bytes, &mut self.offset, num_accounts).ok()?;

// Read the length of the data, and then update the offset to skip
// over the data.
let data_len = optimized_read_compressed_u16(self.bytes, &mut self.offset).ok()?;
// SAFETY: Only returned after we check that there are enough bytes.
let data = unsafe {
core::slice::from_raw_parts(
self.bytes.as_ptr().add(self.offset),
usize::from(data_len),
)
};
advance_offset_for_array::<u8>(self.bytes, &mut self.offset, data_len).ok()?;
self.index = self.index.wrapping_add(1);

Some(SVMInstruction {
program_id_index,
accounts,
data,
})
} else {
None
}
}
}

impl ExactSizeIterator for InstructionsIterator<'_> {
fn len(&self) -> usize {
usize::from(self.num_instructions.wrapping_sub(self.index))
}
}

#[cfg(test)]
mod tests {
use {
Expand Down
37 changes: 35 additions & 2 deletions transaction-view/src/transaction_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
crate::{
address_table_lookup_meta::AddressTableLookupMeta,
bytes::advance_offset_for_type,
instructions_meta::InstructionsMeta,
instructions_meta::{InstructionsIterator, InstructionsMeta},
message_header_meta::{MessageHeaderMeta, TransactionVersion},
result::{Result, TransactionParsingError},
signature_meta::SignatureMeta,
Expand Down Expand Up @@ -142,6 +142,19 @@ impl TransactionMeta {
.as_ptr()
.add(usize::from(self.recent_blockhash_offset)) as *const Hash)
}

/// Return an iterator over the instructions in the transaction.
/// # Safety
/// - This function must be called with the same `bytes` slice that was
/// used to create the `TransactionMeta` instance.
pub unsafe fn instructions_iter<'a>(&self, bytes: &'a [u8]) -> InstructionsIterator<'a> {
InstructionsIterator {
bytes,
offset: usize::from(self.instructions.offset),
num_instructions: self.instructions.num_instructions,
index: 0,
}
}
}

#[cfg(test)]
Expand All @@ -153,7 +166,7 @@ mod tests {
message::{v0, Message, MessageHeader, VersionedMessage},
pubkey::Pubkey,
signature::Signature,
system_instruction,
system_instruction::{self, SystemInstruction},
transaction::VersionedTransaction,
},
};
Expand Down Expand Up @@ -391,4 +404,24 @@ mod tests {
assert_eq!(recent_blockhash, tx.message.recent_blockhash());
}
}

#[test]
fn test_instructions_iter() {
let tx = simple_transfer();
let bytes = bincode::serialize(&tx).unwrap();
let meta = TransactionMeta::try_new(&bytes).unwrap();

// SAFETY: `bytes` is the same slice used to create `meta`.
unsafe {
let mut iter = meta.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());
}
}
}

0 comments on commit 472d848

Please sign in to comment.