diff --git a/transaction-view/src/lib.rs b/transaction-view/src/lib.rs index a16187f62ccd82..e73eaa0cba1249 100644 --- a/transaction-view/src/lib.rs +++ b/transaction-view/src/lib.rs @@ -7,3 +7,5 @@ pub mod bytes; mod bytes; pub mod result; +#[allow(dead_code)] +mod signature_meta; diff --git a/transaction-view/src/signature_meta.rs b/transaction-view/src/signature_meta.rs new file mode 100644 index 00000000000000..d775513d1abbaf --- /dev/null +++ b/transaction-view/src/signature_meta.rs @@ -0,0 +1,97 @@ +use { + crate::{ + bytes::{offset_array_len, 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 { + // 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::() + core::mem::size_of::())) + 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; + offset_array_len::(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::::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::()); + } + + #[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::()); + } + + #[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()); + } +}