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 <sakridge@gmail.com>
Co-authored-by: Ryan Leung <ryan.leung@solana.com>
This commit is contained in:
ryleung-solana 2021-11-30 21:16:13 -05:00 committed by GitHub
parent 8d0357794e
commit 8cf36e5cb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 573 additions and 56 deletions

1
Cargo.lock generated
View File

@ -4774,6 +4774,7 @@ dependencies = [
name = "solana-entry"
version = "1.9.0"
dependencies = [
"bincode",
"dlopen",
"dlopen_derive",
"log 0.4.14",

View File

@ -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"]

View File

@ -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::<Vec<_>>();
let verify_transaction = {
move |versioned_tx: VersionedTransaction,
verification_mode: TransactionVerificationMode|
-> Result<SanitizedTransaction> {
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::<Vec<_>>();
let verify_transaction = {
move |versioned_tx: VersionedTransaction| -> Result<SanitizedTransaction> {
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));
})
}

View File

@ -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<JoinHandle<(bool, u64)>>,
}
pub enum DeviceSigVerificationData {
Cpu(),
Gpu(GpuSigVerificationData),
}
pub struct EntrySigVerificationState {
verification_status: EntryVerificationStatus,
entries: Option<Vec<EntryType>>,
device_verification_data: DeviceSigVerificationData,
gpu_verify_duration_us: u64,
}
impl<'a> EntrySigVerificationState {
pub fn entries(&mut self) -> Option<Vec<EntryType>> {
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<PinnedVec<Hash>>,
tick_count_recycler: Recycler<PinnedVec<u64>>,
packet_recycler: PacketsRecycler,
out_recycler: Recycler<PinnedVec<u8>>,
tx_offset_recycler: Recycler<sigverify::TxOffset>,
}
#[derive(PartialEq, Clone, Copy, Debug)]
@ -339,6 +396,170 @@ pub fn verify_transactions(
})
}
pub fn start_verify_transactions(
entries: Vec<Entry>,
skip_verification: bool,
verify_recyclers: VerifyRecyclers,
verify: Arc<
dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<SanitizedTransaction>
+ Send
+ Sync,
>,
) -> Result<EntrySigVerificationState> {
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<usize> {
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<SanitizedTransaction> {
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<SanitizedTransaction> {
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::<Vec<_>>();
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::<Result<Vec<_>>>()?;
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<Entry>,
skip_verification: bool,
verify_recyclers: VerifyRecyclers,
verify: Arc<
dyn Fn(
VersionedTransaction,
TransactionVerificationMode,
) -> Result<SanitizedTransaction>
+ 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<SanitizedTransaction> {
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<SanitizedTransaction> {
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::<Vec<_>>();
let entries_valid = (0..1025)
.map(|_| {
let transaction = test_tx();
next_entry_mut(&mut Hash::default(), 0, vec![transaction])
})
.collect::<Vec<_>>();
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();

View File

@ -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<SanitizedTransaction> {
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<SanitizedTransaction> {
bank.verify_transaction(versioned_tx, skip_verification)
move |versioned_tx: VersionedTransaction,
verification_mode: TransactionVerificationMode|
-> Result<SanitizedTransaction> {
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

View File

@ -250,6 +250,29 @@ impl<T: Clone + Default + Sized> PinnedVec<T> {
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<R: Rng>(&mut self, rng: &mut R) {
self.x.shuffle(rng)
}

View File

@ -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();

View File

@ -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<SanitizedTransaction> {
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(),
);
}
}

View File

@ -127,6 +127,13 @@ pub enum TransactionError {
WouldExceedMaxAccountCostLimit,
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum TransactionVerificationMode {
HashOnly,
HashAndVerifyPrecompiles,
FullVerification,
}
pub type Result<T> = result::Result<T, TransactionError>;
impl From<SanitizeError> 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