Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TransactionView: InstructionsIterator #2580

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was on the fence about whether this should return the actual type or just an impl Iterator/ExactSizeIterator.

Anyone got strong feelings on this one way or another?

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());
}
}
}