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"
|
name = "solana-entry"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
"dlopen",
|
"dlopen",
|
||||||
"dlopen_derive",
|
"dlopen_derive",
|
||||||
"log 0.4.14",
|
"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-perf = { path = "../perf", version = "=1.9.0" }
|
||||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.9.0" }
|
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.9.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "=1.9.0" }
|
solana-sdk = { path = "../sdk", version = "=1.9.0" }
|
||||||
|
bincode = "1.3.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
matches = "0.1.9"
|
matches = "0.1.9"
|
||||||
|
@ -31,5 +32,8 @@ solana-logger = { path = "../logger", version = "=1.9.0" }
|
||||||
crate-type = ["lib"]
|
crate-type = ["lib"]
|
||||||
name = "solana_entry"
|
name = "solana_entry"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "entry_sigverify"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
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_measure::measure::Measure;
|
||||||
use solana_merkle_tree::MerkleTree;
|
use solana_merkle_tree::MerkleTree;
|
||||||
use solana_metrics::*;
|
use solana_metrics::*;
|
||||||
use solana_perf::cuda_runtime::PinnedVec;
|
use solana_perf::{
|
||||||
use solana_perf::perf_libs;
|
cuda_runtime::PinnedVec,
|
||||||
use solana_perf::recycler::Recycler;
|
packet::{Packet, Packets, PacketsRecycler, PACKETS_PER_BATCH},
|
||||||
|
perf_libs,
|
||||||
|
recycler::Recycler,
|
||||||
|
sigverify,
|
||||||
|
};
|
||||||
use solana_rayon_threadlimit::get_thread_count;
|
use solana_rayon_threadlimit::get_thread_count;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
|
use solana_sdk::packet::Meta;
|
||||||
use solana_sdk::timing;
|
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::cell::RefCell;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
@ -244,10 +252,59 @@ pub struct EntryVerificationState {
|
||||||
device_verification_data: DeviceVerificationData,
|
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)]
|
#[derive(Default, Clone)]
|
||||||
pub struct VerifyRecyclers {
|
pub struct VerifyRecyclers {
|
||||||
hash_recycler: Recycler<PinnedVec<Hash>>,
|
hash_recycler: Recycler<PinnedVec<Hash>>,
|
||||||
tick_count_recycler: Recycler<PinnedVec<u64>>,
|
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)]
|
#[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 {
|
fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
|
||||||
let actual = if !ref_entry.transactions.is_empty() {
|
let actual = if !ref_entry.transactions.is_empty() {
|
||||||
let tx_hash = hash_transactions(&ref_entry.transactions);
|
let tx_hash = hash_transactions(&ref_entry.transactions);
|
||||||
|
@ -692,6 +913,11 @@ mod tests {
|
||||||
system_transaction,
|
system_transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use solana_perf::test_tx::{test_invalid_tx, test_tx};
|
||||||
|
use solana_sdk::transaction::{
|
||||||
|
Result, SanitizedTransaction, TransactionError, VersionedTransaction,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entry_verify() {
|
fn test_entry_verify() {
|
||||||
let zero = Hash::default();
|
let zero = Hash::default();
|
||||||
|
@ -702,6 +928,116 @@ mod tests {
|
||||||
assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad
|
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]
|
#[test]
|
||||||
fn test_transaction_reorder_attack() {
|
fn test_transaction_reorder_attack() {
|
||||||
let zero = Hash::default();
|
let zero = Hash::default();
|
||||||
|
|
|
@ -33,6 +33,7 @@ use solana_runtime::{
|
||||||
vote_account::VoteAccount,
|
vote_account::VoteAccount,
|
||||||
vote_sender_types::ReplayVoteSender,
|
vote_sender_types::ReplayVoteSender,
|
||||||
};
|
};
|
||||||
|
use solana_sdk::timing;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{Slot, MAX_PROCESSING_AGE},
|
clock::{Slot, MAX_PROCESSING_AGE},
|
||||||
feature_set,
|
feature_set,
|
||||||
|
@ -40,8 +41,10 @@ use solana_sdk::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{Keypair, Signature},
|
signature::{Keypair, Signature},
|
||||||
timing,
|
transaction::{
|
||||||
transaction::{Result, SanitizedTransaction, TransactionError, VersionedTransaction},
|
Result, SanitizedTransaction, TransactionError, TransactionVerificationMode,
|
||||||
|
VersionedTransaction,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use solana_transaction_status::token_balances::{
|
use solana_transaction_status::token_balances::{
|
||||||
collect_token_balances, TransactionTokenBalancesSet,
|
collect_token_balances, TransactionTokenBalancesSet,
|
||||||
|
@ -301,7 +304,7 @@ pub fn process_entries_for_tests(
|
||||||
let verify_transaction = {
|
let verify_transaction = {
|
||||||
let bank = bank.clone();
|
let bank = bank.clone();
|
||||||
move |versioned_tx: VersionedTransaction| -> Result<SanitizedTransaction> {
|
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 verify_transaction = {
|
||||||
let bank = bank.clone();
|
let bank = bank.clone();
|
||||||
move |versioned_tx: VersionedTransaction| -> Result<SanitizedTransaction> {
|
move |versioned_tx: VersionedTransaction,
|
||||||
bank.verify_transaction(versioned_tx, skip_verification)
|
verification_mode: TransactionVerificationMode|
|
||||||
|
-> Result<SanitizedTransaction> {
|
||||||
|
bank.verify_transaction(versioned_tx, verification_mode)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let check_start = Instant::now();
|
let check_start = Instant::now();
|
||||||
let mut entries = entry::verify_transactions(entries, Arc::new(verify_transaction))?;
|
let check_result = entry::start_verify_transactions(
|
||||||
let transaction_duration_us = timing::duration_as_us(&check_start.elapsed());
|
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");
|
match check_result {
|
||||||
let mut execute_timings = ExecuteTimings::default();
|
Ok(mut check_result) => {
|
||||||
let cost_capacity_meter = Arc::new(RwLock::new(BlockCostCapacityMeter::default()));
|
let entries = check_result.entries();
|
||||||
// Note: This will shuffle entries' transactions in-place.
|
assert!(entries.is_some());
|
||||||
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();
|
|
||||||
|
|
||||||
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 {
|
timing.execute_timings.accumulate(&execute_timings);
|
||||||
let verified = verifier.finish_verify();
|
|
||||||
timing.poh_verify_elapsed += verifier.poh_duration_us();
|
// If running signature verification on the GPU, wait for that
|
||||||
timing.transaction_verify_elapsed += transaction_duration_us;
|
// computation to finish, and get the result of it. If we did the
|
||||||
if !verified {
|
// 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());
|
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
|
// 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");
|
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) {
|
pub fn shuffle<R: Rng>(&mut self, rng: &mut R) {
|
||||||
self.x.shuffle(rng)
|
self.x.shuffle(rng)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ pub fn test_tx() -> Transaction {
|
||||||
system_transaction::transfer(&keypair1, &pubkey1, 42, zero)
|
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 {
|
pub fn test_multisig_tx() -> Transaction {
|
||||||
let keypair0 = Keypair::new();
|
let keypair0 = Keypair::new();
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
|
|
|
@ -120,7 +120,8 @@ use solana_sdk::{
|
||||||
sysvar::{self},
|
sysvar::{self},
|
||||||
timing::years_as_slots,
|
timing::years_as_slots,
|
||||||
transaction::{
|
transaction::{
|
||||||
Result, SanitizedTransaction, Transaction, TransactionError, VersionedTransaction,
|
Result, SanitizedTransaction, Transaction, TransactionError, TransactionVerificationMode,
|
||||||
|
VersionedTransaction,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use solana_stake_program::stake_state::{
|
use solana_stake_program::stake_state::{
|
||||||
|
@ -5502,7 +5503,7 @@ impl Bank {
|
||||||
pub fn verify_transaction(
|
pub fn verify_transaction(
|
||||||
&self,
|
&self,
|
||||||
tx: VersionedTransaction,
|
tx: VersionedTransaction,
|
||||||
skip_verification: bool,
|
verification_mode: TransactionVerificationMode,
|
||||||
) -> Result<SanitizedTransaction> {
|
) -> Result<SanitizedTransaction> {
|
||||||
let sanitized_tx = {
|
let sanitized_tx = {
|
||||||
let size =
|
let size =
|
||||||
|
@ -5510,7 +5511,8 @@ impl Bank {
|
||||||
if size > PACKET_DATA_SIZE as u64 {
|
if size > PACKET_DATA_SIZE as u64 {
|
||||||
return Err(TransactionError::SanitizeFailure);
|
return Err(TransactionError::SanitizeFailure);
|
||||||
}
|
}
|
||||||
let message_hash = if !skip_verification {
|
let message_hash = if verification_mode == TransactionVerificationMode::FullVerification
|
||||||
|
{
|
||||||
tx.verify_and_hash_message()?
|
tx.verify_and_hash_message()?
|
||||||
} else {
|
} else {
|
||||||
tx.message.hash()
|
tx.message.hash()
|
||||||
|
@ -5525,7 +5527,9 @@ impl Bank {
|
||||||
return Err(TransactionError::SanitizeFailure);
|
return Err(TransactionError::SanitizeFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skip_verification {
|
if verification_mode == TransactionVerificationMode::HashAndVerifyPrecompiles
|
||||||
|
|| verification_mode == TransactionVerificationMode::FullVerification
|
||||||
|
{
|
||||||
sanitized_tx.verify_precompiles(&self.feature_set)?;
|
sanitized_tx.verify_precompiles(&self.feature_set)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15212,14 +15216,17 @@ pub(crate) mod tests {
|
||||||
{
|
{
|
||||||
let tx = make_transaction(TestCase::RemoveSignature);
|
let tx = make_transaction(TestCase::RemoveSignature);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bank.verify_transaction(tx.into(), false).err(),
|
bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification)
|
||||||
|
.err(),
|
||||||
Some(TransactionError::SanitizeFailure),
|
Some(TransactionError::SanitizeFailure),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Too many signatures: Success without feature switch
|
// Too many signatures: Success without feature switch
|
||||||
{
|
{
|
||||||
let tx = make_transaction(TestCase::AddSignature);
|
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();
|
let tx = make_transaction();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bank.verify_transaction(tx.into(), false).err(),
|
bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification)
|
||||||
|
.err(),
|
||||||
Some(TransactionError::AccountLoadedTwice),
|
Some(TransactionError::AccountLoadedTwice),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15283,14 +15291,17 @@ pub(crate) mod tests {
|
||||||
{
|
{
|
||||||
let tx = make_transaction(5);
|
let tx = make_transaction(5);
|
||||||
assert!(bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64);
|
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.
|
// Big transaction.
|
||||||
{
|
{
|
||||||
let tx = make_transaction(25);
|
let tx = make_transaction(25);
|
||||||
assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64);
|
assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bank.verify_transaction(tx.into(), false).err(),
|
bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification)
|
||||||
|
.err(),
|
||||||
Some(TransactionError::SanitizeFailure),
|
Some(TransactionError::SanitizeFailure),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15300,7 +15311,8 @@ pub(crate) mod tests {
|
||||||
let tx = make_transaction(size);
|
let tx = make_transaction(size);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64,
|
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,
|
WouldExceedMaxAccountCostLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||||
|
pub enum TransactionVerificationMode {
|
||||||
|
HashOnly,
|
||||||
|
HashAndVerifyPrecompiles,
|
||||||
|
FullVerification,
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, TransactionError>;
|
pub type Result<T> = result::Result<T, TransactionError>;
|
||||||
|
|
||||||
impl From<SanitizeError> for 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
|
/// Verify the length of signatures matches the value in the message header
|
||||||
pub fn verify_signatures_len(&self) -> bool {
|
pub fn verify_signatures_len(&self) -> bool {
|
||||||
self.signatures.len() == self.message.header.num_required_signatures as usize
|
self.signatures.len() == self.message.header.num_required_signatures as usize
|
||||||
|
|
Loading…
Reference in New Issue