diff --git a/Cargo.lock b/Cargo.lock index 258f361af1b8cc..fdbda427d11f41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5049,6 +5049,7 @@ dependencies = [ "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", + "solana-vote-program", ] [[package]] diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 6c4c33d6d67b94..26c66db4cbd00d 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -23,6 +23,7 @@ solana-logger = { path = "../logger", version = "=1.7.13" } solana-metrics = { path = "../metrics", version = "=1.7.13" } solana-sdk = { path = "../sdk", version = "=1.7.13" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.7.13" } +solana-vote-program = { path = "../programs/vote", version = "=1.7.13" } [lib] name = "solana_perf" diff --git a/perf/benches/sigverify.rs b/perf/benches/sigverify.rs index 588392fa554947..2a013ae1918965 100644 --- a/perf/benches/sigverify.rs +++ b/perf/benches/sigverify.rs @@ -28,11 +28,12 @@ fn bench_get_offsets(bencher: &mut Bencher) { let tx = test_tx(); // generate packet vector - let batches = to_packets_chunked(&std::iter::repeat(tx).take(1024).collect::>(), 1024); + let mut batches = + to_packets_chunked(&std::iter::repeat(tx).take(1024).collect::>(), 1024); let recycler = Recycler::default(); // verify packets bencher.iter(|| { - let _ans = sigverify::generate_offsets(&batches, &recycler); + let _ans = sigverify::generate_offsets(&mut batches, &recycler); }) } diff --git a/perf/src/sigverify.rs b/perf/src/sigverify.rs index 5b6c591482a9cc..00bda1b6fc589b 100644 --- a/perf/src/sigverify.rs +++ b/perf/src/sigverify.rs @@ -11,6 +11,7 @@ use crate::recycler::Recycler; use rayon::ThreadPool; use solana_metrics::inc_new_counter_debug; use solana_rayon_threadlimit::get_thread_count; +use solana_sdk::hash::Hash; use solana_sdk::message::MESSAGE_HEADER_LENGTH; use solana_sdk::pubkey::Pubkey; use solana_sdk::short_vec::decode_shortu16_len; @@ -45,15 +46,23 @@ struct PacketOffsets { pub sig_start: u32, pub msg_start: u32, pub pubkey_start: u32, + pub pubkey_len: u32, } impl PacketOffsets { - pub fn new(sig_len: u32, sig_start: u32, msg_start: u32, pubkey_start: u32) -> Self { + pub fn new( + sig_len: u32, + sig_start: u32, + msg_start: u32, + pubkey_start: u32, + pubkey_len: u32, + ) -> Self { Self { sig_len, sig_start, msg_start, pubkey_start, + pubkey_len, } } } @@ -66,6 +75,8 @@ pub enum PacketError { InvalidSignatureLen, MismatchSignatureLen, PayerNotWritable, + InvalidProgramIdIndex, + InvalidProgramLen, } impl std::convert::From> for PacketError { @@ -234,20 +245,88 @@ fn do_get_packet_offsets( u32::try_from(sig_start)?, u32::try_from(msg_start)?, u32::try_from(pubkey_start)?, + u32::try_from(pubkey_len)?, )) } -fn get_packet_offsets(packet: &Packet, current_offset: usize) -> PacketOffsets { +fn get_packet_offsets(packet: &mut Packet, current_offset: usize) -> PacketOffsets { let unsanitized_packet_offsets = do_get_packet_offsets(packet, current_offset); if let Ok(offsets) = unsanitized_packet_offsets { + check_for_simple_vote_transaction(packet, &offsets, current_offset).ok(); offsets } else { // force sigverify to fail by returning zeros - PacketOffsets::new(0, 0, 0, 0) + PacketOffsets::new(0, 0, 0, 0, 0) } } -pub fn generate_offsets(batches: &[Packets], recycler: &Recycler) -> TxOffsets { +fn check_for_simple_vote_transaction( + packet: &mut Packet, + packet_offsets: &PacketOffsets, + current_offset: usize, +) -> Result<(), PacketError> { + if packet_offsets.sig_len != 1 { + return Err(PacketError::InvalidSignatureLen); + } + + let pubkey_start = (packet_offsets.pubkey_start as usize) + .checked_sub(current_offset) + .ok_or(PacketError::InvalidLen)?; + + let instructions_len_offset = (packet_offsets.pubkey_len as usize) + .checked_mul(size_of::()) + .and_then(|v| v.checked_add(pubkey_start)) + .and_then(|v| v.checked_add(size_of::())) + .ok_or(PacketError::InvalidLen)?; + + // Packet should have at least 1 more byte for instructions.len + let _ = instructions_len_offset + .checked_add(1usize) + .filter(|v| *v <= packet.meta.size) + .ok_or(PacketError::InvalidLen)?; + + let (instruction_len, instruction_len_size) = + decode_shortu16_len(&packet.data[instructions_len_offset..]) + .map_err(|_| PacketError::InvalidLen)?; + + // skip if has more than 1 instruction + if instruction_len != 1 { + return Err(PacketError::InvalidProgramLen); + } + + let instruction_start = instructions_len_offset + .checked_add(instruction_len_size) + .ok_or(PacketError::InvalidLen)?; + + // Packet should have at least 1 more byte for one instructions_program_id + let _ = instruction_start + .checked_add(1usize) + .filter(|v| *v <= packet.meta.size) + .ok_or(PacketError::InvalidLen)?; + + let instruction_program_id_index: usize = usize::from(packet.data[instruction_start]); + + if instruction_program_id_index >= packet_offsets.pubkey_len as usize { + return Err(PacketError::InvalidProgramIdIndex); + } + + let instruction_program_id_start = instruction_program_id_index + .checked_mul(size_of::()) + .and_then(|v| v.checked_add(pubkey_start)) + .ok_or(PacketError::InvalidLen)?; + let instruction_program_id_end = instruction_program_id_start + .checked_add(size_of::()) + .ok_or(PacketError::InvalidLen)?; + + if &packet.data[instruction_program_id_start..instruction_program_id_end] + == solana_sdk::vote::program::id().as_ref() + { + packet.meta.is_simple_vote_tx = true; + } + Ok(()) +} + +pub fn generate_offsets(batches: &mut [Packets], recycler: &Recycler) -> TxOffsets { debug!("allocating.."); let mut signature_offsets: PinnedVec<_> = recycler.allocate("sig_offsets"); signature_offsets.set_pinnable(); @@ -259,9 +338,9 @@ pub fn generate_offsets(batches: &[Packets], recycler: &Recycler) -> T msg_sizes.set_pinnable(); let mut current_offset: usize = 0; let mut v_sig_lens = Vec::new(); - batches.iter().for_each(|p| { + batches.iter_mut().for_each(|p| { let mut sig_lens = Vec::new(); - p.packets.iter().for_each(|packet| { + p.packets.iter_mut().for_each(|packet| { let packet_offsets = get_packet_offsets(packet, current_offset); sig_lens.push(packet_offsets.sig_len); @@ -471,11 +550,11 @@ mod tests { use crate::packet::{Packet, Packets}; use crate::sigverify; use crate::sigverify::PacketOffsets; - use crate::test_tx::{test_multisig_tx, test_tx}; + use crate::test_tx::{test_multisig_tx, test_tx, vote_tx}; use bincode::{deserialize, serialize}; - use solana_sdk::hash::Hash; + use solana_sdk::instruction::CompiledInstruction; use solana_sdk::message::{Message, MessageHeader}; - use solana_sdk::signature::Signature; + use solana_sdk::signature::{Keypair, Signature}; use solana_sdk::transaction::Transaction; const SIG_OFFSET: usize = 1; @@ -516,9 +595,9 @@ mod tests { let tx = test_tx(); let tx_bytes = serialize(&tx).unwrap(); let message_data = tx.message_data(); - let packet = sigverify::make_packet_from_transaction(tx.clone()); + let mut packet = sigverify::make_packet_from_transaction(tx.clone()); - let packet_offsets = sigverify::get_packet_offsets(&packet, 0); + let packet_offsets = sigverify::get_packet_offsets(&mut packet, 0); assert_eq!( memfind(&tx_bytes, tx.signatures[0].as_ref()), @@ -742,13 +821,14 @@ mod tests { // Just like get_packet_offsets, but not returning redundant information. fn get_packet_offsets_from_tx(tx: Transaction, current_offset: u32) -> PacketOffsets { - let packet = sigverify::make_packet_from_transaction(tx); - let packet_offsets = sigverify::get_packet_offsets(&packet, current_offset as usize); + let mut packet = sigverify::make_packet_from_transaction(tx); + let packet_offsets = sigverify::get_packet_offsets(&mut packet, current_offset as usize); PacketOffsets::new( packet_offsets.sig_len, packet_offsets.sig_start - current_offset, packet_offsets.msg_start - packet_offsets.sig_start, packet_offsets.pubkey_start - packet_offsets.msg_start, + packet_offsets.pubkey_len, ) } @@ -756,23 +836,23 @@ mod tests { fn test_get_packet_offsets() { assert_eq!( get_packet_offsets_from_tx(test_tx(), 0), - PacketOffsets::new(1, 1, 64, 4) + PacketOffsets::new(1, 1, 64, 4, 2) ); assert_eq!( get_packet_offsets_from_tx(test_tx(), 100), - PacketOffsets::new(1, 1, 64, 4) + PacketOffsets::new(1, 1, 64, 4, 2) ); // Ensure we're not indexing packet by the `current_offset` parameter. assert_eq!( get_packet_offsets_from_tx(test_tx(), 1_000_000), - PacketOffsets::new(1, 1, 64, 4) + PacketOffsets::new(1, 1, 64, 4, 2) ); // Ensure we're returning sig_len, not sig_size. assert_eq!( get_packet_offsets_from_tx(test_multisig_tx(), 0), - PacketOffsets::new(2, 1, 128, 4) + PacketOffsets::new(2, 1, 128, 4, 4) ); } @@ -1024,4 +1104,80 @@ mod tests { failed_g.load(Ordering::Relaxed) ); } + + #[test] + fn test_is_simple_vote_transaction() { + solana_logger::setup(); + + // tansfer tx is not + { + let mut tx = test_tx(); + tx.message.instructions[0].data = vec![1, 2, 3]; + let mut packet = sigverify::make_packet_from_transaction(tx); + let packet_offsets = do_get_packet_offsets(&packet, 0).unwrap(); + check_for_simple_vote_transaction(&mut packet, &packet_offsets, 0).ok(); + assert!(!packet.meta.is_simple_vote_tx); + } + + // single vote tx is + { + let mut tx = vote_tx(); + tx.message.instructions[0].data = vec![1, 2, 3]; + let mut packet = sigverify::make_packet_from_transaction(tx); + let packet_offsets = do_get_packet_offsets(&packet, 0).unwrap(); + check_for_simple_vote_transaction(&mut packet, &packet_offsets, 0).ok(); + assert!(packet.meta.is_simple_vote_tx); + } + + // multiple mixed tx is not + { + let key = Keypair::new(); + let key1 = Pubkey::new_unique(); + let key2 = Pubkey::new_unique(); + let tx = Transaction::new_with_compiled_instructions( + &[&key], + &[key1, key2], + Hash::default(), + vec![solana_vote_program::id(), Pubkey::new_unique()], + vec![ + CompiledInstruction::new(3, &(), vec![0, 1]), + CompiledInstruction::new(4, &(), vec![0, 2]), + ], + ); + let mut packet = sigverify::make_packet_from_transaction(tx); + let packet_offsets = do_get_packet_offsets(&packet, 0).unwrap(); + check_for_simple_vote_transaction(&mut packet, &packet_offsets, 0).ok(); + assert!(!packet.meta.is_simple_vote_tx); + } + } + + #[test] + fn test_is_simple_vote_transaction_with_offsets() { + solana_logger::setup(); + + let mut current_offset = 0usize; + let mut batch = Packets::default(); + batch + .packets + .push(sigverify::make_packet_from_transaction(test_tx())); + batch + .packets + .push(sigverify::make_packet_from_transaction(vote_tx())); + batch + .packets + .iter_mut() + .enumerate() + .for_each(|(index, mut packet)| { + let packet_offsets = do_get_packet_offsets(&packet, current_offset).unwrap(); + check_for_simple_vote_transaction(&mut packet, &packet_offsets, current_offset) + .ok(); + if index == 1 { + assert!(packet.meta.is_simple_vote_tx); + } else { + assert!(!packet.meta.is_simple_vote_tx); + } + + current_offset = current_offset.saturating_add(size_of::()); + }); + } } diff --git a/perf/src/test_tx.rs b/perf/src/test_tx.rs index a6845317183e1d..7939e0732f8376 100644 --- a/perf/src/test_tx.rs +++ b/perf/src/test_tx.rs @@ -7,6 +7,7 @@ use solana_sdk::{ system_program, system_transaction, transaction::Transaction, }; +use solana_vote_program::vote_transaction; pub fn test_tx() -> Transaction { let keypair1 = Keypair::new(); @@ -40,3 +41,16 @@ pub fn test_multisig_tx() -> Transaction { instructions, ) } + +pub fn vote_tx() -> Transaction { + let keypair = Keypair::new(); + vote_transaction::new_vote_transaction( + vec![2], + Hash::default(), + Hash::default(), + &keypair, + &keypair, + &keypair, + None, + ) +} diff --git a/sdk/src/packet.rs b/sdk/src/packet.rs index 436621779825c8..62b9e0f960400a 100644 --- a/sdk/src/packet.rs +++ b/sdk/src/packet.rs @@ -25,6 +25,7 @@ pub struct Meta { pub seed: [u8; 32], pub slot: Slot, pub is_tracer_tx: bool, + pub is_simple_vote_tx: bool, } #[derive(Clone)]