diff --git a/core/benches/blocktree.rs b/core/benches/blocktree.rs index 287f85380e..10e1b9eeff 100644 --- a/core/benches/blocktree.rs +++ b/core/benches/blocktree.rs @@ -33,7 +33,11 @@ fn setup_read_bench( slot: u64, ) { // Make some big and small entries - let entries = create_ticks(num_large_shreds * 4 + num_small_shreds * 2, Hash::default()); + let entries = create_ticks( + num_large_shreds * 4 + num_small_shreds * 2, + 0, + Hash::default(), + ); // Convert the entries to shreds, write the shreds to the ledger let shreds = entries_to_test_shreds(entries, slot, slot.saturating_sub(1), true); @@ -48,7 +52,7 @@ fn setup_read_bench( fn bench_write_small(bench: &mut Bencher) { let ledger_path = get_tmp_ledger_path!(); let num_entries = 32 * 1024; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); bench_write_shreds(bench, entries, &ledger_path); } @@ -58,7 +62,7 @@ fn bench_write_small(bench: &mut Bencher) { fn bench_write_big(bench: &mut Bencher) { let ledger_path = get_tmp_ledger_path!(); let num_entries = 32 * 1024; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); bench_write_shreds(bench, entries, &ledger_path); } @@ -127,7 +131,7 @@ fn bench_insert_data_shred_small(bench: &mut Bencher) { let blocktree = Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"); let num_entries = 32 * 1024; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); bench.iter(move || { let shreds = entries_to_test_shreds(entries.clone(), 0, 0, true); blocktree.insert_shreds(shreds, None).unwrap(); @@ -142,7 +146,7 @@ fn bench_insert_data_shred_big(bench: &mut Bencher) { let blocktree = Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"); let num_entries = 32 * 1024; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); bench.iter(move || { let shreds = entries_to_test_shreds(entries.clone(), 0, 0, true); blocktree.insert_shreds(shreds, None).unwrap(); diff --git a/core/benches/shredder.rs b/core/benches/shredder.rs index 6536429823..c72b5cc8d2 100644 --- a/core/benches/shredder.rs +++ b/core/benches/shredder.rs @@ -33,7 +33,7 @@ fn bench_shredder_ticks(bencher: &mut Bencher) { let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size; // ~1Mb let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64; - let entries = create_ticks(num_ticks, Hash::default()); + let entries = create_ticks(num_ticks, 0, Hash::default()); bencher.iter(|| { let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp.clone()).unwrap(); shredder.entries_to_shreds(&entries, true, 0); @@ -62,7 +62,7 @@ fn bench_deshredder(bencher: &mut Bencher) { // ~10Mb let num_shreds = ((10000 * 1000) + (shred_size - 1)) / shred_size; let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64; - let entries = create_ticks(num_ticks, Hash::default()); + let entries = create_ticks(num_ticks, 0, Hash::default()); let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp).unwrap(); let data_shreds = shredder.entries_to_shreds(&entries, true, 0).0; bencher.iter(|| { diff --git a/core/src/blockstream_service.rs b/core/src/blockstream_service.rs index 69530faaf5..2d2c5e178f 100644 --- a/core/src/blockstream_service.rs +++ b/core/src/blockstream_service.rs @@ -134,7 +134,7 @@ mod test { let (slot_full_sender, slot_full_receiver) = channel(); // Create entries - 4 ticks + 1 populated entry + 1 tick - let mut entries = create_ticks(4, Hash::default()); + let mut entries = create_ticks(4, 0, Hash::default()); let keypair = Keypair::new(); let mut blockhash = entries[3].hash; @@ -142,7 +142,7 @@ mod test { let entry = Entry::new(&mut blockhash, 1, vec![tx]); blockhash = entry.hash; entries.push(entry); - let final_tick = create_ticks(1, blockhash); + let final_tick = create_ticks(1, 0, blockhash); entries.extend_from_slice(&final_tick); let expected_entries = entries.clone(); diff --git a/core/src/broadcast_stage.rs b/core/src/broadcast_stage.rs index cdfd535ad4..dc4170354b 100644 --- a/core/src/broadcast_stage.rs +++ b/core/src/broadcast_stage.rs @@ -281,7 +281,7 @@ mod test { max_tick_height = bank.max_tick_height(); ticks_per_slot = bank.ticks_per_slot(); slot = bank.slot(); - let ticks = create_ticks(max_tick_height - start_tick_height, Hash::default()); + let ticks = create_ticks(max_tick_height - start_tick_height, 0, Hash::default()); for (i, tick) in ticks.into_iter().enumerate() { entry_sender .send((bank.clone(), (tick, i as u64 + 1))) diff --git a/core/src/broadcast_stage/standard_broadcast_run.rs b/core/src/broadcast_stage/standard_broadcast_run.rs index b3a7e74dbe..80ec26ba1d 100644 --- a/core/src/broadcast_stage/standard_broadcast_run.rs +++ b/core/src/broadcast_stage/standard_broadcast_run.rs @@ -334,7 +334,7 @@ mod test { setup(num_shreds_per_slot); // Insert 1 less than the number of ticks needed to finish the slot - let ticks = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash()); + let ticks = create_ticks(genesis_block.ticks_per_slot - 1, 0, genesis_block.hash()); let receive_results = ReceiveResults { entries: ticks.clone(), time_elapsed: Duration::new(3, 0), @@ -372,7 +372,7 @@ mod test { // Interrupting the slot should cause the unfinished_slot and stats to reset let num_shreds = 1; assert!(num_shreds < num_shreds_per_slot); - let ticks = create_ticks(max_ticks_per_n_shreds(num_shreds), genesis_block.hash()); + let ticks = create_ticks(max_ticks_per_n_shreds(num_shreds), 0, genesis_block.hash()); let receive_results = ReceiveResults { entries: ticks.clone(), time_elapsed: Duration::new(2, 0), @@ -401,7 +401,7 @@ mod test { setup(num_shreds_per_slot); // Insert complete slot of ticks needed to finish the slot - let ticks = create_ticks(genesis_block.ticks_per_slot, genesis_block.hash()); + let ticks = create_ticks(genesis_block.ticks_per_slot, 0, genesis_block.hash()); let receive_results = ReceiveResults { entries: ticks.clone(), time_elapsed: Duration::new(3, 0), diff --git a/core/src/chacha_cuda.rs b/core/src/chacha_cuda.rs index 39ec67994e..e6466ba1b2 100644 --- a/core/src/chacha_cuda.rs +++ b/core/src/chacha_cuda.rs @@ -131,7 +131,7 @@ mod tests { } let slots_per_segment = 32; - let entries = create_ticks(slots_per_segment, Hash::default()); + let entries = create_ticks(slots_per_segment, 0, Hash::default()); let ledger_dir = "test_encrypt_file_many_keys_single"; let ledger_path = get_tmp_ledger_path(ledger_dir); let ticks_per_slot = 16; @@ -196,7 +196,7 @@ mod tests { let ledger_dir = "test_encrypt_file_many_keys_multiple"; let ledger_path = get_tmp_ledger_path(ledger_dir); let ticks_per_slot = 90; - let entries = create_ticks(2 * ticks_per_slot, Hash::default()); + let entries = create_ticks(2 * ticks_per_slot, 0, Hash::default()); let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap()); blocktree .write_entries( diff --git a/core/src/packet.rs b/core/src/packet.rs index 84802d8343..28166f3f4d 100644 --- a/core/src/packet.rs +++ b/core/src/packet.rs @@ -160,14 +160,6 @@ impl fmt::Debug for Blob { } } -#[derive(Debug)] -pub enum BlobError { - /// the Blob's meta and data are not self-consistent - BadState, - /// Blob verification failed - VerificationFailed, -} - impl Packets { pub fn recv_from(&mut self, socket: &UdpSocket) -> Result { let mut i = 0; diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index e813f7a889..eb27b4c754 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -5,12 +5,12 @@ use crate::confidence::{ AggregateConfidenceService, ConfidenceAggregationData, ForkConfidenceCache, }; use crate::consensus::{StakeLockout, Tower}; -use crate::packet::BlobError; use crate::poh_recorder::PohRecorder; use crate::result::{Error, Result}; use crate::rpc_subscriptions::RpcSubscriptions; use crate::service::Service; use solana_ledger::bank_forks::BankForks; +use solana_ledger::block_error::BlockError; use solana_ledger::blocktree::{Blocktree, BlocktreeError}; use solana_ledger::blocktree_processor; use solana_ledger::entry::{Entry, EntrySlice}; @@ -113,6 +113,7 @@ struct ForkProgress { last_entry: Hash, num_shreds: usize, num_entries: usize, + tick_hash_count: u64, started_ms: u64, is_dead: bool, stats: ReplaySlotStats, @@ -124,6 +125,7 @@ impl ForkProgress { last_entry, num_shreds: 0, num_entries: 0, + tick_hash_count: 0, started_ms: timing::timestamp(), is_dead: false, stats: ReplaySlotStats::new(slot), @@ -399,7 +401,7 @@ impl ReplayStage { let tx_error = Err(e.clone()); !Bank::can_commit(&tx_error) } - Err(Error::BlobError(BlobError::VerificationFailed)) => true, + Err(Error::BlockError(_)) => true, Err(Error::BlocktreeError(BlocktreeError::InvalidShredData(_))) => true, _ => false, } @@ -759,25 +761,46 @@ impl ReplayStage { result } + fn verify_ticks( + bank: &Arc, + entries: &[Entry], + tick_hash_count: &mut u64, + ) -> std::result::Result<(), BlockError> { + if entries.is_empty() { + return Ok(()); + } + + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); + if !entries.verify_tick_hash_count(tick_hash_count, hashes_per_tick) { + return Err(BlockError::InvalidTickHashCount); + } + + let next_bank_tick_height = bank.tick_height() + entries.tick_count(); + let max_bank_tick_height = bank.max_tick_height(); + if next_bank_tick_height > max_bank_tick_height { + return Err(BlockError::InvalidTickCount); + } + + let has_trailing_entry = !entries.last().unwrap().is_tick(); + if next_bank_tick_height == max_bank_tick_height && has_trailing_entry { + return Err(BlockError::TrailingEntry); + } + + Ok(()) + } + fn verify_and_process_entries( bank: &Arc, entries: &[Entry], shred_index: usize, bank_progress: &mut ForkProgress, ) -> Result<()> { - datapoint_debug!("verify-batch-size", ("size", entries.len() as i64, i64)); - let mut verify_total = Measure::start("verify_and_process_entries"); let last_entry = &bank_progress.last_entry; - let mut entry_state = entries.start_verify(last_entry); - - let mut replay_elapsed = Measure::start("replay_elapsed"); - let res = blocktree_processor::process_entries(bank, entries, true); - replay_elapsed.stop(); - bank_progress.stats.replay_elapsed += replay_elapsed.as_us(); - - if !entry_state.finish_verify(entries) { - info!( - "entry verification failed, slot: {}, entry len: {}, tick_height: {}, last entry: {}, last_blockhash: {}, shred_index: {}", + let tick_hash_count = &mut bank_progress.tick_hash_count; + let handle_block_error = move |block_error: BlockError| -> Result<()> { + warn!( + "{:#?}, slot: {}, entry len: {}, tick_height: {}, last entry: {}, last_blockhash: {}, shred_index: {}", + block_error, bank.slot(), entries.len(), bank.tick_height(), @@ -791,8 +814,27 @@ impl ReplayStage { ("slot", bank.slot(), i64), ("last_entry", last_entry.to_string(), String), ); - return Err(Error::BlobError(BlobError::VerificationFailed)); + + Err(Error::BlockError(block_error)) + }; + + if let Err(block_error) = Self::verify_ticks(bank, entries, tick_hash_count) { + return handle_block_error(block_error); } + + datapoint_info!("verify-batch-size", ("size", entries.len() as i64, i64)); + let mut verify_total = Measure::start("verify_and_process_entries"); + let mut entry_state = entries.start_verify(last_entry); + + let mut replay_elapsed = Measure::start("replay_elapsed"); + let res = blocktree_processor::process_entries(bank, entries, true); + replay_elapsed.stop(); + bank_progress.stats.replay_elapsed += replay_elapsed.as_us(); + + if !entry_state.finish_verify(entries) { + return handle_block_error(BlockError::InvalidEntryHash); + } + verify_total.stop(); bank_progress.stats.entry_verification_elapsed = verify_total.as_us() - replay_elapsed.as_us(); @@ -951,17 +993,20 @@ mod test { let missing_keypair = Keypair::new(); let missing_keypair2 = Keypair::new(); - let res = check_dead_fork(|_keypair, blockhash, slot| { + let res = check_dead_fork(|_keypair, bank| { + let blockhash = bank.last_blockhash(); + let slot = bank.slot(); + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); let entry = entry::next_entry( - blockhash, - 1, + &blockhash, + hashes_per_tick.saturating_sub(1), vec![ - system_transaction::transfer(&keypair1, &keypair2.pubkey(), 2, *blockhash), // should be fine, + system_transaction::transfer(&keypair1, &keypair2.pubkey(), 2, blockhash), // should be fine, system_transaction::transfer( &missing_keypair, &missing_keypair2.pubkey(), 2, - *blockhash, + blockhash, ), // should cause AccountNotFound error ], ); @@ -977,29 +1022,105 @@ mod test { #[test] fn test_dead_fork_entry_verification_failure() { let keypair2 = Keypair::new(); - let res = check_dead_fork(|genesis_keypair, blockhash, slot| { + let res = check_dead_fork(|genesis_keypair, bank| { + let blockhash = bank.last_blockhash(); + let slot = bank.slot(); let bad_hash = hash(&[2; 30]); + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); let entry = entry::next_entry( // Use wrong blockhash so that the entry causes an entry verification failure &bad_hash, - 1, + hashes_per_tick.saturating_sub(1), vec![system_transaction::transfer( &genesis_keypair, &keypair2.pubkey(), 2, - *blockhash, + blockhash, )], ); entries_to_test_shreds(vec![entry], slot, slot.saturating_sub(1), false) }); - assert_matches!(res, Err(Error::BlobError(BlobError::VerificationFailed))); + if let Err(Error::BlockError(block_error)) = res { + assert_eq!(block_error, BlockError::InvalidEntryHash); + } else { + assert!(false); + } + } + + #[test] + fn test_dead_fork_invalid_tick_hash_count() { + let res = check_dead_fork(|_keypair, bank| { + let blockhash = bank.last_blockhash(); + let slot = bank.slot(); + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); + assert!(hashes_per_tick > 0); + + let too_few_hashes_tick = Entry::new(&blockhash, hashes_per_tick - 1, vec![]); + entries_to_test_shreds( + vec![too_few_hashes_tick], + slot, + slot.saturating_sub(1), + false, + ) + }); + + if let Err(Error::BlockError(block_error)) = res { + assert_eq!(block_error, BlockError::InvalidTickHashCount); + } else { + assert!(false); + } + } + + #[test] + fn test_dead_fork_invalid_slot_tick_count() { + let res = check_dead_fork(|_keypair, bank| { + let blockhash = bank.last_blockhash(); + let slot = bank.slot(); + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); + entries_to_test_shreds( + entry::create_ticks(bank.ticks_per_slot() + 1, hashes_per_tick, blockhash), + slot, + slot.saturating_sub(1), + false, + ) + }); + + if let Err(Error::BlockError(block_error)) = res { + assert_eq!(block_error, BlockError::InvalidTickCount); + } else { + assert!(false); + } + } + + #[test] + fn test_dead_fork_trailing_entry() { + let keypair = Keypair::new(); + let res = check_dead_fork(|genesis_keypair, bank| { + let blockhash = bank.last_blockhash(); + let slot = bank.slot(); + let hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); + let mut entries = + entry::create_ticks(bank.ticks_per_slot(), hashes_per_tick, blockhash.clone()); + let last_entry_hash = entries.last().unwrap().hash; + let tx = + system_transaction::transfer(&genesis_keypair, &keypair.pubkey(), 2, blockhash); + let trailing_entry = entry::next_entry(&last_entry_hash, 1, vec![tx]); + entries.push(trailing_entry); + entries_to_test_shreds(entries, slot, slot.saturating_sub(1), false) + }); + + if let Err(Error::BlockError(block_error)) = res { + assert_eq!(block_error, BlockError::TrailingEntry); + } else { + assert!(false); + } } #[test] fn test_dead_fork_entry_deserialize_failure() { // Insert entry that causes deserialization failure - let res = check_dead_fork(|_, _, _| { + let res = check_dead_fork(|_, _| { let payload_len = SIZE_OF_DATA_SHRED_PAYLOAD; let gibberish = [0xa5u8; PACKET_DATA_SIZE]; let mut data_header = DataShredHeader::default(); @@ -1027,7 +1148,7 @@ mod test { // marked as dead. Returns the error for caller to verify. fn check_dead_fork(shred_to_insert: F) -> Result<()> where - F: Fn(&Keypair, &Hash, u64) -> Vec, + F: Fn(&Keypair, Arc) -> Vec, { let ledger_path = get_tmp_ledger_path!(); let res = { @@ -1035,15 +1156,16 @@ mod test { Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"), ); let GenesisBlockInfo { - genesis_block, + mut genesis_block, mint_keypair, .. } = create_genesis_block(1000); + genesis_block.poh_config.hashes_per_tick = Some(2); let bank0 = Arc::new(Bank::new(&genesis_block)); let mut progress = HashMap::new(); let last_blockhash = bank0.last_blockhash(); progress.insert(bank0.slot(), ForkProgress::new(0, last_blockhash)); - let shreds = shred_to_insert(&mint_keypair, &last_blockhash, bank0.slot()); + let shreds = shred_to_insert(&mint_keypair, bank0.clone()); blocktree.insert_shreds(shreds, None).unwrap(); let (res, _tx_count) = ReplayStage::replay_blocktree_into_bank(&bank0, &blocktree, &mut progress); diff --git a/core/src/result.rs b/core/src/result.rs index ebada594a7..87d063053a 100644 --- a/core/src/result.rs +++ b/core/src/result.rs @@ -1,8 +1,8 @@ //! The `result` module exposes a Result type that propagates one of many different Error types. use crate::cluster_info; -use crate::packet; use crate::poh_recorder; +use solana_ledger::block_error; use solana_ledger::blocktree; use solana_ledger::snapshot_utils; use solana_sdk::transaction; @@ -23,10 +23,10 @@ pub enum Error { Serialize(std::boxed::Box), TransactionError(transaction::TransactionError), ClusterInfoError(cluster_info::ClusterInfoError), - BlobError(packet::BlobError), ErasureError(reed_solomon_erasure::Error), SendError, PohRecorderError(poh_recorder::PohRecorderError), + BlockError(block_error::BlockError), BlocktreeError(blocktree::BlocktreeError), FsExtra(fs_extra::error::Error), ToBlobError, diff --git a/core/src/window_service.rs b/core/src/window_service.rs index 318e0a4aef..f8733b2437 100644 --- a/core/src/window_service.rs +++ b/core/src/window_service.rs @@ -326,7 +326,7 @@ mod test { let blocktree_path = get_tmp_ledger_path!(); let blocktree = Arc::new(Blocktree::open(&blocktree_path).unwrap()); let num_entries = 10; - let original_entries = create_ticks(num_entries, Hash::default()); + let original_entries = create_ticks(num_entries, 0, Hash::default()); let mut shreds = local_entries_to_shred(&original_entries, 0, 0, &Arc::new(Keypair::new())); shreds.reverse(); blocktree diff --git a/core/tests/storage_stage.rs b/core/tests/storage_stage.rs index 85472a7047..45de08f138 100644 --- a/core/tests/storage_stage.rs +++ b/core/tests/storage_stage.rs @@ -117,6 +117,7 @@ mod tests { &next_bank, &entry::create_ticks( DEFAULT_TICKS_PER_SLOT * next_bank.slots_per_segment() + 1, + 0, bank.last_blockhash(), ), true, @@ -207,7 +208,7 @@ mod tests { let bank = Arc::new(Bank::new_from_parent(&last_bank, &keypair.pubkey(), i)); blocktree_processor::process_entries( &bank, - &entry::create_ticks(64, bank.last_blockhash()), + &entry::create_ticks(64, 0, bank.last_blockhash()), true, ) .expect("failed process entries"); diff --git a/ledger/src/block_error.rs b/ledger/src/block_error.rs new file mode 100644 index 0000000000..eca7342a76 --- /dev/null +++ b/ledger/src/block_error.rs @@ -0,0 +1,15 @@ +#[derive(Debug, PartialEq)] +pub enum BlockError { + /// Block entries hashes must all be valid + InvalidEntryHash, + + /// Blocks can not have extra ticks or missing ticks + InvalidTickCount, + + /// All ticks must contain the same number of hashes within a block + InvalidTickHashCount, + + /// Blocks must end in a tick entry, trailing transaction entries are not allowed to guarantee + /// that each block has the same number of hashes + TrailingEntry, +} diff --git a/ledger/src/blocktree.rs b/ledger/src/blocktree.rs index 263f7a6cc3..ce3ff6e869 100644 --- a/ledger/src/blocktree.rs +++ b/ledger/src/blocktree.rs @@ -888,18 +888,12 @@ impl Blocktree { keypair: &Arc, entries: Vec, ) -> Result { - assert!(num_ticks_in_start_slot < ticks_per_slot); - let mut remaining_ticks_in_slot = ticks_per_slot - num_ticks_in_start_slot; + let mut parent_slot = parent.map_or(start_slot.saturating_sub(1), |v| v); + let num_slots = (start_slot - parent_slot).max(1); // Note: slot 0 has parent slot 0 + assert!(num_ticks_in_start_slot < num_slots * ticks_per_slot); + let mut remaining_ticks_in_slot = num_slots * ticks_per_slot - num_ticks_in_start_slot; let mut current_slot = start_slot; - let mut parent_slot = parent.map_or( - if current_slot == 0 { - current_slot - } else { - current_slot - 1 - }, - |v| v, - ); let mut shredder = Shredder::new(current_slot, parent_slot, 0.0, keypair.clone()) .expect("Failed to create entry shredder"); let mut all_shreds = vec![]; @@ -1686,14 +1680,14 @@ fn slot_has_updates(slot_meta: &SlotMeta, slot_meta_backup: &Option) - // // Returns the blockhash that can be used to append entries with. pub fn create_new_ledger(ledger_path: &Path, genesis_block: &GenesisBlock) -> Result { - let ticks_per_slot = genesis_block.ticks_per_slot; Blocktree::destroy(ledger_path)?; genesis_block.write(&ledger_path)?; // Fill slot 0 with ticks that link back to the genesis_block to bootstrap the ledger. let blocktree = Blocktree::open(ledger_path)?; - - let entries = create_ticks(ticks_per_slot, genesis_block.hash()); + let ticks_per_slot = genesis_block.ticks_per_slot; + let hashes_per_tick = genesis_block.poh_config.hashes_per_tick.unwrap_or(0); + let entries = create_ticks(ticks_per_slot, hashes_per_tick, genesis_block.hash()); let last_hash = entries.last().unwrap().hash; let shredder = Shredder::new(0, 0, 0.0, Arc::new(Keypair::new())) @@ -1787,16 +1781,18 @@ pub fn entries_to_test_shreds( shredder.entries_to_shreds(&entries, is_full_slot, 0).0 } +// used for tests only pub fn make_slot_entries( slot: u64, parent_slot: u64, num_entries: u64, ) -> (Vec, Vec) { - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); let shreds = entries_to_test_shreds(entries.clone(), slot, parent_slot, true); (shreds, entries) } +// used for tests only pub fn make_many_slot_entries( start_slot: u64, num_slots: u64, @@ -1816,6 +1812,7 @@ pub fn make_many_slot_entries( } // Create shreds for slots that have a parent-child relationship defined by the input `chain` +// used for tests only pub fn make_chaining_slot_entries( chain: &[u64], entries_per_slot: u64, @@ -1857,7 +1854,7 @@ pub mod tests { let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); let ledger = Blocktree::open(&ledger_path).unwrap(); - let ticks = create_ticks(genesis_block.ticks_per_slot, genesis_block.hash()); + let ticks = create_ticks(genesis_block.ticks_per_slot, 0, genesis_block.hash()); let entries = ledger.get_slot_entries(0, 0, None).unwrap(); assert_eq!(ticks, entries); @@ -1911,7 +1908,7 @@ pub mod tests { let mut shreds_per_slot = vec![]; for i in 0..num_slots { - let mut new_ticks = create_ticks(ticks_per_slot, Hash::default()); + let mut new_ticks = create_ticks(ticks_per_slot, 0, Hash::default()); let num_shreds = ledger .write_entries( i, @@ -2241,7 +2238,7 @@ pub mod tests { let blocktree_path = get_tmp_ledger_path("test_get_slot_entries1"); { let blocktree = Blocktree::open(&blocktree_path).unwrap(); - let entries = create_ticks(8, Hash::default()); + let entries = create_ticks(8, 0, Hash::default()); let shreds = entries_to_test_shreds(entries[0..4].to_vec(), 1, 0, false); blocktree .insert_shreds(shreds, None) @@ -2276,7 +2273,7 @@ pub mod tests { let num_slots = 5 as u64; let mut index = 0; for slot in 0..num_slots { - let entries = create_ticks(slot + 1, Hash::default()); + let entries = create_ticks(slot + 1, 0, Hash::default()); let last_entry = entries.last().unwrap().clone(); let mut shreds = entries_to_test_shreds(entries, slot, slot.saturating_sub(1), false); @@ -2308,13 +2305,13 @@ pub mod tests { let num_slots = 5 as u64; let shreds_per_slot = 5 as u64; let entry_serialized_size = - bincode::serialized_size(&create_ticks(1, Hash::default())).unwrap(); + bincode::serialized_size(&create_ticks(1, 0, Hash::default())).unwrap(); let entries_per_slot = (shreds_per_slot * PACKET_DATA_SIZE as u64) / entry_serialized_size; // Write entries for slot in 0..num_slots { - let entries = create_ticks(entries_per_slot, Hash::default()); + let entries = create_ticks(entries_per_slot, 0, Hash::default()); let shreds = entries_to_test_shreds(entries.clone(), slot, slot.saturating_sub(1), false); assert!(shreds.len() as u64 >= shreds_per_slot); @@ -3097,7 +3094,7 @@ pub mod tests { assert!(gap > 3); // Create enough entries to ensure there are at least two shreds created let num_entries = max_ticks_per_n_shreds(1) + 1; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); let mut shreds = entries_to_test_shreds(entries, slot, 0, true); let num_shreds = shreds.len(); assert!(num_shreds > 1); @@ -3189,7 +3186,7 @@ pub mod tests { assert_eq!(blocktree.find_missing_data_indexes(slot, 4, 3, 1), empty); assert_eq!(blocktree.find_missing_data_indexes(slot, 1, 2, 0), empty); - let entries = create_ticks(100, Hash::default()); + let entries = create_ticks(100, 0, Hash::default()); let mut shreds = entries_to_test_shreds(entries, slot, 0, true); assert!(shreds.len() > 2); shreds.drain(2..); @@ -3231,7 +3228,7 @@ pub mod tests { // Write entries let num_entries = 10; - let entries = create_ticks(num_entries, Hash::default()); + let entries = create_ticks(num_entries, 0, Hash::default()); let shreds = entries_to_test_shreds(entries, slot, 0, true); let num_shreds = shreds.len(); @@ -3746,7 +3743,7 @@ pub mod tests { { let blocktree = Blocktree::open(&blocktree_path).unwrap(); let num_ticks = 8; - let entries = create_ticks(num_ticks, Hash::default()); + let entries = create_ticks(num_ticks, 0, Hash::default()); let slot = 1; let shreds = entries_to_test_shreds(entries, slot, 0, false); let next_shred_index = shreds.len(); diff --git a/ledger/src/blocktree_processor.rs b/ledger/src/blocktree_processor.rs index ee27966672..350ff216aa 100644 --- a/ledger/src/blocktree_processor.rs +++ b/ledger/src/blocktree_processor.rs @@ -1,4 +1,5 @@ use crate::bank_forks::BankForks; +use crate::block_error::BlockError; use crate::blocktree::Blocktree; use crate::blocktree_meta::SlotMeta; use crate::entry::{create_ticks, Entry, EntrySlice}; @@ -173,9 +174,18 @@ pub struct BankForksInfo { pub bank_slot: u64, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum BlocktreeProcessorError { - LedgerVerificationFailed, + FailedToLoadEntries, + FailedToLoadMeta, + InvalidBlock(BlockError), + InvalidTransaction, +} + +impl From for BlocktreeProcessorError { + fn from(block_error: BlockError) -> Self { + BlocktreeProcessorError::InvalidBlock(block_error) + } } /// Callback for accessing bank state while processing the blocktree @@ -277,7 +287,7 @@ pub fn process_blocktree_from_root( Ok((bank_forks, bank_forks_info, leader_schedule_cache)) } -fn verify_and_process_entries( +fn verify_and_process_slot_entries( bank: &Arc, entries: &[Entry], last_entry_hash: Hash, @@ -285,9 +295,34 @@ fn verify_and_process_entries( ) -> result::Result { assert!(!entries.is_empty()); - if opts.verify_ledger && !entries.verify(&last_entry_hash) { - warn!("Ledger proof of history failed at slot: {}", bank.slot()); - return Err(BlocktreeProcessorError::LedgerVerificationFailed); + if opts.verify_ledger { + let next_bank_tick_height = bank.tick_height() + entries.tick_count(); + let max_bank_tick_height = bank.max_tick_height(); + if next_bank_tick_height != max_bank_tick_height { + warn!( + "Invalid number of entry ticks found in slot: {}", + bank.slot() + ); + return Err(BlockError::InvalidTickCount.into()); + } else if !entries.last().unwrap().is_tick() { + warn!("Slot: {} did not end with a tick entry", bank.slot()); + return Err(BlockError::TrailingEntry.into()); + } + + if let Some(hashes_per_tick) = bank.hashes_per_tick() { + if !entries.verify_tick_hash_count(&mut 0, *hashes_per_tick) { + warn!( + "Tick with invalid number of hashes found in slot: {}", + bank.slot() + ); + return Err(BlockError::InvalidTickHashCount.into()); + } + } + + if !entries.verify(&last_entry_hash) { + warn!("Ledger proof of history failed at slot: {}", bank.slot()); + return Err(BlockError::InvalidEntryHash.into()); + } } process_entries_with_callback(bank, &entries, true, opts.entry_callback.as_ref()).map_err( @@ -297,7 +332,7 @@ fn verify_and_process_entries( bank.slot(), err ); - BlocktreeProcessorError::LedgerVerificationFailed + BlocktreeProcessorError::InvalidTransaction }, )?; @@ -315,15 +350,10 @@ fn process_bank_0( // Fetch all entries for this slot let entries = blocktree.get_slot_entries(0, 0, None).map_err(|err| { warn!("Failed to load entries for slot 0, err: {:?}", err); - BlocktreeProcessorError::LedgerVerificationFailed + BlocktreeProcessorError::FailedToLoadEntries })?; - if entries.is_empty() { - warn!("entry0 not present"); - return Err(BlocktreeProcessorError::LedgerVerificationFailed); - } - - verify_and_process_entries(bank0, &entries, bank0.last_blockhash(), opts)?; + verify_and_process_slot_entries(bank0, &entries, bank0.last_blockhash(), opts)?; bank0.freeze(); @@ -355,7 +385,7 @@ fn process_next_slots( .meta(*next_slot) .map_err(|err| { warn!("Failed to load meta for slot {}: {:?}", next_slot, err); - BlocktreeProcessorError::LedgerVerificationFailed + BlocktreeProcessorError::FailedToLoadMeta })? .unwrap(); @@ -419,10 +449,10 @@ fn process_pending_slots( // Fetch all entries for this slot let entries = blocktree.get_slot_entries(slot, 0, None).map_err(|err| { warn!("Failed to load entries for slot {}: {:?}", slot, err); - BlocktreeProcessorError::LedgerVerificationFailed + BlocktreeProcessorError::FailedToLoadEntries })?; - verify_and_process_entries(&bank, &entries, last_entry_hash, opts)?; + verify_and_process_slot_entries(&bank, &entries, last_entry_hash, opts)?; bank.freeze(); // all banks handled by this routine are created from complete slots @@ -463,7 +493,8 @@ pub fn fill_blocktree_slot_with_ticks( parent_slot: u64, last_entry_hash: Hash, ) -> Hash { - let entries = create_ticks(ticks_per_slot, last_entry_hash); + let num_slots = (slot - parent_slot).max(1); // Note: slot 0 has parent slot 0 + let entries = create_ticks(num_slots * ticks_per_slot, 0, last_entry_hash); let last_entry_hash = entries.last().unwrap().hash; blocktree @@ -486,7 +517,7 @@ pub fn fill_blocktree_slot_with_ticks( pub mod tests { use super::*; use crate::blocktree::create_new_tmp_ledger; - use crate::entry::{create_ticks, next_entry, next_entry_mut, Entry}; + use crate::entry::{create_ticks, next_entry, next_entry_mut}; use crate::genesis_utils::{ create_genesis_block, create_genesis_block_with_leader, GenesisBlockInfo, }; @@ -503,6 +534,140 @@ pub mod tests { }; use std::sync::RwLock; + #[test] + fn test_process_blocktree_with_missing_hashes() { + solana_logger::setup(); + + let hashes_per_tick = 2; + let GenesisBlockInfo { + mut genesis_block, .. + } = create_genesis_block(10_000); + genesis_block.poh_config.hashes_per_tick = Some(hashes_per_tick); + let ticks_per_slot = genesis_block.ticks_per_slot; + + let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block); + let blocktree = + Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger"); + + let parent_slot = 0; + let slot = 1; + let entries = create_ticks(ticks_per_slot, hashes_per_tick - 1, blockhash); + blocktree + .write_entries( + slot, + 0, + 0, + ticks_per_slot, + Some(parent_slot), + true, + &Arc::new(Keypair::new()), + entries, + ) + .expect("Expected to write shredded entries to blocktree"); + + let opts = ProcessOptions { + verify_ledger: true, + ..ProcessOptions::default() + }; + assert_eq!( + process_blocktree(&genesis_block, &blocktree, None, opts).err(), + Some(BlocktreeProcessorError::InvalidBlock( + BlockError::InvalidTickHashCount + )), + ); + } + + #[test] + fn test_process_blocktree_with_invalid_slot_tick_count() { + solana_logger::setup(); + + let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000); + let ticks_per_slot = genesis_block.ticks_per_slot; + + // Create a new ledger with slot 0 full of ticks + let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block); + let blocktree = Blocktree::open(&ledger_path).unwrap(); + + // Write slot 1 with one tick missing + let parent_slot = 0; + let slot = 1; + let entries = create_ticks(ticks_per_slot - 1, 0, blockhash); + blocktree + .write_entries( + slot, + 0, + 0, + ticks_per_slot, + Some(parent_slot), + true, + &Arc::new(Keypair::new()), + entries, + ) + .expect("Expected to write shredded entries to blocktree"); + + let opts = ProcessOptions { + verify_ledger: true, + ..ProcessOptions::default() + }; + assert_eq!( + process_blocktree(&genesis_block, &blocktree, None, opts).err(), + Some(BlocktreeProcessorError::InvalidBlock( + BlockError::InvalidTickCount + )), + ); + } + + #[test] + fn test_process_blocktree_with_slot_with_trailing_entry() { + solana_logger::setup(); + + let GenesisBlockInfo { + mint_keypair, + genesis_block, + .. + } = create_genesis_block(10_000); + let ticks_per_slot = genesis_block.ticks_per_slot; + + let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block); + let blocktree = Blocktree::open(&ledger_path).unwrap(); + + let mut entries = create_ticks(ticks_per_slot, 0, blockhash); + let trailing_entry = { + let keypair = Keypair::new(); + let tx = system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 1, blockhash); + next_entry(&blockhash, 1, vec![tx]) + }; + entries.push(trailing_entry); + + // Tricks blocktree into writing the trailing entry by lying that there is one more tick + // per slot. + let parent_slot = 0; + let slot = 1; + blocktree + .write_entries( + slot, + 0, + 0, + ticks_per_slot + 1, + Some(parent_slot), + true, + &Arc::new(Keypair::new()), + entries, + ) + .expect("Expected to write shredded entries to blocktree"); + + let opts = ProcessOptions { + verify_ledger: true, + ..ProcessOptions::default() + }; + assert_eq!( + process_blocktree(&genesis_block, &blocktree, None, opts).err(), + Some(BlocktreeProcessorError::InvalidBlock( + BlockError::TrailingEntry + )), + ); + } + #[test] fn test_process_blocktree_with_incomplete_slot() { solana_logger::setup(); @@ -534,7 +699,7 @@ pub mod tests { { let parent_slot = 0; let slot = 1; - let mut entries = create_ticks(ticks_per_slot, blockhash); + let mut entries = create_ticks(ticks_per_slot, 0, blockhash); blockhash = entries.last().unwrap().hash; // throw away last one @@ -841,7 +1006,7 @@ pub mod tests { } = create_genesis_block(2); let bank = Arc::new(Bank::new(&genesis_block)); let keypair = Keypair::new(); - let slot_entries = create_ticks(genesis_block.ticks_per_slot, genesis_block.hash()); + let slot_entries = create_ticks(genesis_block.ticks_per_slot, 1, genesis_block.hash()); let tx = system_transaction::transfer( &mint_keypair, &keypair.pubkey(), @@ -865,11 +1030,13 @@ pub mod tests { solana_logger::setup(); let leader_pubkey = Pubkey::new_rand(); let mint = 100; + let hashes_per_tick = 10; let GenesisBlockInfo { - genesis_block, + mut genesis_block, mint_keypair, .. } = create_genesis_block_with_leader(mint, &leader_pubkey, 50); + genesis_block.poh_config.hashes_per_tick = Some(hashes_per_tick); let (ledger_path, mut last_entry_hash) = create_new_tmp_ledger!(&genesis_block); debug!("ledger_path: {:?}", ledger_path); @@ -880,8 +1047,7 @@ pub mod tests { // Transfer one token from the mint to a random account let keypair = Keypair::new(); let tx = system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 1, blockhash); - let entry = Entry::new(&last_entry_hash, 1, vec![tx]); - last_entry_hash = entry.hash; + let entry = next_entry_mut(&mut last_entry_hash, 1, vec![tx]); entries.push(entry); // Add a second Transaction that will produce a @@ -889,14 +1055,22 @@ pub mod tests { let keypair2 = Keypair::new(); let tx = system_transaction::transfer(&mint_keypair, &keypair2.pubkey(), 101, blockhash); - let entry = Entry::new(&last_entry_hash, 1, vec![tx]); - last_entry_hash = entry.hash; + let entry = next_entry_mut(&mut last_entry_hash, 1, vec![tx]); entries.push(entry); } + let remaining_hashes = hashes_per_tick - entries.len() as u64; + let tick_entry = next_entry_mut(&mut last_entry_hash, remaining_hashes, vec![]); + entries.push(tick_entry); + // Fill up the rest of slot 1 with ticks - entries.extend(create_ticks(genesis_block.ticks_per_slot, last_entry_hash)); + entries.extend(create_ticks( + genesis_block.ticks_per_slot - 1, + genesis_block.poh_config.hashes_per_tick.unwrap(), + last_entry_hash, + )); let last_blockhash = entries.last().unwrap().hash; + let blocktree = Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger"); blocktree @@ -1004,7 +1178,11 @@ pub mod tests { let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let mut entries = vec![entry_1, entry_2]; - entries.extend(create_ticks(genesis_block.ticks_per_slot, last_entry_hash)); + entries.extend(create_ticks( + genesis_block.ticks_per_slot, + 0, + last_entry_hash, + )); blocktree .write_entries( 1, @@ -1683,7 +1861,8 @@ pub mod tests { let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1)); bank1.squash(); let slot1_entries = blocktree.get_slot_entries(1, 0, None).unwrap(); - verify_and_process_entries(&bank1, &slot1_entries, bank0.last_blockhash(), &opts).unwrap(); + verify_and_process_slot_entries(&bank1, &slot1_entries, bank0.last_blockhash(), &opts) + .unwrap(); // Test process_blocktree_from_root() from slot 1 onwards let (bank_forks, bank_forks_info, _) = diff --git a/ledger/src/entry.rs b/ledger/src/entry.rs index 7960167233..7b24fbc603 100644 --- a/ledger/src/entry.rs +++ b/ledger/src/entry.rs @@ -61,32 +61,18 @@ pub struct Entry { impl Entry { /// Creates the next Entry `num_hashes` after `start_hash`. - pub fn new(prev_hash: &Hash, num_hashes: u64, transactions: Vec) -> Self { - 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 new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec) -> Self { + // If you passed in transactions, but passed in num_hashes == 0, then + // next_hash will generate the next hash and set num_hashes == 1 + if num_hashes == 0 && !transactions.is_empty() { + num_hashes = 1; + } + + let hash = next_hash(prev_hash, num_hashes, &transactions); + Entry { + num_hashes, + hash, + transactions, } } @@ -219,6 +205,12 @@ pub trait EntrySlice { fn verify_cpu(&self, start_hash: &Hash) -> EntryVerifyState; fn start_verify(&self, start_hash: &Hash) -> EntryVerifyState; fn verify(&self, start_hash: &Hash) -> bool; + /// Checks that each entry tick has the correct number of hashes. Entry slices do not + /// necessarily end in a tick, so `tick_hash_count` is used to carry over the hash count + /// for the next entry slice. + fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool; + /// Counts tick entries + fn tick_count(&self) -> u64; } impl EntrySlice for [Entry] { @@ -338,6 +330,34 @@ impl EntrySlice for [Entry] { hashes: Some(hashes), } } + + fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool { + // When hashes_per_tick is 0, hashing is disabled. + if hashes_per_tick == 0 { + return true; + } + + for entry in self { + *tick_hash_count += entry.num_hashes; + if entry.is_tick() { + if *tick_hash_count != hashes_per_tick { + warn!( + "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}", + entry, + tick_hash_count, + hashes_per_tick + ); + return false; + } + *tick_hash_count = 0; + } + } + *tick_hash_count < hashes_per_tick + } + + fn tick_count(&self) -> u64 { + self.iter().filter(|e| e.is_tick()).count() as u64 + } } pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec) -> Entry { @@ -346,10 +366,10 @@ pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec Vec { +pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec { 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![]); + let new_tick = next_entry_mut(&mut hash, hashes_per_tick, vec![]); ticks.push(new_tick); } @@ -373,9 +393,11 @@ mod tests { use chrono::prelude::Utc; use solana_budget_api::budget_instruction; use solana_sdk::{ - hash::hash, + hash::{hash, Hash}, + message::Message, signature::{Keypair, KeypairUtil}, system_transaction, + transaction::Transaction, }; fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction { @@ -528,4 +550,58 @@ mod tests { bad_ticks[1].hash = one; assert!(!bad_ticks.verify(&one)); // inductive step, bad } + + #[test] + fn test_verify_tick_hash_count() { + let hashes_per_tick = 10; + let keypairs: Vec<&Keypair> = Vec::new(); + let tx: Transaction = + Transaction::new(&keypairs, Message::new(Vec::new()), Hash::default()); + let tx_entry = Entry::new(&Hash::default(), 1, vec![tx]); + let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default()); + let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default()); + let no_hash_tick_entry = Entry::new_tick(0, &Hash::default()); + let single_hash_tick_entry = Entry::new_tick(1, &Hash::default()); + + let no_ticks = vec![]; + let mut tick_hash_count = 0; + assert!(no_ticks.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, 0); + + // validation is disabled when hashes_per_tick == 0 + let no_hash_tick = vec![no_hash_tick_entry.clone()]; + assert!(no_hash_tick.verify_tick_hash_count(&mut tick_hash_count, 0)); + assert_eq!(tick_hash_count, 0); + + // validation is disabled when hashes_per_tick == 0 + let tx_and_no_hash_tick = vec![tx_entry.clone(), no_hash_tick_entry]; + assert!(tx_and_no_hash_tick.verify_tick_hash_count(&mut tick_hash_count, 0)); + assert_eq!(tick_hash_count, 0); + + let single_tick = vec![full_tick_entry.clone()]; + assert!(single_tick.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, 0); + assert!(!single_tick.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1)); + assert_eq!(tick_hash_count, hashes_per_tick); + tick_hash_count = 0; + + let ticks_and_txs = vec![tx_entry.clone(), partial_tick_entry.clone()]; + assert!(ticks_and_txs.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, 0); + + let partial_tick = vec![partial_tick_entry.clone()]; + assert!(!partial_tick.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, hashes_per_tick - 1); + tick_hash_count = 0; + + let tx_entries: Vec = (0..hashes_per_tick - 1).map(|_| tx_entry.clone()).collect(); + let tx_entries_and_tick = [tx_entries, vec![single_hash_tick_entry]].concat(); + assert!(tx_entries_and_tick.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, 0); + + let too_many_tx_entries: Vec = + (0..hashes_per_tick).map(|_| tx_entry.clone()).collect(); + assert!(!too_many_tx_entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick)); + assert_eq!(tick_hash_count, hashes_per_tick); + } } diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index fa0c064ecf..c64a98dec9 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -1,4 +1,5 @@ pub mod bank_forks; +pub mod block_error; #[macro_use] pub mod blocktree; mod blocktree_db; diff --git a/ledger/src/shred.rs b/ledger/src/shred.rs index 539200c07c..a39cb99f1a 100644 --- a/ledger/src/shred.rs +++ b/ledger/src/shred.rs @@ -715,7 +715,7 @@ impl Shredder { } pub fn max_ticks_per_n_shreds(num_shreds: u64) -> u64 { - let ticks = create_ticks(1, Hash::default()); + let ticks = create_ticks(1, 0, Hash::default()); max_entries_per_n_shred(&ticks[0], num_shreds) } diff --git a/ledger/tests/blocktree.rs b/ledger/tests/blocktree.rs index 87dfa3e05f..b996a22add 100644 --- a/ledger/tests/blocktree.rs +++ b/ledger/tests/blocktree.rs @@ -19,7 +19,7 @@ fn test_multiple_threads_insert_shred() { // with parent = slot 0 let threads: Vec<_> = (0..num_threads) .map(|i| { - let entries = entry::create_ticks(1, Hash::default()); + let entries = entry::create_ticks(1, 0, Hash::default()); let shreds = blocktree::entries_to_test_shreds(entries, i + 1, 0, false); let blocktree_ = blocktree.clone(); Builder::new() diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 22e9be0a6d..ae195f4197 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -197,6 +197,9 @@ pub struct Bank { // Bank max_tick_height max_tick_height: u64, + /// The number of hashes in each tick. None value means hashing is disabled. + hashes_per_tick: Option, + /// The number of ticks in each slot. ticks_per_slot: u64, @@ -319,6 +322,7 @@ impl Bank { blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()), // TODO: clean this up, soo much special-case copying... + hashes_per_tick: parent.hashes_per_tick, ticks_per_slot: parent.ticks_per_slot, slots_per_segment: parent.slots_per_segment, slots_per_year: parent.slots_per_year, @@ -662,6 +666,7 @@ impl Bank { .unwrap() .genesis_hash(&genesis_block.hash(), &self.fee_calculator); + self.hashes_per_tick = genesis_block.poh_config.hashes_per_tick; self.ticks_per_slot = genesis_block.ticks_per_slot; self.slots_per_segment = genesis_block.slots_per_segment; self.max_tick_height = (self.slot + 1) * self.ticks_per_slot; @@ -1421,6 +1426,11 @@ impl Bank { ) } + /// Return the number of hashes per tick + pub fn hashes_per_tick(&self) -> &Option { + &self.hashes_per_tick + } + /// Return the number of ticks per slot pub fn ticks_per_slot(&self) -> u64 { self.ticks_per_slot @@ -1564,6 +1574,7 @@ impl Bank { assert_eq!(self.slot, dbank.slot); assert_eq!(self.collector_id, dbank.collector_id); assert_eq!(self.epoch_schedule, dbank.epoch_schedule); + assert_eq!(self.hashes_per_tick, dbank.hashes_per_tick); assert_eq!(self.ticks_per_slot, dbank.ticks_per_slot); assert_eq!(self.parent_hash, dbank.parent_hash); assert_eq!(