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

Fix Compact Arrays Header Parsing #6

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
38 changes: 35 additions & 3 deletions src/solana/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,16 +365,48 @@ fn parse_compact_array_of_bytes<'a>(
LEN_ARRAY_HEADER_BYTES,
&format!("{section} Array Header"),
)?;
let bytes_num = tx_body_remainder[0] as usize;
let parse_len = (bytes_num + 1) * LEN_ARRAY_HEADER_BYTES;
let (length, tx_body_remainder) = read_compact_u16(&tx_body_remainder)?;
let parse_len = length * LEN_ARRAY_HEADER_BYTES;
validate_length(tx_body_remainder, parse_len, &format!("{section} Array"))?;
let bytes: Vec<u8> = tx_body_remainder[LEN_ARRAY_HEADER_BYTES..parse_len].to_vec();
let bytes: Vec<u8> = tx_body_remainder[0..parse_len].to_vec();
Ok((
bytes,
&tx_body_remainder[parse_len..tx_body_remainder.len()],
))
}

fn read_compact_u16(tx_body_remainder: &[u8]) -> Result<(usize, &[u8]), Box<dyn std::error::Error>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's add some brief docs for this fn

Copy link
Collaborator

Choose a reason for hiding this comment

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

is there a reference implementation for this? if so, we should link it — might add some peace of mind as it's fairly lowlevel

let mut value = 0u16;
let mut shift = 0u16;
let mut bytes_read = 0;

loop {
// Check if there are enough bytes
if bytes_read >= tx_body_remainder.len() {
return Err(format!(
"Unsigned transaction provided is incorrectly formatted, error while parsing compact array header, not enough bytes"
)
.into());
}

let byte = tx_body_remainder[bytes_read];
bytes_read += 1;

value |= ((byte & 0x7f) as u16) << shift;
if byte & 0x80 == 0 {
return Ok((value as usize, &tx_body_remainder[bytes_read..tx_body_remainder.len()]));
}

shift += 7;
if shift >= 16 {
return Err(format!(
"Unsigned transaction provided is incorrectly formatted, error while parsing compact array header, invalid u16"
)
.into());
}
}
}

// Each signature is a Vec<u8> of 64 bytes
pub type Signature = Vec<u8>;

Expand Down
16 changes: 16 additions & 0 deletions src/solana/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,19 @@ use crate::solana::parser::SolanaTransaction;
];
assert_eq!(exp_lookups, transaction_metadata.address_table_lookups);
}

#[test]
fn parses_transaction_with_multi_byte_compact_array_header() {
// The purpose of this test is to ensure that transactions with compact array headers that are multiple bytes long are parsed correctly
// multiple byte array headers are possible based on the compact-u16 format as described in solana documentation here: https://solana.com/docs/core/transactions#compact-array-format

// ensure that transaction gets parsed without errors
let unsigned_transaction = "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800100071056837517cb604056d3d10dca4553663be1e7a8f0cb7a78abd50862eb2073fbd827a00dfa20ba5511ba322e07293a47397c9e842de88aa4d359ff9a0073f88217740218a08252e747966f313cef860d86d095a76f033098f0cb383d4a5078cc8dedfee61094ac6637619b7c78339527ef0a4460a9e32a0f37fda8c68aea1b751dd39306efe4d9bbb93cfa6c484c1016bb7a52fe3feeca3157d7d0791a25f345798b18611a5b9ca7a6a37eac499749d95233f53f18ec5e692915e2582ade72d68962bedcf3cccf19cbf35daaa34926be22b1fc6bfc7a0938bbb6ee5593046168974592a5996b45dcf0f07ef85b77388f204a784bbf8b212806048c3f9276485de4c353609a6762251896323d9bcd3e70b7bf0ddb03ff381afd5601e994ab9b5f9c0306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a400000008c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90479d55bf231c06eee74c56ece681507fdb1b2dea3f48e5102b1cda256bc138fb43ffa27f5d7f64a74c09b1f295879de4b09ab36dfc9dd514b321aa7b38ce5e80d0720fe448de59d8811e24d6df917dc8d0d98b392ddf4dd2b622a747a60fded9b48fc124b1d8ff29225062e50ea775462ef424c3c21bda18e3bf47b835bbdd90909000502c027090009000903098b0200000000000a06000100150b0c01010b0200010c0200000000e1f505000000000c010101110a06000200160b0c01010d1d0c0001020d160d0e0d1710171112010215161317000c0c18170304050d23e517cb977ae3ad2a010000002664000100e1f505000000006d2d4a01000000002100000c0301000001090f0c001916061a020708140b0c0a8f02828362be28ce44327e16490100000000000000000000000000000000000000000000000000000000000000000000210514000000532f27101965dd16442e59d40670faf5ebb142e40000000000000000000000000000000000000000000000075858938cec63c6b3140000009528cf48a8deb982b5549d72abbb764ffdbce3010056837517cb604056d3d10dca4553663be1e7a8f0cb7a78abd50862eb2073fbd800140000009528cf48a8deb982b5549d72abbb764ffdbce301000001000000009a06e62b93010000420000000101000000d831640000000000000000000000000000000000b3c663ec8c935858070000000000000000000000000000000000000000000000000000000000000000026f545fe588dd627fb93f2295f47652ccd56feab015ec282c500bf33679e3b3d10423222928042b2a26256a88a76573c8d9d435fad46f194977a3aead561e0c01a6d9b5873c9f05e4dd8e010302020c".to_string();
let parsed_tx = SolanaTransaction::new(&unsigned_transaction, true).unwrap();
let transaction_metadata = parsed_tx.transaction_metadata().unwrap();

// sanity check signatures
let parsed_tx_sigs = transaction_metadata.signatures;
assert_eq!(1, parsed_tx_sigs.len());
assert_eq!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), parsed_tx_sigs[0]);
}