| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908 |
- //! The `entry` module is a fundamental building block of Proof of History. It contains a
- //! unique ID that is the hash of the Entry before it, plus the hash of the
- //! transactions within it. Entries cannot be reordered, and its field `num_hashes`
- //! represents an approximate amount of time since the last Entry was created.
- use crate::packet::{Blob, SharedBlob, BLOB_DATA_SIZE};
- use crate::poh::Poh;
- use crate::result::Result;
- use bincode::{deserialize, serialized_size};
- use chrono::prelude::Utc;
- use rayon::prelude::*;
- use rayon::ThreadPool;
- use solana_budget_api::budget_instruction;
- use solana_merkle_tree::MerkleTree;
- use solana_metrics::inc_new_counter_warn;
- use solana_sdk::hash::Hash;
- use solana_sdk::signature::{Keypair, KeypairUtil};
- use solana_sdk::timing;
- use solana_sdk::transaction::Transaction;
- use std::borrow::Borrow;
- use std::cell::RefCell;
- use std::sync::mpsc::{Receiver, Sender};
- use std::sync::{Arc, RwLock};
- #[cfg(feature = "cuda")]
- use crate::sigverify::poh_verify_many;
- use solana_rayon_threadlimit::get_thread_count;
- #[cfg(feature = "cuda")]
- use std::sync::Mutex;
- #[cfg(feature = "cuda")]
- use std::thread;
- use std::time::Instant;
- pub const NUM_THREADS: u32 = 10;
- thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new()
- .num_threads(get_thread_count())
- .build()
- .unwrap()));
- pub type EntrySender = Sender<Vec<Entry>>;
- pub type EntryReceiver = Receiver<Vec<Entry>>;
- /// Each Entry contains three pieces of data. The `num_hashes` field is the number
- /// of hashes performed since the previous entry. The `hash` field is the result
- /// of hashing `hash` from the previous entry `num_hashes` times. The `transactions`
- /// field points to Transactions that took place shortly before `hash` was generated.
- ///
- /// If you divide `num_hashes` by the amount of time it takes to generate a new hash, you
- /// get a duration estimate since the last Entry. Since processing power increases
- /// over time, one should expect the duration `num_hashes` represents to decrease proportionally.
- /// An upper bound on Duration can be estimated by assuming each hash was generated by the
- /// world's fastest processor at the time the entry was recorded. Or said another way, it
- /// is physically not possible for a shorter duration to have occurred if one assumes the
- /// hash was computed by the world's fastest processor at that time. The hash chain is both
- /// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of
- /// Work consensus!)
- #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)]
- pub struct Entry {
- /// The number of hashes since the previous Entry ID.
- pub num_hashes: u64,
- /// The SHA-256 hash `num_hashes` after the previous Entry ID.
- pub hash: Hash,
- /// An unordered list of transactions that were observed before the Entry ID was
- /// generated. They may have been observed before a previous Entry ID but were
- /// pushed back into this list to ensure deterministic interpretation of the ledger.
- pub transactions: Vec<Transaction>,
- }
- impl Entry {
- /// Creates the next Entry `num_hashes` after `start_hash`.
- pub fn new(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Self {
- assert!(Self::serialized_to_blob_size(&transactions) <= BLOB_DATA_SIZE as u64);
- if num_hashes == 0 && transactions.is_empty() {
- Entry {
- num_hashes: 0,
- hash: *prev_hash,
- transactions,
- }
- } else if num_hashes == 0 {
- // If you passed in transactions, but passed in num_hashes == 0, then
- // next_hash will generate the next hash and set num_hashes == 1
- let hash = next_hash(prev_hash, 1, &transactions);
- Entry {
- num_hashes: 1,
- hash,
- transactions,
- }
- } else {
- // Otherwise, the next Entry `num_hashes` after `start_hash`.
- // If you wanted a tick for instance, then pass in num_hashes = 1
- // and transactions = empty
- let hash = next_hash(prev_hash, num_hashes, &transactions);
- Entry {
- num_hashes,
- hash,
- transactions,
- }
- }
- }
- pub fn to_shared_blob(&self) -> SharedBlob {
- let blob = self.to_blob();
- Arc::new(RwLock::new(blob))
- }
- pub fn to_blob(&self) -> Blob {
- Blob::from_serializable(&vec![&self])
- }
- /// return serialized_size of a vector with a single Entry for given TXs
- /// since Blobs carry Vec<Entry>...
- /// calculate the total without actually constructing the full Entry (which
- /// would require a clone() of the transactions)
- pub fn serialized_to_blob_size(transactions: &[Transaction]) -> u64 {
- let txs_size: u64 = transactions
- .iter()
- .map(|tx| serialized_size(tx).unwrap())
- .sum();
- serialized_size(&vec![Entry {
- num_hashes: 0,
- hash: Hash::default(),
- transactions: vec![],
- }])
- .unwrap()
- + txs_size
- }
- pub fn new_mut(
- start_hash: &mut Hash,
- num_hashes: &mut u64,
- transactions: Vec<Transaction>,
- ) -> Self {
- assert!(Self::serialized_to_blob_size(&transactions) <= BLOB_DATA_SIZE as u64);
- let entry = Self::new(start_hash, *num_hashes, transactions);
- *start_hash = entry.hash;
- *num_hashes = 0;
- entry
- }
- #[cfg(test)]
- pub fn new_tick(num_hashes: u64, hash: &Hash) -> Self {
- Entry {
- num_hashes,
- hash: *hash,
- transactions: vec![],
- }
- }
- /// Verifies self.hash is the result of hashing a `start_hash` `self.num_hashes` times.
- /// If the transaction is not a Tick, then hash that as well.
- pub fn verify(&self, start_hash: &Hash) -> bool {
- let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions);
- if self.hash != ref_hash {
- warn!(
- "next_hash is invalid expected: {:?} actual: {:?}",
- self.hash, ref_hash
- );
- return false;
- }
- true
- }
- pub fn is_tick(&self) -> bool {
- self.transactions.is_empty()
- }
- }
- pub fn hash_transactions(transactions: &[Transaction]) -> Hash {
- // a hash of a slice of transactions only needs to hash the signatures
- let signatures: Vec<_> = transactions
- .iter()
- .flat_map(|tx| tx.signatures.iter())
- .collect();
- let merkle_tree = MerkleTree::new(&signatures);
- if let Some(root_hash) = merkle_tree.get_root() {
- *root_hash
- } else {
- Hash::default()
- }
- }
- /// Creates the hash `num_hashes` after `start_hash`. If the transaction contains
- /// a signature, the final hash will be a hash of both the previous ID and
- /// the signature. If num_hashes is zero and there's no transaction data,
- /// start_hash is returned.
- pub fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -> Hash {
- if num_hashes == 0 && transactions.is_empty() {
- return *start_hash;
- }
- let mut poh = Poh::new(*start_hash, None);
- poh.hash(num_hashes.saturating_sub(1));
- if transactions.is_empty() {
- poh.tick().unwrap().hash
- } else {
- poh.record(hash_transactions(transactions)).unwrap().hash
- }
- }
- pub fn reconstruct_entries_from_blobs<I>(blobs: I) -> Result<(Vec<Entry>, u64)>
- where
- I: IntoIterator,
- I::Item: Borrow<Blob>,
- {
- let mut entries: Vec<Entry> = vec![];
- let mut num_ticks = 0;
- for blob in blobs.into_iter() {
- let new_entries: Vec<Entry> = {
- let msg_size = blob.borrow().size();
- deserialize(&blob.borrow().data()[..msg_size])?
- };
- let num_new_ticks: u64 = new_entries.iter().map(|entry| entry.is_tick() as u64).sum();
- num_ticks += num_new_ticks;
- entries.extend(new_entries)
- }
- Ok((entries, num_ticks))
- }
- // an EntrySlice is a slice of Entries
- pub trait EntrySlice {
- /// Verifies the hashes and counts of a slice of transactions are all consistent.
- fn verify_cpu(&self, start_hash: &Hash) -> bool;
- fn verify(&self, start_hash: &Hash) -> bool;
- fn to_shared_blobs(&self) -> Vec<SharedBlob>;
- fn to_blobs(&self) -> Vec<Blob>;
- fn to_single_entry_blobs(&self) -> Vec<Blob>;
- fn to_single_entry_shared_blobs(&self) -> Vec<SharedBlob>;
- }
- impl EntrySlice for [Entry] {
- fn verify_cpu(&self, start_hash: &Hash) -> bool {
- let now = Instant::now();
- let genesis = [Entry {
- num_hashes: 0,
- hash: *start_hash,
- transactions: vec![],
- }];
- let entry_pairs = genesis.par_iter().chain(self).zip(self);
- let res = PAR_THREAD_POOL.with(|thread_pool| {
- thread_pool.borrow().install(|| {
- entry_pairs.all(|(x0, x1)| {
- let r = x1.verify(&x0.hash);
- if !r {
- warn!(
- "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
- x0.hash,
- x1.hash,
- x1.transactions.len()
- );
- }
- r
- })
- })
- });
- inc_new_counter_warn!(
- "entry_verify-duration",
- timing::duration_as_ms(&now.elapsed()) as usize
- );
- res
- }
- #[cfg(not(feature = "cuda"))]
- fn verify(&self, start_hash: &Hash) -> bool {
- self.verify_cpu(start_hash)
- }
- #[cfg(feature = "cuda")]
- fn verify(&self, start_hash: &Hash) -> bool {
- inc_new_counter_warn!("entry_verify-num_entries", self.len() as usize);
- // Use CPU verify if the batch length is < 1K
- if self.len() < 1024 {
- return self.verify_cpu(start_hash);
- }
- let start = Instant::now();
- let genesis = [Entry {
- num_hashes: 0,
- hash: *start_hash,
- transactions: vec![],
- }];
- let hashes: Vec<Hash> = genesis
- .iter()
- .chain(self)
- .map(|entry| entry.hash)
- .take(self.len())
- .collect();
- let num_hashes_vec: Vec<u64> = self
- .into_iter()
- .map(|entry| entry.num_hashes.saturating_sub(1))
- .collect();
- let length = self.len();
- let hashes = Arc::new(Mutex::new(hashes));
- let hashes_clone = hashes.clone();
- let gpu_wait = Instant::now();
- let gpu_verify_thread = thread::spawn(move || {
- let mut hashes = hashes_clone.lock().unwrap();
- let res;
- unsafe {
- res = poh_verify_many(
- hashes.as_mut_ptr() as *mut u8,
- num_hashes_vec.as_ptr(),
- length,
- 1,
- );
- }
- if res != 0 {
- panic!("GPU PoH verify many failed");
- }
- });
- let tx_hashes: Vec<Option<Hash>> = PAR_THREAD_POOL.with(|thread_pool| {
- thread_pool.borrow().install(|| {
- self.into_par_iter()
- .map(|entry| {
- if entry.transactions.is_empty() {
- None
- } else {
- Some(hash_transactions(&entry.transactions))
- }
- })
- .collect()
- })
- });
- gpu_verify_thread.join().unwrap();
- inc_new_counter_warn!(
- "entry_verify-gpu_thread",
- timing::duration_as_ms(&gpu_wait.elapsed()) as usize
- );
- let hashes = Arc::try_unwrap(hashes).unwrap().into_inner().unwrap();
- let res =
- PAR_THREAD_POOL.with(|thread_pool| {
- thread_pool.borrow().install(|| {
- hashes.into_par_iter().zip(tx_hashes).zip(self).all(
- |((hash, tx_hash), answer)| {
- if answer.num_hashes == 0 {
- hash == answer.hash
- } else {
- let mut poh = Poh::new(hash, None);
- if let Some(mixin) = tx_hash {
- poh.record(mixin).unwrap().hash == answer.hash
- } else {
- poh.tick().unwrap().hash == answer.hash
- }
- }
- },
- )
- })
- });
- inc_new_counter_warn!(
- "entry_verify-duration",
- timing::duration_as_ms(&start.elapsed()) as usize
- );
- res
- }
- fn to_blobs(&self) -> Vec<Blob> {
- split_serializable_chunks(
- &self,
- BLOB_DATA_SIZE as u64,
- &|s| bincode::serialized_size(&s).unwrap(),
- &mut |entries: &[Entry]| Blob::from_serializable(entries),
- )
- }
- fn to_shared_blobs(&self) -> Vec<SharedBlob> {
- self.to_blobs()
- .into_iter()
- .map(|b| Arc::new(RwLock::new(b)))
- .collect()
- }
- fn to_single_entry_shared_blobs(&self) -> Vec<SharedBlob> {
- self.to_single_entry_blobs()
- .into_iter()
- .map(|b| Arc::new(RwLock::new(b)))
- .collect()
- }
- fn to_single_entry_blobs(&self) -> Vec<Blob> {
- self.iter().map(Entry::to_blob).collect()
- }
- }
- pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
- let entry = Entry::new(&start, num_hashes, transactions);
- *start = entry.hash;
- entry
- }
- pub fn num_will_fit<T, F>(serializables: &[T], max_size: u64, serialized_size: &F) -> usize
- where
- F: Fn(&[T]) -> u64,
- {
- if serializables.is_empty() {
- return 0;
- }
- let mut num = serializables.len();
- let mut upper = serializables.len();
- let mut lower = 1; // if one won't fit, we have a lot of TODOs
- loop {
- let next;
- if serialized_size(&serializables[..num]) <= max_size {
- next = (upper + num) / 2;
- lower = num;
- } else {
- if num == 1 {
- // if not even one will fit, bail
- num = 0;
- break;
- }
- next = (lower + num) / 2;
- upper = num;
- }
- // same as last time
- if next == num {
- break;
- }
- num = next;
- }
- num
- }
- pub fn split_serializable_chunks<T, R, F1, F2>(
- serializables: &[T],
- max_size: u64,
- serialized_size: &F1,
- converter: &mut F2,
- ) -> Vec<R>
- where
- F1: Fn(&[T]) -> u64,
- F2: FnMut(&[T]) -> R,
- {
- let mut result = vec![];
- let mut chunk_start = 0;
- while chunk_start < serializables.len() {
- let chunk_end =
- chunk_start + num_will_fit(&serializables[chunk_start..], max_size, serialized_size);
- result.push(converter(&serializables[chunk_start..chunk_end]));
- chunk_start = chunk_end;
- }
- result
- }
- /// Creates the next entries for given transactions, outputs
- /// updates start_hash to hash of last Entry, sets num_hashes to 0
- fn next_entries_mut(
- start_hash: &mut Hash,
- num_hashes: &mut u64,
- transactions: Vec<Transaction>,
- ) -> Vec<Entry> {
- split_serializable_chunks(
- &transactions[..],
- BLOB_DATA_SIZE as u64,
- &Entry::serialized_to_blob_size,
- &mut |txs: &[Transaction]| Entry::new_mut(start_hash, num_hashes, txs.to_vec()),
- )
- }
- /// Creates the next Entries for given transactions
- pub fn next_entries(
- start_hash: &Hash,
- num_hashes: u64,
- transactions: Vec<Transaction>,
- ) -> Vec<Entry> {
- let mut hash = *start_hash;
- let mut num_hashes = num_hashes;
- next_entries_mut(&mut hash, &mut num_hashes, transactions)
- }
- pub fn create_ticks(num_ticks: u64, mut hash: Hash) -> Vec<Entry> {
- let mut ticks = Vec::with_capacity(num_ticks as usize);
- for _ in 0..num_ticks {
- let new_tick = next_entry_mut(&mut hash, 1, vec![]);
- ticks.push(new_tick);
- }
- ticks
- }
- pub fn make_tiny_test_entries_from_hash(start: &Hash, num: usize) -> Vec<Entry> {
- let keypair = Keypair::new();
- let pubkey = keypair.pubkey();
- let mut hash = *start;
- let mut num_hashes = 0;
- (0..num)
- .map(|_| {
- let ix = budget_instruction::apply_timestamp(&pubkey, &pubkey, &pubkey, Utc::now());
- let tx = Transaction::new_signed_instructions(&[&keypair], vec![ix], *start);
- Entry::new_mut(&mut hash, &mut num_hashes, vec![tx])
- })
- .collect()
- }
- pub fn make_tiny_test_entries(num: usize) -> Vec<Entry> {
- let zero = Hash::default();
- let one = solana_sdk::hash::hash(&zero.as_ref());
- make_tiny_test_entries_from_hash(&one, num)
- }
- pub fn make_large_test_entries(num_entries: usize) -> Vec<Entry> {
- let zero = Hash::default();
- let one = solana_sdk::hash::hash(&zero.as_ref());
- let keypair = Keypair::new();
- let pubkey = keypair.pubkey();
- let ix = budget_instruction::apply_timestamp(&pubkey, &pubkey, &pubkey, Utc::now());
- let tx = Transaction::new_signed_instructions(&[&keypair], vec![ix], one);
- let serialized_size = serialized_size(&tx).unwrap();
- let num_txs = BLOB_DATA_SIZE / serialized_size as usize;
- let txs = vec![tx; num_txs];
- let entry = next_entries(&one, 1, txs)[0].clone();
- vec![entry; num_entries]
- }
- #[cfg(test)]
- pub fn make_consecutive_blobs(
- id: &solana_sdk::pubkey::Pubkey,
- num_blobs_to_make: u64,
- start_height: u64,
- start_hash: Hash,
- addr: &std::net::SocketAddr,
- ) -> Vec<SharedBlob> {
- let entries = create_ticks(num_blobs_to_make, start_hash);
- let blobs = entries.to_single_entry_shared_blobs();
- let mut index = start_height;
- for blob in &blobs {
- let mut blob = blob.write().unwrap();
- blob.set_index(index);
- blob.set_id(id);
- blob.meta.set_addr(addr);
- index += 1;
- }
- blobs
- }
- #[cfg(test)]
- /// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
- pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
- assert!(num_hashes > 0 || transactions.is_empty());
- Entry {
- num_hashes,
- hash: next_hash(prev_hash, num_hashes, &transactions),
- transactions,
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- use crate::entry::Entry;
- use crate::packet::{to_blobs, BLOB_DATA_SIZE, PACKET_DATA_SIZE};
- use solana_sdk::hash::hash;
- use solana_sdk::instruction::Instruction;
- use solana_sdk::pubkey::Pubkey;
- use solana_sdk::signature::{Keypair, KeypairUtil};
- use solana_sdk::system_transaction;
- use std::net::{IpAddr, Ipv4Addr, SocketAddr};
- fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction {
- let pubkey = keypair.pubkey();
- let ixs = budget_instruction::payment(&pubkey, &pubkey, 1);
- Transaction::new_signed_instructions(&[keypair], ixs, hash)
- }
- fn create_sample_timestamp(keypair: &Keypair, hash: Hash) -> Transaction {
- let pubkey = keypair.pubkey();
- let ix = budget_instruction::apply_timestamp(&pubkey, &pubkey, &pubkey, Utc::now());
- Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
- }
- fn create_sample_apply_signature(keypair: &Keypair, hash: Hash) -> Transaction {
- let pubkey = keypair.pubkey();
- let ix = budget_instruction::apply_signature(&pubkey, &pubkey, &pubkey);
- Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
- }
- #[test]
- fn test_entry_verify() {
- let zero = Hash::default();
- let one = hash(&zero.as_ref());
- assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case, never used
- assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
- assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step
- assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad
- }
- #[test]
- fn test_transaction_reorder_attack() {
- let zero = Hash::default();
- // First, verify entries
- let keypair = Keypair::new();
- let tx0 = system_transaction::create_user_account(&keypair, &keypair.pubkey(), 0, zero);
- let tx1 = system_transaction::create_user_account(&keypair, &keypair.pubkey(), 1, zero);
- let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
- assert!(e0.verify(&zero));
- // Next, swap two transactions and ensure verification fails.
- e0.transactions[0] = tx1; // <-- attack
- e0.transactions[1] = tx0;
- assert!(!e0.verify(&zero));
- }
- #[test]
- fn test_witness_reorder_attack() {
- let zero = Hash::default();
- // First, verify entries
- let keypair = Keypair::new();
- let tx0 = create_sample_timestamp(&keypair, zero);
- let tx1 = create_sample_apply_signature(&keypair, zero);
- let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
- assert!(e0.verify(&zero));
- // Next, swap two witness transactions and ensure verification fails.
- e0.transactions[0] = tx1; // <-- attack
- e0.transactions[1] = tx0;
- assert!(!e0.verify(&zero));
- }
- #[test]
- fn test_next_entry() {
- let zero = Hash::default();
- let tick = next_entry(&zero, 1, vec![]);
- assert_eq!(tick.num_hashes, 1);
- assert_ne!(tick.hash, zero);
- let tick = next_entry(&zero, 0, vec![]);
- assert_eq!(tick.num_hashes, 0);
- assert_eq!(tick.hash, zero);
- let keypair = Keypair::new();
- let tx0 = create_sample_timestamp(&keypair, zero);
- let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
- assert_eq!(entry0.num_hashes, 1);
- assert_eq!(entry0.hash, next_hash(&zero, 1, &vec![tx0]));
- }
- #[test]
- #[should_panic]
- fn test_next_entry_panic() {
- let zero = Hash::default();
- let keypair = Keypair::new();
- let tx = system_transaction::create_user_account(&keypair, &keypair.pubkey(), 0, zero);
- next_entry(&zero, 0, vec![tx]);
- }
- #[test]
- fn test_serialized_to_blob_size() {
- let zero = Hash::default();
- let keypair = Keypair::new();
- let tx = system_transaction::create_user_account(&keypair, &keypair.pubkey(), 0, zero);
- let entry = next_entry(&zero, 1, vec![tx.clone()]);
- assert_eq!(
- Entry::serialized_to_blob_size(&[tx]),
- serialized_size(&vec![entry]).unwrap() // blobs are Vec<Entry>
- );
- }
- #[test]
- fn test_verify_slice() {
- solana_logger::setup();
- let zero = Hash::default();
- let one = hash(&zero.as_ref());
- assert!(vec![][..].verify(&zero)); // base case
- assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero)); // singleton case 1
- assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one)); // singleton case 2, bad
- assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero)); // inductive step
- let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
- bad_ticks[1].hash = one;
- assert!(!bad_ticks.verify(&zero)); // inductive step, bad
- }
- #[test]
- fn test_verify_slice_with_hashes() {
- solana_logger::setup();
- let zero = Hash::default();
- let one = hash(&zero.as_ref());
- let two = hash(&one.as_ref());
- assert!(vec![][..].verify(&one)); // base case
- assert!(vec![Entry::new_tick(1, &two)][..].verify(&one)); // singleton case 1
- assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two)); // singleton case 2, bad
- let mut ticks = vec![next_entry(&one, 1, vec![])];
- ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
- assert!(ticks.verify(&one)); // inductive step
- let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
- bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
- bad_ticks[1].hash = one;
- assert!(!bad_ticks.verify(&one)); // inductive step, bad
- }
- #[test]
- fn test_verify_slice_with_hashes_and_transactions() {
- solana_logger::setup();
- let zero = Hash::default();
- let one = hash(&zero.as_ref());
- let two = hash(&one.as_ref());
- let alice_pubkey = Keypair::default();
- let tx0 = create_sample_payment(&alice_pubkey, one);
- let tx1 = create_sample_timestamp(&alice_pubkey, one);
- assert!(vec![][..].verify(&one)); // base case
- assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one)); // singleton case 1
- assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two)); // singleton case 2, bad
- let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
- ticks.push(next_entry(
- &ticks.last().unwrap().hash,
- 1,
- vec![tx1.clone()],
- ));
- assert!(ticks.verify(&one)); // inductive step
- let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
- bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
- bad_ticks[1].hash = one;
- assert!(!bad_ticks.verify(&one)); // inductive step, bad
- }
- fn blob_sized_entries(num_entries: usize) -> Vec<Entry> {
- // rough guess
- let mut magic_len = BLOB_DATA_SIZE
- - serialized_size(&vec![Entry {
- num_hashes: 0,
- hash: Hash::default(),
- transactions: vec![],
- }])
- .unwrap() as usize;
- loop {
- let entries = vec![Entry {
- num_hashes: 0,
- hash: Hash::default(),
- transactions: vec![Transaction::new_unsigned_instructions(vec![
- Instruction::new(Pubkey::default(), &vec![0u8; magic_len as usize], vec![]),
- ])],
- }];
- let size = serialized_size(&entries).unwrap() as usize;
- if size < BLOB_DATA_SIZE {
- magic_len += BLOB_DATA_SIZE - size;
- } else if size > BLOB_DATA_SIZE {
- magic_len -= size - BLOB_DATA_SIZE;
- } else {
- break;
- }
- }
- vec![
- Entry {
- num_hashes: 0,
- hash: Hash::default(),
- transactions: vec![Transaction::new_unsigned_instructions(vec![
- Instruction::new(Pubkey::default(), &vec![0u8; magic_len], vec![]),
- ])],
- };
- num_entries
- ]
- }
- #[test]
- fn test_entries_to_blobs() {
- solana_logger::setup();
- let entries = blob_sized_entries(10);
- let blobs = entries.to_blobs();
- for blob in &blobs {
- assert_eq!(blob.size(), BLOB_DATA_SIZE);
- }
- assert_eq!(reconstruct_entries_from_blobs(blobs).unwrap().0, entries);
- }
- #[test]
- fn test_multiple_entries_to_blobs() {
- solana_logger::setup();
- let num_blobs = 10;
- let serialized_size =
- bincode::serialized_size(&make_tiny_test_entries_from_hash(&Hash::default(), 1))
- .unwrap();
- let num_entries = (num_blobs * BLOB_DATA_SIZE as u64) / serialized_size;
- let entries = make_tiny_test_entries_from_hash(&Hash::default(), num_entries as usize);
- let blob_q = entries.to_blobs();
- assert_eq!(blob_q.len() as u64, num_blobs);
- assert_eq!(reconstruct_entries_from_blobs(blob_q).unwrap().0, entries);
- }
- #[test]
- fn test_bad_blobs_attack() {
- solana_logger::setup();
- let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000);
- let blobs_q = to_blobs(vec![(0, addr)]).unwrap(); // <-- attack!
- assert!(reconstruct_entries_from_blobs(blobs_q).is_err());
- }
- #[test]
- fn test_next_entries() {
- solana_logger::setup();
- let hash = Hash::default();
- let next_hash = solana_sdk::hash::hash(&hash.as_ref());
- let keypair = Keypair::new();
- let tx_small = create_sample_timestamp(&keypair, next_hash);
- let tx_large = create_sample_payment(&keypair, next_hash);
- let tx_small_size = serialized_size(&tx_small).unwrap() as usize;
- let tx_large_size = serialized_size(&tx_large).unwrap() as usize;
- let entry_size = serialized_size(&Entry {
- num_hashes: 0,
- hash: Hash::default(),
- transactions: vec![],
- })
- .unwrap() as usize;
- assert!(tx_small_size < tx_large_size);
- assert!(tx_large_size < PACKET_DATA_SIZE);
- let threshold = (BLOB_DATA_SIZE - entry_size) / tx_small_size;
- // verify no split
- let transactions = vec![tx_small.clone(); threshold];
- let entries0 = next_entries(&hash, 0, transactions.clone());
- assert_eq!(entries0.len(), 1);
- assert!(entries0.verify(&hash));
- // verify the split with uniform transactions
- let transactions = vec![tx_small.clone(); threshold * 2];
- let entries0 = next_entries(&hash, 0, transactions.clone());
- assert_eq!(entries0.len(), 2);
- assert!(entries0.verify(&hash));
- // verify the split with small transactions followed by large
- // transactions
- let mut transactions = vec![tx_small.clone(); BLOB_DATA_SIZE / tx_small_size];
- let large_transactions = vec![tx_large.clone(); BLOB_DATA_SIZE / tx_large_size];
- transactions.extend(large_transactions);
- let entries0 = next_entries(&hash, 0, transactions.clone());
- assert!(entries0.len() >= 2);
- assert!(entries0.verify(&hash));
- }
- #[test]
- fn test_num_will_fit_empty() {
- let serializables: Vec<u32> = vec![];
- let result = num_will_fit(&serializables[..], 8, &|_| 4);
- assert_eq!(result, 0);
- }
- #[test]
- fn test_num_will_fit() {
- let serializables_vec: Vec<u8> = (0..10).map(|_| 1).collect();
- let serializables = &serializables_vec[..];
- let sum = |i: &[u8]| (0..i.len()).into_iter().sum::<usize>() as u64;
- // sum[0] is = 0, but sum[0..1] > 0, so result contains 1 item
- let result = num_will_fit(serializables, 0, &sum);
- assert_eq!(result, 1);
- // sum[0..3] is <= 8, but sum[0..4] > 8, so result contains 3 items
- let result = num_will_fit(serializables, 8, &sum);
- assert_eq!(result, 4);
- // sum[0..1] is = 1, but sum[0..2] > 0, so result contains 2 items
- let result = num_will_fit(serializables, 1, &sum);
- assert_eq!(result, 2);
- // sum[0..9] = 45, so contains all items
- let result = num_will_fit(serializables, 45, &sum);
- assert_eq!(result, 10);
- // sum[0..8] <= 44, but sum[0..9] = 45, so contains all but last item
- let result = num_will_fit(serializables, 44, &sum);
- assert_eq!(result, 9);
- // sum[0..9] <= 46, but contains all items
- let result = num_will_fit(serializables, 46, &sum);
- assert_eq!(result, 10);
- // too small to fit a single u64
- let result = num_will_fit(&[0u64], (std::mem::size_of::<u64>() - 1) as u64, &|i| {
- (std::mem::size_of::<u64>() * i.len()) as u64
- });
- assert_eq!(result, 0);
- }
- }
|