forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 331
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TransactionView: Message Header Meta
- Loading branch information
Showing
2 changed files
with
125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,6 @@ pub mod bytes; | |
#[allow(dead_code)] | ||
mod bytes; | ||
|
||
#[allow(dead_code)] | ||
mod message_header_meta; | ||
pub mod result; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use { | ||
crate::{ | ||
bytes::read_byte, | ||
result::{Result, TransactionParsingError}, | ||
}, | ||
solana_sdk::message::MESSAGE_VERSION_PREFIX, | ||
}; | ||
|
||
/// A byte that represents the version of the transaction. | ||
#[derive(Copy, Clone, Default)] | ||
#[repr(u8)] | ||
pub enum TransactionVersion { | ||
#[default] | ||
Legacy = u8::MAX, | ||
V0 = 0, | ||
} | ||
|
||
/// Meta data for accessing message header fields in a transaction view. | ||
pub(crate) struct MessageHeaderMeta { | ||
/// The offset to the first byte of the message in the transaction packet. | ||
pub(crate) offset: u16, | ||
/// The version of the transaction. | ||
pub(crate) version: TransactionVersion, | ||
/// The number of signatures required for this message to be considered | ||
/// valid. | ||
pub(crate) num_required_signatures: u8, | ||
/// The last `num_readonly_signed_accounts` of the signed keys are | ||
/// read-only. | ||
pub(crate) num_readonly_signed_accounts: u8, | ||
/// The last `num_readonly_unsigned_accounts` of the unsigned keys are | ||
/// read-only accounts. | ||
pub(crate) num_readonly_unsigned_accounts: u8, | ||
} | ||
|
||
impl MessageHeaderMeta { | ||
pub fn try_new(bytes: &[u8], offset: &mut usize) -> Result<Self> { | ||
// Get the message offset. | ||
// We know the offset does not exceed packet length, and our packet | ||
// length is less than u16::MAX, so we can safely cast to u16. | ||
let message_offset = *offset as u16; | ||
|
||
// Read the message prefix byte if present. This byte is present in V0 | ||
// transactions but not in legacy transactions. | ||
// The message header begins immediately after the message prefix byte | ||
// if present. | ||
let message_prefix = read_byte(bytes, offset)?; | ||
let (version, message_header_offset) = if message_prefix & MESSAGE_VERSION_PREFIX != 0 { | ||
let version = message_prefix & !MESSAGE_VERSION_PREFIX; | ||
match version { | ||
// add one byte for prefix byte. Do not need to check for | ||
// overflow here, because we have already checked. | ||
0 => (TransactionVersion::V0, message_offset + 1), | ||
_ => return Err(TransactionParsingError), | ||
} | ||
} else { | ||
(TransactionVersion::Legacy, message_offset) | ||
}; | ||
|
||
// Offset should get reset to header offset - the byte we read may have | ||
// actually been part of the header instead of the prefix (Legacy). | ||
*offset = usize::from(message_header_offset); | ||
|
||
let num_required_signatures = read_byte(bytes, offset)?; | ||
let num_readonly_signed_accounts = read_byte(bytes, offset)?; | ||
let num_readonly_unsigned_accounts = read_byte(bytes, offset)?; | ||
|
||
Ok(Self { | ||
offset: message_offset, | ||
version, | ||
num_required_signatures, | ||
num_readonly_signed_accounts, | ||
num_readonly_unsigned_accounts, | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_invalid_version() { | ||
let bytes = [0b1000_0001]; | ||
let mut offset = 0; | ||
assert!(MessageHeaderMeta::try_new(&bytes, &mut offset).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_legacy_transaction_missing_header_byte() { | ||
let bytes = [5, 0]; | ||
let mut offset = 0; | ||
assert!(MessageHeaderMeta::try_new(&bytes, &mut offset).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_legacy_transaction_valid() { | ||
let bytes = [5, 1, 2]; | ||
let mut offset = 0; | ||
let header = MessageHeaderMeta::try_new(&bytes, &mut offset).unwrap(); | ||
assert!(matches!(header.version, TransactionVersion::Legacy)); | ||
assert_eq!(header.num_required_signatures, 5); | ||
assert_eq!(header.num_readonly_signed_accounts, 1); | ||
assert_eq!(header.num_readonly_unsigned_accounts, 2); | ||
} | ||
|
||
#[test] | ||
fn test_v0_transaction_missing_header_byte() { | ||
let bytes = [MESSAGE_VERSION_PREFIX, 5, 1]; | ||
let mut offset = 0; | ||
assert!(MessageHeaderMeta::try_new(&bytes, &mut offset).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_v0_transaction_valid() { | ||
let bytes = [MESSAGE_VERSION_PREFIX, 5, 1, 2]; | ||
let mut offset = 0; | ||
let header = MessageHeaderMeta::try_new(&bytes, &mut offset).unwrap(); | ||
assert!(matches!(header.version, TransactionVersion::V0)); | ||
assert_eq!(header.num_required_signatures, 5); | ||
assert_eq!(header.num_readonly_signed_accounts, 1); | ||
assert_eq!(header.num_readonly_unsigned_accounts, 2); | ||
} | ||
} |