From 8cf36e5cb05192257648561e1e377d2b80c00473 Mon Sep 17 00:00:00 2001 From: ryleung-solana <91908731+ryleung-solana@users.noreply.github.com> Date: Tue, 30 Nov 2021 21:16:13 -0500 Subject: [PATCH] Add GPU sigverify for verify path (#20851) Allows the use of GPU acceleration in verifying the signatures in Entry's after deserialization in the replay stage Co-authored-by: Stephen Akridge Co-authored-by: Ryan Leung --- Cargo.lock | 1 + entry/Cargo.toml | 4 + entry/benches/entry_sigverify.rs | 89 ++++++++ entry/src/entry.rs | 344 ++++++++++++++++++++++++++++- ledger/src/blockstore_processor.rs | 119 ++++++---- perf/src/cuda_runtime.rs | 23 ++ perf/src/test_tx.rs | 6 + runtime/src/bank.rs | 32 ++- sdk/src/transaction/mod.rs | 11 + 9 files changed, 573 insertions(+), 56 deletions(-) create mode 100644 entry/benches/entry_sigverify.rs diff --git a/Cargo.lock b/Cargo.lock index 36a2f7e9c..f1aa6e8e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4774,6 +4774,7 @@ dependencies = [ name = "solana-entry" version = "1.9.0" dependencies = [ + "bincode", "dlopen", "dlopen_derive", "log 0.4.14", diff --git a/entry/Cargo.toml b/entry/Cargo.toml index e0b9573d6..d275d6e1c 100644 --- a/entry/Cargo.toml +++ b/entry/Cargo.toml @@ -22,6 +22,7 @@ solana-metrics = { path = "../metrics", version = "=1.9.0" } solana-perf = { path = "../perf", version = "=1.9.0" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.9.0" } solana-sdk = { path = "../sdk", version = "=1.9.0" } +bincode = "1.3.3" [dev-dependencies] matches = "0.1.9" @@ -31,5 +32,8 @@ solana-logger = { path = "../logger", version = "=1.9.0" } crate-type = ["lib"] name = "solana_entry" +[[bench]] +name = "entry_sigverify" + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/entry/benches/entry_sigverify.rs b/entry/benches/entry_sigverify.rs new file mode 100644 index 000000000..07bf92d61 --- /dev/null +++ b/entry/benches/entry_sigverify.rs @@ -0,0 +1,89 @@ +#![feature(test)] +extern crate test; +use test::Bencher; + +use std::sync::Arc; + +use solana_perf::test_tx::test_tx; +use solana_sdk::transaction::{ + Result, SanitizedTransaction, TransactionError, TransactionVerificationMode, + VersionedTransaction, +}; + +use solana_entry::entry::{self, VerifyRecyclers}; + +use solana_sdk::hash::Hash; + +#[bench] +fn bench_gpusigverify(bencher: &mut Bencher) { + let entries = (0..131072) + .map(|_| { + let transaction = test_tx(); + entry::next_entry_mut(&mut Hash::default(), 0, vec![transaction]) + }) + .collect::>(); + + let verify_transaction = { + move |versioned_tx: VersionedTransaction, + verification_mode: TransactionVerificationMode| + -> Result { + let sanitized_tx = { + let message_hash = + if verification_mode == TransactionVerificationMode::FullVerification { + versioned_tx.verify_and_hash_message()? + } else { + versioned_tx.message.hash() + }; + + SanitizedTransaction::try_create(versioned_tx, message_hash, None, |_| { + Err(TransactionError::UnsupportedVersion) + }) + }?; + + Ok(sanitized_tx) + } + }; + + let recycler = VerifyRecyclers::default(); + + bencher.iter(|| { + let res = entry::start_verify_transactions( + entries.clone(), + false, + recycler.clone(), + Arc::new(verify_transaction), + ); + + if let Ok(mut res) = res { + let _ans = res.finish_verify(); + } + }) +} + +#[bench] +fn bench_cpusigverify(bencher: &mut Bencher) { + let entries = (0..131072) + .map(|_| { + let transaction = test_tx(); + entry::next_entry_mut(&mut Hash::default(), 0, vec![transaction]) + }) + .collect::>(); + + let verify_transaction = { + move |versioned_tx: VersionedTransaction| -> Result { + let sanitized_tx = { + let message_hash = versioned_tx.verify_and_hash_message()?; + + SanitizedTransaction::try_create(versioned_tx, message_hash, None, |_| { + Err(TransactionError::UnsupportedVersion) + }) + }?; + + Ok(sanitized_tx) + } + }; + + bencher.iter(|| { + let _ans = entry::verify_transactions(entries.clone(), Arc::new(verify_transaction)); + }) +} diff --git a/entry/src/entry.rs b/entry/src/entry.rs index c5827842f..79adc5799 100644 --- a/entry/src/entry.rs +++ b/entry/src/entry.rs @@ -13,13 +13,21 @@ use serde::{Deserialize, Serialize}; use solana_measure::measure::Measure; use solana_merkle_tree::MerkleTree; use solana_metrics::*; -use solana_perf::cuda_runtime::PinnedVec; -use solana_perf::perf_libs; -use solana_perf::recycler::Recycler; +use solana_perf::{ + cuda_runtime::PinnedVec, + packet::{Packet, Packets, PacketsRecycler, PACKETS_PER_BATCH}, + perf_libs, + recycler::Recycler, + sigverify, +}; use solana_rayon_threadlimit::get_thread_count; use solana_sdk::hash::Hash; +use solana_sdk::packet::Meta; use solana_sdk::timing; -use solana_sdk::transaction::{Result, SanitizedTransaction, Transaction, VersionedTransaction}; +use solana_sdk::transaction::{ + Result, SanitizedTransaction, Transaction, TransactionError, TransactionVerificationMode, + VersionedTransaction, +}; use std::cell::RefCell; use std::ffi::OsStr; use std::sync::mpsc::{Receiver, Sender}; @@ -244,10 +252,59 @@ pub struct EntryVerificationState { device_verification_data: DeviceVerificationData, } +pub struct GpuSigVerificationData { + thread_h: Option>, +} + +pub enum DeviceSigVerificationData { + Cpu(), + Gpu(GpuSigVerificationData), +} + +pub struct EntrySigVerificationState { + verification_status: EntryVerificationStatus, + entries: Option>, + device_verification_data: DeviceSigVerificationData, + gpu_verify_duration_us: u64, +} + +impl<'a> EntrySigVerificationState { + pub fn entries(&mut self) -> Option> { + self.entries.take() + } + pub fn finish_verify(&mut self) -> bool { + match &mut self.device_verification_data { + DeviceSigVerificationData::Gpu(verification_state) => { + let (verified, gpu_time_us) = + verification_state.thread_h.take().unwrap().join().unwrap(); + self.gpu_verify_duration_us = gpu_time_us; + self.verification_status = if verified { + EntryVerificationStatus::Success + } else { + EntryVerificationStatus::Failure + }; + verified + } + DeviceSigVerificationData::Cpu() => { + self.verification_status == EntryVerificationStatus::Success + } + } + } + pub fn status(&self) -> EntryVerificationStatus { + self.verification_status + } + pub fn gpu_verify_duration(&self) -> u64 { + self.gpu_verify_duration_us + } +} + #[derive(Default, Clone)] pub struct VerifyRecyclers { hash_recycler: Recycler>, tick_count_recycler: Recycler>, + packet_recycler: PacketsRecycler, + out_recycler: Recycler>, + tx_offset_recycler: Recycler, } #[derive(PartialEq, Clone, Copy, Debug)] @@ -339,6 +396,170 @@ pub fn verify_transactions( }) } +pub fn start_verify_transactions( + entries: Vec, + skip_verification: bool, + verify_recyclers: VerifyRecyclers, + verify: Arc< + dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result + + Send + + Sync, + >, +) -> Result { + let api = perf_libs::api(); + + // Use the CPU if we have too few transactions for GPU signature verification to be worth it. + // We will also use the CPU if no acceleration API is used or if we're skipping + // the signature verification as we'd have nothing to do on the GPU in that case. + // TODO: make the CPU-to GPU crossover point dynamic, perhaps based on similar future + // heuristics to what might be used in sigverify::ed25519_verify when a dynamic crossover + // is introduced for that function (see TODO in sigverify::ed25519_verify) + let use_cpu = skip_verification + || api.is_none() + || entries + .iter() + .try_fold(0, |accum: usize, entry: &Entry| -> Option { + if accum.saturating_add(entry.transactions.len()) < 512 { + Some(accum.saturating_add(entry.transactions.len())) + } else { + None + } + }) + .is_some(); + + if use_cpu { + let verify_func = { + let verification_mode = if skip_verification { + TransactionVerificationMode::HashOnly + } else { + TransactionVerificationMode::FullVerification + }; + move |versioned_tx: VersionedTransaction| -> Result { + verify(versioned_tx, verification_mode) + } + }; + + let entries = verify_transactions(entries, Arc::new(verify_func)); + + match entries { + Ok(entries_val) => { + return Ok(EntrySigVerificationState { + verification_status: EntryVerificationStatus::Success, + entries: Some(entries_val), + device_verification_data: DeviceSigVerificationData::Cpu(), + gpu_verify_duration_us: 0, + }); + } + Err(err) => { + return Err(err); + } + } + } + + let verify_func = { + move |versioned_tx: VersionedTransaction| -> Result { + verify( + versioned_tx, + TransactionVerificationMode::HashAndVerifyPrecompiles, + ) + } + }; + let entries = verify_transactions(entries, Arc::new(verify_func)); + match entries { + Ok(entries) => { + let num_transactions: usize = entries + .iter() + .map(|entry: &EntryType| -> usize { + match entry { + EntryType::Transactions(transactions) => transactions.len(), + EntryType::Tick(_) => 0, + } + }) + .sum(); + + if num_transactions == 0 { + return Ok(EntrySigVerificationState { + verification_status: EntryVerificationStatus::Success, + entries: Some(entries), + device_verification_data: DeviceSigVerificationData::Cpu(), + gpu_verify_duration_us: 0, + }); + } + let entry_txs: Vec<&SanitizedTransaction> = entries + .iter() + .filter_map(|entry_type| match entry_type { + EntryType::Tick(_) => None, + EntryType::Transactions(transactions) => Some(transactions), + }) + .flatten() + .collect::>(); + let mut packets_vec = entry_txs + .par_iter() + .chunks(PACKETS_PER_BATCH) + .map(|slice| { + let vec_size = slice.len(); + let mut packets = Packets::new_with_recycler( + verify_recyclers.packet_recycler.clone(), + vec_size, + "entry-sig-verify", + ); + // We use set_len here instead of resize(num_transactions, Packet::default()), to save + // memory bandwidth and avoid writing a large amount of data that will be overwritten + // soon afterwards. As well, Packet::default() actually leaves the packet data + // uninitialized anyway, so the initilization would simply write junk into + // the vector anyway. + unsafe { + packets.packets.set_len(vec_size); + } + let entry_tx_iter = slice + .into_par_iter() + .map(|tx| tx.to_versioned_transaction()); + + let res = packets + .packets + .par_iter_mut() + .zip(entry_tx_iter) + .all(|pair| { + pair.0.meta = Meta::default(); + Packet::populate_packet(pair.0, None, &pair.1).is_ok() + }); + if res { + Ok(packets) + } else { + Err(TransactionError::SanitizeFailure) + } + }) + .collect::>>()?; + + let tx_offset_recycler = verify_recyclers.tx_offset_recycler; + let out_recycler = verify_recyclers.out_recycler; + let gpu_verify_thread = thread::spawn(move || { + let mut verify_time = Measure::start("sigverify"); + sigverify::ed25519_verify( + &mut packets_vec, + &tx_offset_recycler, + &out_recycler, + false, + ); + let verified = packets_vec + .iter() + .all(|packets| packets.packets.iter().all(|p| !p.meta.discard)); + verify_time.stop(); + (verified, verify_time.as_us()) + }); + Ok(EntrySigVerificationState { + verification_status: EntryVerificationStatus::Pending, + entries: Some(entries), + device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData { + thread_h: Some(gpu_verify_thread), + }), + gpu_verify_duration_us: 0, + }) + } + Err(err) => Err(err), + } +} + fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool { let actual = if !ref_entry.transactions.is_empty() { let tx_hash = hash_transactions(&ref_entry.transactions); @@ -692,6 +913,11 @@ mod tests { system_transaction, }; + use solana_perf::test_tx::{test_invalid_tx, test_tx}; + use solana_sdk::transaction::{ + Result, SanitizedTransaction, TransactionError, VersionedTransaction, + }; + #[test] fn test_entry_verify() { let zero = Hash::default(); @@ -702,6 +928,116 @@ mod tests { assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad } + fn test_verify_transactions( + entries: Vec, + skip_verification: bool, + verify_recyclers: VerifyRecyclers, + verify: Arc< + dyn Fn( + VersionedTransaction, + TransactionVerificationMode, + ) -> Result + + Send + + Sync, + >, + ) -> bool { + let verify_func = { + let verify = verify.clone(); + let verification_mode = if skip_verification { + TransactionVerificationMode::HashOnly + } else { + TransactionVerificationMode::FullVerification + }; + move |versioned_tx: VersionedTransaction| -> Result { + verify(versioned_tx, verification_mode) + } + }; + + let cpu_verify_result = verify_transactions(entries.clone(), Arc::new(verify_func)); + let mut gpu_verify_result: EntrySigVerificationState = { + let verify_result = + start_verify_transactions(entries, skip_verification, verify_recyclers, verify); + match verify_result { + Ok(res) => res, + _ => EntrySigVerificationState { + verification_status: EntryVerificationStatus::Failure, + entries: None, + device_verification_data: DeviceSigVerificationData::Cpu(), + gpu_verify_duration_us: 0, + }, + } + }; + + match cpu_verify_result { + Ok(_) => { + assert!(gpu_verify_result.verification_status != EntryVerificationStatus::Failure); + assert!(gpu_verify_result.finish_verify()); + true + } + _ => { + assert!( + gpu_verify_result.verification_status == EntryVerificationStatus::Failure + || !gpu_verify_result.finish_verify() + ); + false + } + } + } + + #[test] + fn test_entry_gpu_verify() { + let verify_transaction = { + move |versioned_tx: VersionedTransaction, + verification_mode: TransactionVerificationMode| + -> Result { + let sanitized_tx = { + let message_hash = + if verification_mode == TransactionVerificationMode::FullVerification { + versioned_tx.verify_and_hash_message()? + } else { + versioned_tx.message.hash() + }; + + SanitizedTransaction::try_create(versioned_tx, message_hash, None, |_| { + Err(TransactionError::UnsupportedVersion) + }) + }?; + + Ok(sanitized_tx) + } + }; + + let recycler = VerifyRecyclers::default(); + + // Make sure we test with a number of transactions that's not a multiple of PACKETS_PER_BATCH + let entries_invalid = (0..1025) + .map(|_| { + let transaction = test_invalid_tx(); + next_entry_mut(&mut Hash::default(), 0, vec![transaction]) + }) + .collect::>(); + + let entries_valid = (0..1025) + .map(|_| { + let transaction = test_tx(); + next_entry_mut(&mut Hash::default(), 0, vec![transaction]) + }) + .collect::>(); + + assert!(!test_verify_transactions( + entries_invalid, + false, + recycler.clone(), + Arc::new(verify_transaction) + )); + assert!(test_verify_transactions( + entries_valid, + false, + recycler, + Arc::new(verify_transaction) + )); + } + #[test] fn test_transaction_reorder_attack() { let zero = Hash::default(); diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 3393f0985..9a374d5df 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -33,6 +33,7 @@ use solana_runtime::{ vote_account::VoteAccount, vote_sender_types::ReplayVoteSender, }; +use solana_sdk::timing; use solana_sdk::{ clock::{Slot, MAX_PROCESSING_AGE}, feature_set, @@ -40,8 +41,10 @@ use solana_sdk::{ hash::Hash, pubkey::Pubkey, signature::{Keypair, Signature}, - timing, - transaction::{Result, SanitizedTransaction, TransactionError, VersionedTransaction}, + transaction::{ + Result, SanitizedTransaction, TransactionError, TransactionVerificationMode, + VersionedTransaction, + }, }; use solana_transaction_status::token_balances::{ collect_token_balances, TransactionTokenBalancesSet, @@ -301,7 +304,7 @@ pub fn process_entries_for_tests( let verify_transaction = { let bank = bank.clone(); move |versioned_tx: VersionedTransaction| -> Result { - bank.verify_transaction(versioned_tx, false) + bank.verify_transaction(versioned_tx, TransactionVerificationMode::FullVerification) } }; @@ -919,54 +922,86 @@ pub fn confirm_slot( let verify_transaction = { let bank = bank.clone(); - move |versioned_tx: VersionedTransaction| -> Result { - bank.verify_transaction(versioned_tx, skip_verification) + move |versioned_tx: VersionedTransaction, + verification_mode: TransactionVerificationMode| + -> Result { + bank.verify_transaction(versioned_tx, verification_mode) } }; + let check_start = Instant::now(); - let mut entries = entry::verify_transactions(entries, Arc::new(verify_transaction))?; - let transaction_duration_us = timing::duration_as_us(&check_start.elapsed()); + let check_result = entry::start_verify_transactions( + entries, + skip_verification, + recyclers.clone(), + Arc::new(verify_transaction), + ); + let transaction_cpu_duration_us = timing::duration_as_us(&check_start.elapsed()); - let mut replay_elapsed = Measure::start("replay_elapsed"); - let mut execute_timings = ExecuteTimings::default(); - let cost_capacity_meter = Arc::new(RwLock::new(BlockCostCapacityMeter::default())); - // Note: This will shuffle entries' transactions in-place. - let process_result = process_entries_with_callback( - bank, - &mut entries, - true, // shuffle transactions. - entry_callback, - transaction_status_sender, - replay_vote_sender, - &mut execute_timings, - cost_capacity_meter, - ) - .map_err(BlockstoreProcessorError::from); - replay_elapsed.stop(); - timing.replay_elapsed += replay_elapsed.as_us(); + match check_result { + Ok(mut check_result) => { + let entries = check_result.entries(); + assert!(entries.is_some()); - timing.execute_timings.accumulate(&execute_timings); + let mut replay_elapsed = Measure::start("replay_elapsed"); + let mut execute_timings = ExecuteTimings::default(); + let cost_capacity_meter = Arc::new(RwLock::new(BlockCostCapacityMeter::default())); + // Note: This will shuffle entries' transactions in-place. + let process_result = process_entries_with_callback( + bank, + &mut entries.unwrap(), + true, // shuffle transactions. + entry_callback, + transaction_status_sender, + replay_vote_sender, + &mut execute_timings, + cost_capacity_meter, + ) + .map_err(BlockstoreProcessorError::from); + replay_elapsed.stop(); + timing.replay_elapsed += replay_elapsed.as_us(); - if let Some(mut verifier) = verifier { - let verified = verifier.finish_verify(); - timing.poh_verify_elapsed += verifier.poh_duration_us(); - timing.transaction_verify_elapsed += transaction_duration_us; - if !verified { + timing.execute_timings.accumulate(&execute_timings); + + // If running signature verification on the GPU, wait for that + // computation to finish, and get the result of it. If we did the + // signature verification on the CPU, this just returns the + // already-computed result produced in start_verify_transactions. + // Either way, check the result of the signature verification. + if !check_result.finish_verify() { + warn!("Ledger proof of history failed at slot: {}", bank.slot()); + return Err(TransactionError::SignatureFailure.into()); + } + + if let Some(mut verifier) = verifier { + let verified = verifier.finish_verify(); + timing.poh_verify_elapsed += verifier.poh_duration_us(); + // The GPU Entry verification (if any) is kicked off right when the CPU-side + // Entry verification finishes, so these times should be disjoint + timing.transaction_verify_elapsed += + transaction_cpu_duration_us + check_result.gpu_verify_duration(); + if !verified { + warn!("Ledger proof of history failed at slot: {}", bank.slot()); + return Err(BlockError::InvalidEntryHash.into()); + } + } + + process_result?; + + progress.num_shreds += num_shreds; + progress.num_entries += num_entries; + progress.num_txs += num_txs; + if let Some(last_entry_hash) = last_entry_hash { + progress.last_entry = last_entry_hash; + } + + Ok(()) + } + Err(err) => { warn!("Ledger proof of history failed at slot: {}", bank.slot()); - return Err(BlockError::InvalidEntryHash.into()); + Err(err.into()) } } - - process_result?; - - progress.num_shreds += num_shreds; - progress.num_entries += num_entries; - progress.num_txs += num_txs; - if let Some(last_entry_hash) = last_entry_hash { - progress.last_entry = last_entry_hash; - } - - Ok(()) } // Special handling required for processing the entries in slot 0 diff --git a/perf/src/cuda_runtime.rs b/perf/src/cuda_runtime.rs index 4d63517d5..8f26529f0 100644 --- a/perf/src/cuda_runtime.rs +++ b/perf/src/cuda_runtime.rs @@ -250,6 +250,29 @@ impl PinnedVec { self.check_ptr(old_ptr, old_capacity, "resize"); } + /// Forces the length of the vector to `new_len`. + /// + /// This is a low-level operation that maintains none of the normal + /// invariants of the type. Normally changing the length of a vector + /// is done using one of the safe operations instead, such as + /// [`truncate`], [`resize`], [`extend`], or [`clear`]. + /// + /// [`truncate`]: Vec::truncate + /// [`resize`]: Vec::resize + /// [`extend`]: Extend::extend + /// [`clear`]: Vec::clear + /// + /// # Safety + /// + /// - `new_len` must be less than or equal to [`capacity()`]. + /// - The elements at `old_len..new_len` must be initialized. + /// + /// [`capacity()`]: Vec::capacity + /// + pub unsafe fn set_len(&mut self, size: usize) { + self.x.set_len(size); + } + pub fn shuffle(&mut self, rng: &mut R) { self.x.shuffle(rng) } diff --git a/perf/src/test_tx.rs b/perf/src/test_tx.rs index 20da0e7c2..14ce3ea0f 100644 --- a/perf/src/test_tx.rs +++ b/perf/src/test_tx.rs @@ -18,6 +18,12 @@ pub fn test_tx() -> Transaction { system_transaction::transfer(&keypair1, &pubkey1, 42, zero) } +pub fn test_invalid_tx() -> Transaction { + let mut tx = test_tx(); + tx.signatures = vec![Transaction::get_invalid_signature()]; + tx +} + pub fn test_multisig_tx() -> Transaction { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 31efb6876..9d059922d 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -120,7 +120,8 @@ use solana_sdk::{ sysvar::{self}, timing::years_as_slots, transaction::{ - Result, SanitizedTransaction, Transaction, TransactionError, VersionedTransaction, + Result, SanitizedTransaction, Transaction, TransactionError, TransactionVerificationMode, + VersionedTransaction, }, }; use solana_stake_program::stake_state::{ @@ -5502,7 +5503,7 @@ impl Bank { pub fn verify_transaction( &self, tx: VersionedTransaction, - skip_verification: bool, + verification_mode: TransactionVerificationMode, ) -> Result { let sanitized_tx = { let size = @@ -5510,7 +5511,8 @@ impl Bank { if size > PACKET_DATA_SIZE as u64 { return Err(TransactionError::SanitizeFailure); } - let message_hash = if !skip_verification { + let message_hash = if verification_mode == TransactionVerificationMode::FullVerification + { tx.verify_and_hash_message()? } else { tx.message.hash() @@ -5525,7 +5527,9 @@ impl Bank { return Err(TransactionError::SanitizeFailure); } - if !skip_verification { + if verification_mode == TransactionVerificationMode::HashAndVerifyPrecompiles + || verification_mode == TransactionVerificationMode::FullVerification + { sanitized_tx.verify_precompiles(&self.feature_set)?; } @@ -15212,14 +15216,17 @@ pub(crate) mod tests { { let tx = make_transaction(TestCase::RemoveSignature); assert_eq!( - bank.verify_transaction(tx.into(), false).err(), + bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .err(), Some(TransactionError::SanitizeFailure), ); } // Too many signatures: Success without feature switch { let tx = make_transaction(TestCase::AddSignature); - assert!(bank.verify_transaction(tx.into(), false).is_ok()); + assert!(bank + .verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .is_ok()); } } @@ -15254,7 +15261,8 @@ pub(crate) mod tests { { let tx = make_transaction(); assert_eq!( - bank.verify_transaction(tx.into(), false).err(), + bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .err(), Some(TransactionError::AccountLoadedTwice), ); } @@ -15283,14 +15291,17 @@ pub(crate) mod tests { { let tx = make_transaction(5); assert!(bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64); - assert!(bank.verify_transaction(tx.into(), false).is_ok(),); + assert!(bank + .verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .is_ok(),); } // Big transaction. { let tx = make_transaction(25); assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64); assert_eq!( - bank.verify_transaction(tx.into(), false).err(), + bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .err(), Some(TransactionError::SanitizeFailure), ); } @@ -15300,7 +15311,8 @@ pub(crate) mod tests { let tx = make_transaction(size); assert_eq!( bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64, - bank.verify_transaction(tx.into(), false).is_ok(), + bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification) + .is_ok(), ); } } diff --git a/sdk/src/transaction/mod.rs b/sdk/src/transaction/mod.rs index 5d910569e..6799a1af7 100644 --- a/sdk/src/transaction/mod.rs +++ b/sdk/src/transaction/mod.rs @@ -127,6 +127,13 @@ pub enum TransactionError { WouldExceedMaxAccountCostLimit, } +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum TransactionVerificationMode { + HashOnly, + HashAndVerifyPrecompiles, + FullVerification, +} + pub type Result = result::Result; impl From for TransactionError { @@ -404,6 +411,10 @@ impl Transaction { } } + pub fn get_invalid_signature() -> Signature { + Signature::default() + } + /// Verify the length of signatures matches the value in the message header pub fn verify_signatures_len(&self) -> bool { self.signatures.len() == self.message.header.num_required_signatures as usize