diff --git a/Cargo.lock b/Cargo.lock index bf152dc7a3082d..e206bfb9ef5620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4996,6 +4996,7 @@ dependencies = [ "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", + "solana-vote-program", ] [[package]] diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index a8a898620ddd88..4f8fcbd09592b5 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -1616,7 +1616,7 @@ mod tests { ); trace!("sending bank"); drop(verified_sender); - drop(verified_gossip_sender); + drop(verified_gossip_vote_sender); drop(tpu_vote_sender); exit.store(true, Ordering::Relaxed); poh_service.join().unwrap(); diff --git a/core/src/cluster_info_vote_listener.rs b/core/src/cluster_info_vote_listener.rs index 20a4b15aed3280..8c9508dfc9ee7f 100644 --- a/core/src/cluster_info_vote_listener.rs +++ b/core/src/cluster_info_vote_listener.rs @@ -347,7 +347,10 @@ impl ClusterInfoVoteListener { labels: Vec, ) -> (Vec, Vec<(CrdsValueLabel, Slot, Packets)>) { let mut msgs = packet::to_packets_chunked(&votes, 1); - sigverify::ed25519_verify_cpu(&mut msgs); + + // Votes should already be filtered by this point. + let reject_non_vote = false; + sigverify::ed25519_verify_cpu(&mut msgs, reject_non_vote); let (vote_txs, packets) = izip!(labels.into_iter(), votes.into_iter(), msgs,) .filter_map(|(label, vote, packet)| { diff --git a/core/src/sigverify.rs b/core/src/sigverify.rs index 2103c1b3fa4671..1606e1023e5f88 100644 --- a/core/src/sigverify.rs +++ b/core/src/sigverify.rs @@ -17,6 +17,16 @@ pub use solana_perf::sigverify::{ pub struct TransactionSigVerifier { recycler: Recycler, recycler_out: Recycler>, + reject_non_vote: bool, +} + +impl TransactionSigVerifier { + pub fn new_reject_non_vote() -> Self { + TransactionSigVerifier { + reject_non_vote: true, + ..TransactionSigVerifier::default() + } + } } impl Default for TransactionSigVerifier { @@ -25,13 +35,19 @@ impl Default for TransactionSigVerifier { Self { recycler: Recycler::warmed(50, 4096, None, ""), recycler_out: Recycler::warmed(50, 4096, None, ""), + reject_non_vote: false, } } } impl SigVerifier for TransactionSigVerifier { fn verify_batch(&self, mut batch: Vec) -> Vec { - sigverify::ed25519_verify(&mut batch, &self.recycler, &self.recycler_out); + sigverify::ed25519_verify( + &mut batch, + &self.recycler, + &self.recycler_out, + self.reject_non_vote, + ); batch } } diff --git a/core/src/tpu.rs b/core/src/tpu.rs index 773f71f8f0894b..3ae2c8644c4ca7 100644 --- a/core/src/tpu.rs +++ b/core/src/tpu.rs @@ -95,7 +95,7 @@ impl Tpu { let (vote_verified_sender, vote_verified_receiver) = unbounded(); let vote_sigverify_stage = { - let verifier = TransactionSigVerifier::default(); + let verifier = TransactionSigVerifier::new_reject_non_vote(); SigVerifyStage::new(vote_packet_receiver, vote_verified_sender, verifier) }; diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 5823738bd78d2b..ab16b57eec3fa4 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -21,6 +21,7 @@ log = "0.4.11" solana-sdk = { path = "../sdk", version = "=1.6.27" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.6.27" } solana-budget-program = { path = "../programs/budget", version = "=1.6.27" } +solana-vote-program = { path = "../programs/vote", version = "=1.6.27" } solana-logger = { path = "../logger", version = "=1.6.27" } solana-measure = { path = "../measure", version = "=1.6.27" } solana-metrics = { path = "../metrics", version = "=1.6.27" } diff --git a/perf/benches/sigverify.rs b/perf/benches/sigverify.rs index 75aea3eaa4aba9..a9854eff0ad4b8 100644 --- a/perf/benches/sigverify.rs +++ b/perf/benches/sigverify.rs @@ -19,7 +19,7 @@ fn bench_sigverify(bencher: &mut Bencher) { let recycler_out = Recycler::new_without_limit(""); // verify packets bencher.iter(|| { - let _ans = sigverify::ed25519_verify(&mut batches, &recycler, &recycler_out); + let _ans = sigverify::ed25519_verify(&mut batches, &recycler, &recycler_out, false); }) } diff --git a/perf/src/sigverify.rs b/perf/src/sigverify.rs index c49ff0a93a5edd..c133a788406680 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, } } } @@ -92,7 +101,7 @@ pub fn init() { } } -fn verify_packet(packet: &mut Packet) { +fn verify_packet(packet: &mut Packet, reject_non_vote: bool) { let packet_offsets = get_packet_offsets(packet, 0); let mut sig_start = packet_offsets.sig_start as usize; let mut pubkey_start = packet_offsets.pubkey_start as usize; @@ -113,6 +122,66 @@ fn verify_packet(packet: &mut Packet) { return; } + if reject_non_vote { + if packet_offsets.sig_len != 1 { + packet.meta.discard = true; + return; + } + + let mut reject_pubkey_start = pubkey_start; + let mut vote_index = None; + for i in 0..packet_offsets.pubkey_len { + let pubkey_end = reject_pubkey_start.saturating_add(size_of::()); + if &packet.data[pubkey_start..pubkey_end] == solana_vote_program::id().as_ref() { + vote_index = Some(i); + break; + } + reject_pubkey_start = pubkey_end; + } + + if vote_index.is_none() { + packet.meta.discard = true; + return; + } + + let pubkeys_end = pubkey_start + .saturating_add(size_of::().saturating_mul(packet_offsets.pubkey_len as usize)); + let hash_end = pubkeys_end.saturating_add(size_of::()); + let num_instructions_offset = hash_end; + + if hash_end.saturating_add(1) >= packet.meta.size { + packet.meta.discard = true; + return; + } + + let (instructions_len, instructions_size) = + match decode_shortu16_len(&packet.data[num_instructions_offset..]) { + Ok((len, size)) => (len, size), + Err(_) => { + packet.meta.discard = true; + return; + } + }; + + let instruction0_start = hash_end.saturating_add(instructions_size); + if instruction0_start > packet.meta.size { + packet.meta.discard = true; + return; + } + + let vote_index = vote_index.unwrap(); + // First instruction should be the vote key + if packet.data[instruction0_start] as u32 != vote_index { + packet.meta.discard = true; + return; + } + + if instructions_len != 1 { + packet.meta.discard = true; + return; + } + } + let msg_end = packet.meta.size; for _ in 0..packet_offsets.sig_len { let pubkey_end = pubkey_start.saturating_add(size_of::()); @@ -234,6 +303,7 @@ 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)?, )) } @@ -243,7 +313,7 @@ fn get_packet_offsets(packet: &Packet, current_offset: usize) -> PacketOffsets { offsets } else { // force sigverify to fail by returning zeros - PacketOffsets::new(0, 0, 0, 0) + PacketOffsets::new(0, 0, 0, 0, 0) } } @@ -296,14 +366,16 @@ pub fn generate_offsets(batches: &[Packets], recycler: &Recycler) -> T ) } -pub fn ed25519_verify_cpu(batches: &mut [Packets]) { +pub fn ed25519_verify_cpu(batches: &mut [Packets], reject_non_vote: bool) { use rayon::prelude::*; let count = batch_size(batches); debug!("CPU ECDSA for {}", batch_size(batches)); PAR_THREAD_POOL.install(|| { - batches - .into_par_iter() - .for_each(|p| p.packets.par_iter_mut().for_each(|p| verify_packet(p))) + batches.into_par_iter().for_each(|p| { + p.packets + .par_iter_mut() + .for_each(|p| verify_packet(p, reject_non_vote)) + }) }); inc_new_counter_debug!("ed25519_verify_cpu", count); } @@ -383,10 +455,11 @@ pub fn ed25519_verify( batches: &mut [Packets], recycler: &Recycler, recycler_out: &Recycler>, + reject_non_vote: bool, ) { let api = perf_libs::api(); if api.is_none() { - return ed25519_verify_cpu(batches); + return ed25519_verify_cpu(batches, reject_non_vote); } let api = api.unwrap(); @@ -399,7 +472,7 @@ pub fn ed25519_verify( // may be busy doing other things while being a real validator // TODO: dynamically adjust this crossover if count < 64 { - return ed25519_verify_cpu(batches); + return ed25519_verify_cpu(batches, reject_non_vote); } let (signature_offsets, pubkey_offsets, msg_start_offsets, msg_sizes, sig_lens) = @@ -613,7 +686,7 @@ mod tests { let res = sigverify::do_get_packet_offsets(&packet, 0); assert_eq!(res, Err(PacketError::InvalidPubkeyLen)); - verify_packet(&mut packet); + verify_packet(&mut packet, false); assert!(packet.meta.discard); packet.meta.discard = false; @@ -649,7 +722,7 @@ mod tests { let res = sigverify::do_get_packet_offsets(&packet, 0); assert_eq!(res, Err(PacketError::InvalidPubkeyLen)); - verify_packet(&mut packet); + verify_packet(&mut packet, false); assert!(packet.meta.discard); packet.meta.discard = false; @@ -749,30 +822,32 @@ mod tests { packet_offsets.sig_start - current_offset, packet_offsets.msg_start - packet_offsets.sig_start, packet_offsets.pubkey_start - packet_offsets.msg_start, + 0, // TODO: Fix this ) } #[test] fn test_get_packet_offsets() { + // TODO: not 0 for pubkey_len assert_eq!( get_packet_offsets_from_tx(test_tx(), 0), - PacketOffsets::new(1, 1, 64, 4) + PacketOffsets::new(1, 1, 64, 4, 0) ); assert_eq!( get_packet_offsets_from_tx(test_tx(), 100), - PacketOffsets::new(1, 1, 64, 4) + PacketOffsets::new(1, 1, 64, 4, 0) ); // 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, 0) ); // 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, 0) ); } @@ -823,7 +898,7 @@ mod tests { fn ed25519_verify(batches: &mut [Packets]) { let recycler = Recycler::new_without_limit(""); let recycler_out = Recycler::new_without_limit(""); - sigverify::ed25519_verify(batches, &recycler, &recycler_out); + sigverify::ed25519_verify(batches, &recycler, &recycler_out, false); } #[test] @@ -921,8 +996,8 @@ mod tests { // verify from GPU verification pipeline (when GPU verification is enabled) are // equivalent to the CPU verification pipeline. let mut batches_cpu = batches.clone(); - sigverify::ed25519_verify(&mut batches, &recycler, &recycler_out); - ed25519_verify_cpu(&mut batches_cpu); + sigverify::ed25519_verify(&mut batches, &recycler, &recycler_out, false); + ed25519_verify_cpu(&mut batches_cpu, false); // check result batches