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:
parent
8d0357794e
commit
8cf36e5cb0
|
@ -4774,6 +4774,7 @@ dependencies = [
|
|||
name = "solana-entry"
|
||||
version = "1.9.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"dlopen",
|
||||
"dlopen_derive",
|
||||
"log 0.4.14",
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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));
|
||||
})
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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,13 +922,26 @@ 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());
|
||||
|
||||
match check_result {
|
||||
Ok(mut check_result) => {
|
||||
let entries = check_result.entries();
|
||||
assert!(entries.is_some());
|
||||
|
||||
let mut replay_elapsed = Measure::start("replay_elapsed");
|
||||
let mut execute_timings = ExecuteTimings::default();
|
||||
|
@ -933,7 +949,7 @@ pub fn confirm_slot(
|
|||
// Note: This will shuffle entries' transactions in-place.
|
||||
let process_result = process_entries_with_callback(
|
||||
bank,
|
||||
&mut entries,
|
||||
&mut entries.unwrap(),
|
||||
true, // shuffle transactions.
|
||||
entry_callback,
|
||||
transaction_status_sender,
|
||||
|
@ -947,10 +963,23 @@ pub fn confirm_slot(
|
|||
|
||||
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();
|
||||
timing.transaction_verify_elapsed += transaction_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());
|
||||
|
@ -967,6 +996,12 @@ pub fn confirm_slot(
|
|||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Ledger proof of history failed at slot: {}", bank.slot());
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling required for processing the entries in slot 0
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue