Skip to content

Commit

Permalink
TransactionView: Signature Meta (#2408)
Browse files Browse the repository at this point in the history
  • Loading branch information
apfitzge authored Aug 5, 2024
1 parent a1c00e5 commit 6ac4fe3
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 8 deletions.
20 changes: 12 additions & 8 deletions transaction-view/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ pub fn optimized_read_compressed_u16(bytes: &[u8], offset: &mut usize) -> Result
/// 2. The size of `T` is small enough such that a usize will not overflow if
/// given the maximum array size (u16::MAX).
#[inline(always)]
pub fn offset_array_len<T: Sized>(bytes: &[u8], offset: &mut usize, len: u16) -> Result<()> {
pub fn advance_offset_for_array<T: Sized>(
bytes: &[u8],
offset: &mut usize,
len: u16,
) -> Result<()> {
let array_len_bytes = usize::from(len).wrapping_mul(core::mem::size_of::<T>());
check_remaining(bytes, *offset, array_len_bytes)?;
*offset = offset.wrapping_add(array_len_bytes);
Expand All @@ -116,7 +120,7 @@ pub fn offset_array_len<T: Sized>(bytes: &[u8], offset: &mut usize, len: u16) ->
/// 1. The current offset is not greater than `bytes.len()`.
/// 2. The size of `T` is small enough such that a usize will not overflow.
#[inline(always)]
pub fn offset_type<T: Sized>(bytes: &[u8], offset: &mut usize) -> Result<()> {
pub fn advance_offset_for_type<T: Sized>(bytes: &[u8], offset: &mut usize) -> Result<()> {
let type_size = core::mem::size_of::<T>();
check_remaining(bytes, *offset, type_size)?;
*offset = offset.wrapping_add(type_size);
Expand Down Expand Up @@ -267,7 +271,7 @@ mod tests {
}

#[test]
fn test_offset_array_len() {
fn test_advance_offset_for_array() {
#[repr(C)]
struct MyStruct {
_a: u8,
Expand All @@ -278,17 +282,17 @@ mod tests {
// Test with a buffer that is too short
let bytes = [0u8; 1];
let mut offset = 0;
assert!(offset_array_len::<MyStruct>(&bytes, &mut offset, 1).is_err());
assert!(advance_offset_for_array::<MyStruct>(&bytes, &mut offset, 1).is_err());

// Test with a buffer that is long enough
let bytes = [0u8; 4];
let mut offset = 0;
assert!(offset_array_len::<MyStruct>(&bytes, &mut offset, 2).is_ok());
assert!(advance_offset_for_array::<MyStruct>(&bytes, &mut offset, 2).is_ok());
assert_eq!(offset, 4);
}

#[test]
fn test_offset_type() {
fn test_advance_offset_for_type() {
#[repr(C)]
struct MyStruct {
_a: u8,
Expand All @@ -299,12 +303,12 @@ mod tests {
// Test with a buffer that is too short
let bytes = [0u8; 1];
let mut offset = 0;
assert!(offset_type::<MyStruct>(&bytes, &mut offset).is_err());
assert!(advance_offset_for_type::<MyStruct>(&bytes, &mut offset).is_err());

// Test with a buffer that is long enough
let bytes = [0u8; 4];
let mut offset = 0;
assert!(offset_type::<MyStruct>(&bytes, &mut offset).is_ok());
assert!(advance_offset_for_type::<MyStruct>(&bytes, &mut offset).is_ok());
assert_eq!(offset, 2);
}
}
2 changes: 2 additions & 0 deletions transaction-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ pub mod bytes;
mod bytes;

pub mod result;
#[allow(dead_code)]
mod signature_meta;
108 changes: 108 additions & 0 deletions transaction-view/src/signature_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use {
crate::{
bytes::{advance_offset_for_array, read_byte},
result::{Result, TransactionParsingError},
},
solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Signature},
};

/// Meta data for accessing transaction-level signatures in a transaction view.
pub(crate) struct SignatureMeta {
/// The number of signatures in the transaction.
pub(crate) num_signatures: u16,
/// Offset to the first signature in the transaction packet.
pub(crate) offset: u16,
}

impl SignatureMeta {
/// Get the number of signatures and the offset to the first signature in
/// the transaction packet, starting at the given `offset`.
pub(crate) fn try_new(bytes: &[u8], offset: &mut usize) -> Result<Self> {
// The packet has a maximum length of 1232 bytes.
// Each signature must be paired with a unique static pubkey, so each
// signature really requires 96 bytes. This means the maximum number of
// signatures in a **valid** transaction packet is 12.
// In our u16 encoding scheme, 12 would be encoded as a single byte.
// Rather than using the u16 decoding, we can simply read the byte and
// verify that the MSB is not set.
const MAX_SIGNATURES_PER_PACKET: u16 = (PACKET_DATA_SIZE
/ (core::mem::size_of::<Signature>() + core::mem::size_of::<Pubkey>()))
as u16;
// Maximum number of signatures should be represented by a single byte,
// thus the MSB should not be set.
const _: () = assert!(MAX_SIGNATURES_PER_PACKET & 0b1000_0000 == 0);

let num_signatures = read_byte(bytes, offset)? as u16;
if num_signatures == 0 || num_signatures > MAX_SIGNATURES_PER_PACKET {
return Err(TransactionParsingError);
}

let signature_offset = *offset as u16;
advance_offset_for_array::<Signature>(bytes, offset, num_signatures)?;

Ok(Self {
num_signatures,
offset: signature_offset,
})
}
}

#[cfg(test)]
mod tests {
use {super::*, solana_sdk::short_vec::ShortVec};

#[test]
fn test_zero_signatures() {
let bytes = bincode::serialize(&ShortVec(Vec::<Signature>::new())).unwrap();
let mut offset = 0;
assert!(SignatureMeta::try_new(&bytes, &mut offset).is_err());
}

#[test]
fn test_one_signature() {
let bytes = bincode::serialize(&ShortVec(vec![Signature::default()])).unwrap();
let mut offset = 0;
let meta = SignatureMeta::try_new(&bytes, &mut offset).unwrap();
assert_eq!(meta.num_signatures, 1);
assert_eq!(meta.offset, 1);
assert_eq!(offset, 1 + core::mem::size_of::<Signature>());
}

#[test]
fn test_max_signatures() {
let signatures = vec![Signature::default(); 12];
let bytes = bincode::serialize(&ShortVec(signatures)).unwrap();
let mut offset = 0;
let meta = SignatureMeta::try_new(&bytes, &mut offset).unwrap();
assert_eq!(meta.num_signatures, 12);
assert_eq!(meta.offset, 1);
assert_eq!(offset, 1 + 12 * core::mem::size_of::<Signature>());
}

#[test]
fn test_non_zero_offset() {
let mut bytes = bincode::serialize(&ShortVec(vec![Signature::default()])).unwrap();
bytes.insert(0, 0); // Insert a byte at the beginning of the packet.
let mut offset = 1; // Start at the second byte.
let meta = SignatureMeta::try_new(&bytes, &mut offset).unwrap();
assert_eq!(meta.num_signatures, 1);
assert_eq!(meta.offset, 2);
assert_eq!(offset, 2 + core::mem::size_of::<Signature>());
}

#[test]
fn test_too_many_signatures() {
let signatures = vec![Signature::default(); 13];
let bytes = bincode::serialize(&ShortVec(signatures)).unwrap();
let mut offset = 0;
assert!(SignatureMeta::try_new(&bytes, &mut offset).is_err());
}

#[test]
fn test_u16_max_signatures() {
let signatures = vec![Signature::default(); u16::MAX as usize];
let bytes = bincode::serialize(&ShortVec(signatures)).unwrap();
let mut offset = 0;
assert!(SignatureMeta::try_new(&bytes, &mut offset).is_err());
}
}

0 comments on commit 6ac4fe3

Please sign in to comment.