Remove special handling of first ledger tick (#6263)

* Remove special handling of first ledger tick

* Fix subtraction overflow

* @garious feedback

* Back to height

* More tick_height name changes

* Fix off-by-one

* Fix leader tick error

* Fix merge conflict

* Fix recently added test
This commit is contained in:
Justin Starry
2019-10-16 15:53:11 -04:00
committed by GitHub
parent e267dfacdd
commit 7e6e7e8406
13 changed files with 185 additions and 178 deletions

View File

@ -41,7 +41,7 @@ fn check_txs(
let now = Instant::now(); let now = Instant::now();
let mut no_bank = false; let mut no_bank = false;
loop { loop {
if let Ok((_bank, (entry, _tick_count))) = receiver.recv_timeout(Duration::from_millis(10)) if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::from_millis(10))
{ {
total += entry.transactions.len(); total += entry.transactions.len();
} }

View File

@ -1055,7 +1055,7 @@ mod tests {
.map(|(_bank, (entry, _tick_height))| entry) .map(|(_bank, (entry, _tick_height))| entry)
.collect(); .collect();
trace!("done"); trace!("done");
assert_eq!(entries.len(), genesis_block.ticks_per_slot as usize - 1); assert_eq!(entries.len(), genesis_block.ticks_per_slot as usize);
assert!(entries.verify(&start_hash)); assert!(entries.verify(&start_hash));
assert_eq!(entries[entries.len() - 1].hash, bank.last_blockhash()); assert_eq!(entries[entries.len() - 1].hash, bank.last_blockhash());
banking_stage.join().unwrap(); banking_stage.join().unwrap();

View File

@ -215,7 +215,7 @@ mod test {
let mut entries = Vec::new(); let mut entries = Vec::new();
let mut expected_entries = Vec::new(); let mut expected_entries = Vec::new();
let tick_height_initial = 0; let tick_height_initial = 1;
let tick_height_final = tick_height_initial + ticks_per_slot + 2; let tick_height_final = tick_height_initial + ticks_per_slot + 2;
let mut curr_slot = 0; let mut curr_slot = 0;
let leader_pubkey = Pubkey::new_rand(); let leader_pubkey = Pubkey::new_rand();
@ -223,7 +223,7 @@ mod test {
for tick_height in tick_height_initial..=tick_height_final { for tick_height in tick_height_initial..=tick_height_final {
if tick_height == 5 { if tick_height == 5 {
blockstream blockstream
.emit_block_event(curr_slot, tick_height - 1, &leader_pubkey, blockhash) .emit_block_event(curr_slot, tick_height, &leader_pubkey, blockhash)
.unwrap(); .unwrap();
curr_slot += 1; curr_slot += 1;
} }
@ -238,7 +238,7 @@ mod test {
assert_eq!( assert_eq!(
blockstream.entries().len() as u64, blockstream.entries().len() as u64,
// one entry per tick (0..=N+2) is +3, plus one block // one entry per tick (1..=N+2) is +3, plus one block
ticks_per_slot + 3 + 1 ticks_per_slot + 3 + 1
); );

View File

@ -66,15 +66,8 @@ impl BlockstreamService {
} else { } else {
Some(blocktree_meta.parent_slot) Some(blocktree_meta.parent_slot)
}; };
let ticks_per_slot = entries let ticks_per_slot = entries.iter().filter(|entry| entry.is_tick()).count() as u64;
.iter() let mut tick_height = ticks_per_slot * slot;
.filter(|entry| entry.is_tick())
.fold(0, |acc, _| acc + 1);
let mut tick_height = if slot > 0 && ticks_per_slot > 0 {
ticks_per_slot * slot - 1
} else {
0
};
for (i, entry) in entries.iter().enumerate() { for (i, entry) in entries.iter().enumerate() {
if entry.is_tick() { if entry.is_tick() {
@ -158,7 +151,7 @@ mod test {
entries.extend_from_slice(&final_tick); entries.extend_from_slice(&final_tick);
let expected_entries = entries.clone(); let expected_entries = entries.clone();
let expected_tick_heights = [5, 6, 7, 8, 8, 9]; let expected_tick_heights = [6, 7, 8, 9, 9, 10];
blocktree blocktree
.write_entries( .write_entries(
@ -234,7 +227,7 @@ mod test {
let slot = json["s"].as_u64().unwrap(); let slot = json["s"].as_u64().unwrap();
assert_eq!(1, slot); assert_eq!(1, slot);
let height = json["h"].as_u64().unwrap(); let height = json["h"].as_u64().unwrap();
assert_eq!(2 * ticks_per_slot - 1, height); assert_eq!(2 * ticks_per_slot, height);
} }
} }
} }

View File

@ -304,29 +304,17 @@ fn process_bank_0(
assert_eq!(bank0.slot(), 0); assert_eq!(bank0.slot(), 0);
// Fetch all entries for this slot // Fetch all entries for this slot
let mut entries = blocktree.get_slot_entries(0, 0, None).map_err(|err| { let entries = blocktree.get_slot_entries(0, 0, None).map_err(|err| {
warn!("Failed to load entries for slot 0, err: {:?}", err); warn!("Failed to load entries for slot 0, err: {:?}", err);
BlocktreeProcessorError::LedgerVerificationFailed BlocktreeProcessorError::LedgerVerificationFailed
})?; })?;
// The first entry in the ledger is a pseudo-tick used only to ensure the number of ticks
// in slot 0 is the same as the number of ticks in all subsequent slots. It is not
// processed by the bank, skip over it.
if entries.is_empty() { if entries.is_empty() {
warn!("entry0 not present"); warn!("entry0 not present");
return Err(BlocktreeProcessorError::LedgerVerificationFailed); return Err(BlocktreeProcessorError::LedgerVerificationFailed);
} }
let entry0 = entries.remove(0);
if !(entry0.is_tick() && entry0.verify(&bank0.last_blockhash())) {
warn!("Ledger proof of history failed at entry0");
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
if !entries.is_empty() { verify_and_process_entries(bank0, &entries, bank0.last_blockhash(), opts)?;
verify_and_process_entries(bank0, &entries, entry0.hash, opts)?;
} else {
bank0.register_tick(&entry0.hash);
}
bank0.freeze(); bank0.freeze();
@ -843,7 +831,7 @@ pub mod tests {
} = create_genesis_block(2); } = create_genesis_block(2);
let bank = Arc::new(Bank::new(&genesis_block)); let bank = Arc::new(Bank::new(&genesis_block));
let keypair = Keypair::new(); let keypair = Keypair::new();
let slot_entries = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash()); let slot_entries = create_ticks(genesis_block.ticks_per_slot, genesis_block.hash());
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
&mint_keypair, &mint_keypair,
&keypair.pubkey(), &keypair.pubkey(),
@ -938,7 +926,7 @@ pub mod tests {
bank.get_balance(&mint_keypair.pubkey()), bank.get_balance(&mint_keypair.pubkey()),
mint - deducted_from_mint mint - deducted_from_mint
); );
assert_eq!(bank.tick_height(), 2 * genesis_block.ticks_per_slot - 1); assert_eq!(bank.tick_height(), 2 * genesis_block.ticks_per_slot);
assert_eq!(bank.last_blockhash(), last_blockhash); assert_eq!(bank.last_blockhash(), last_blockhash);
} }

View File

@ -28,7 +28,7 @@ impl BroadcastRun for BroadcastFakeBlobsRun {
// 1) Pull entries from banking stage // 1) Pull entries from banking stage
let receive_results = broadcast_utils::recv_slot_entries(receiver)?; let receive_results = broadcast_utils::recv_slot_entries(receiver)?;
let bank = receive_results.bank.clone(); let bank = receive_results.bank.clone();
let last_tick = receive_results.last_tick; let last_tick_height = receive_results.last_tick_height;
let keypair = &cluster_info.read().unwrap().keypair.clone(); let keypair = &cluster_info.read().unwrap().keypair.clone();
let next_shred_index = blocktree let next_shred_index = blocktree
@ -49,7 +49,7 @@ impl BroadcastRun for BroadcastFakeBlobsRun {
let (data_shreds, coding_shreds, _) = shredder.entries_to_shreds( let (data_shreds, coding_shreds, _) = shredder.entries_to_shreds(
&receive_results.entries, &receive_results.entries,
last_tick == bank.max_tick_height(), last_tick_height == bank.max_tick_height(),
next_shred_index, next_shred_index,
); );
@ -65,13 +65,13 @@ impl BroadcastRun for BroadcastFakeBlobsRun {
let (fake_data_shreds, fake_coding_shreds, _) = shredder.entries_to_shreds( let (fake_data_shreds, fake_coding_shreds, _) = shredder.entries_to_shreds(
&fake_entries, &fake_entries,
last_tick == bank.max_tick_height(), last_tick_height == bank.max_tick_height(),
next_shred_index, next_shred_index,
); );
// If it's the last tick, reset the last block hash to default // If it's the last tick, reset the last block hash to default
// this will cause next run to grab last bank's blockhash // this will cause next run to grab last bank's blockhash
if last_tick == bank.max_tick_height() { if last_tick_height == bank.max_tick_height() {
self.last_blockhash = Hash::default(); self.last_blockhash = Hash::default();
} }

View File

@ -10,7 +10,7 @@ pub(super) struct ReceiveResults {
pub entries: Vec<Entry>, pub entries: Vec<Entry>,
pub time_elapsed: Duration, pub time_elapsed: Duration,
pub bank: Arc<Bank>, pub bank: Arc<Bank>,
pub last_tick: u64, pub last_tick_height: u64,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -20,7 +20,7 @@ pub struct UnfinishedSlotInfo {
pub parent: u64, pub parent: u64,
} }
/// Theis parameter tunes how many entries are received in one iteration of recv loop /// This parameter tunes how many entries are received in one iteration of recv loop
/// This will prevent broadcast stage from consuming more entries, that could have led /// This will prevent broadcast stage from consuming more entries, that could have led
/// to delays in shredding, and broadcasting shreds to peer validators /// to delays in shredding, and broadcasting shreds to peer validators
const RECEIVE_ENTRY_COUNT_THRESHOLD: usize = 8; const RECEIVE_ENTRY_COUNT_THRESHOLD: usize = 8;
@ -28,15 +28,15 @@ const RECEIVE_ENTRY_COUNT_THRESHOLD: usize = 8;
pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result<ReceiveResults> { pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result<ReceiveResults> {
let timer = Duration::new(1, 0); let timer = Duration::new(1, 0);
let recv_start = Instant::now(); let recv_start = Instant::now();
let (mut bank, (entry, mut last_tick)) = receiver.recv_timeout(timer)?; let (mut bank, (entry, mut last_tick_height)) = receiver.recv_timeout(timer)?;
let mut entries = vec![entry]; let mut entries = vec![entry];
let mut slot = bank.slot(); let mut slot = bank.slot();
let mut max_tick_height = bank.max_tick_height(); let mut max_tick_height = bank.max_tick_height();
assert!(last_tick <= max_tick_height); assert!(last_tick_height <= max_tick_height);
if last_tick != max_tick_height { if last_tick_height != max_tick_height {
while let Ok((try_bank, (entry, tick_height))) = receiver.try_recv() { while let Ok((try_bank, (entry, tick_height))) = receiver.try_recv() {
// If the bank changed, that implies the previous slot was interrupted and we do not have to // If the bank changed, that implies the previous slot was interrupted and we do not have to
// broadcast its entries. // broadcast its entries.
@ -47,15 +47,15 @@ pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result
slot = bank.slot(); slot = bank.slot();
max_tick_height = bank.max_tick_height(); max_tick_height = bank.max_tick_height();
} }
last_tick = tick_height; last_tick_height = tick_height;
entries.push(entry); entries.push(entry);
if entries.len() >= RECEIVE_ENTRY_COUNT_THRESHOLD { if entries.len() >= RECEIVE_ENTRY_COUNT_THRESHOLD {
break; break;
} }
assert!(last_tick <= max_tick_height); assert!(last_tick_height <= max_tick_height);
if last_tick == max_tick_height { if last_tick_height == max_tick_height {
break; break;
} }
} }
@ -66,7 +66,7 @@ pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result
entries, entries,
time_elapsed, time_elapsed,
bank, bank,
last_tick, last_tick_height,
}) })
} }
@ -106,7 +106,7 @@ mod tests {
let mut last_hash = genesis_block.hash(); let mut last_hash = genesis_block.hash();
assert!(bank1.max_tick_height() > 1); assert!(bank1.max_tick_height() > 1);
let entries: Vec<_> = (0..bank1.max_tick_height() + 1) let entries: Vec<_> = (1..bank1.max_tick_height() + 1)
.map(|i| { .map(|i| {
let entry = Entry::new(&last_hash, 1, vec![tx.clone()]); let entry = Entry::new(&last_hash, 1, vec![tx.clone()]);
last_hash = entry.hash; last_hash = entry.hash;
@ -118,7 +118,7 @@ mod tests {
let result = recv_slot_entries(&r).unwrap(); let result = recv_slot_entries(&r).unwrap();
assert_eq!(result.bank.slot(), bank1.slot()); assert_eq!(result.bank.slot(), bank1.slot());
assert_eq!(result.last_tick, bank1.max_tick_height()); assert_eq!(result.last_tick_height, bank1.max_tick_height());
assert_eq!(result.entries, entries); assert_eq!(result.entries, entries);
} }
@ -133,17 +133,18 @@ mod tests {
let mut last_hash = genesis_block.hash(); let mut last_hash = genesis_block.hash();
assert!(bank1.max_tick_height() > 1); assert!(bank1.max_tick_height() > 1);
// Simulate slot 2 interrupting slot 1's transmission // Simulate slot 2 interrupting slot 1's transmission
let expected_last_index = bank1.max_tick_height() - 1; let expected_last_height = bank1.max_tick_height();
let last_entry = (0..bank1.max_tick_height()) let last_entry = (1..=bank1.max_tick_height())
.map(|i| { .map(|tick_height| {
let entry = Entry::new(&last_hash, 1, vec![tx.clone()]); let entry = Entry::new(&last_hash, 1, vec![tx.clone()]);
last_hash = entry.hash; last_hash = entry.hash;
// Interrupt slot 1 right before the last tick // Interrupt slot 1 right before the last tick
if i == expected_last_index { if tick_height == expected_last_height {
s.send((bank2.clone(), (entry.clone(), i))).unwrap(); s.send((bank2.clone(), (entry.clone(), tick_height)))
.unwrap();
Some(entry) Some(entry)
} else { } else {
s.send((bank1.clone(), (entry, i))).unwrap(); s.send((bank1.clone(), (entry, tick_height))).unwrap();
None None
} }
}) })
@ -153,7 +154,7 @@ mod tests {
let result = recv_slot_entries(&r).unwrap(); let result = recv_slot_entries(&r).unwrap();
assert_eq!(result.bank.slot(), bank2.slot()); assert_eq!(result.bank.slot(), bank2.slot());
assert_eq!(result.last_tick, expected_last_index); assert_eq!(result.last_tick_height, expected_last_height);
assert_eq!(result.entries, vec![last_entry]); assert_eq!(result.entries, vec![last_entry]);
} }
} }

View File

@ -21,11 +21,11 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
// 1) Pull entries from banking stage // 1) Pull entries from banking stage
let mut receive_results = broadcast_utils::recv_slot_entries(receiver)?; let mut receive_results = broadcast_utils::recv_slot_entries(receiver)?;
let bank = receive_results.bank.clone(); let bank = receive_results.bank.clone();
let last_tick = receive_results.last_tick; let last_tick_height = receive_results.last_tick_height;
// 2) Convert entries to blobs + generate coding blobs. Set a garbage PoH on the last entry // 2) Convert entries to blobs + generate coding blobs. Set a garbage PoH on the last entry
// in the slot to make verification fail on validators // in the slot to make verification fail on validators
if last_tick == bank.max_tick_height() { if last_tick_height == bank.max_tick_height() {
let mut last_entry = receive_results.entries.last_mut().unwrap(); let mut last_entry = receive_results.entries.last_mut().unwrap();
last_entry.hash = Hash::default(); last_entry.hash = Hash::default();
} }
@ -47,7 +47,7 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
let (data_shreds, coding_shreds, _) = shredder.entries_to_shreds( let (data_shreds, coding_shreds, _) = shredder.entries_to_shreds(
&receive_results.entries, &receive_results.entries,
last_tick == bank.max_tick_height(), last_tick_height == bank.max_tick_height(),
next_shred_index, next_shred_index,
); );

View File

@ -144,7 +144,7 @@ impl StandardBroadcastRun {
let mut receive_elapsed = receive_results.time_elapsed; let mut receive_elapsed = receive_results.time_elapsed;
let num_entries = receive_results.entries.len(); let num_entries = receive_results.entries.len();
let bank = receive_results.bank.clone(); let bank = receive_results.bank.clone();
let last_tick = receive_results.last_tick; let last_tick_height = receive_results.last_tick_height;
inc_new_counter_info!("broadcast_service-entries_received", num_entries); inc_new_counter_info!("broadcast_service-entries_received", num_entries);
if self.current_slot_and_parent.is_none() if self.current_slot_and_parent.is_none()
@ -173,7 +173,7 @@ impl StandardBroadcastRun {
let (data_shreds, coding_shreds) = self.entries_to_shreds( let (data_shreds, coding_shreds) = self.entries_to_shreds(
blocktree, blocktree,
&receive_results.entries, &receive_results.entries,
last_tick == bank.max_tick_height(), last_tick_height == bank.max_tick_height(),
); );
let to_shreds_elapsed = to_shreds_start.elapsed(); let to_shreds_elapsed = to_shreds_start.elapsed();
@ -214,10 +214,10 @@ impl StandardBroadcastRun {
duration_as_us(&insert_shreds_elapsed), duration_as_us(&insert_shreds_elapsed),
duration_as_us(&broadcast_elapsed), duration_as_us(&broadcast_elapsed),
duration_as_us(&clone_and_seed_elapsed), duration_as_us(&clone_and_seed_elapsed),
last_tick == bank.max_tick_height(), last_tick_height == bank.max_tick_height(),
); );
if last_tick == bank.max_tick_height() { if last_tick_height == bank.max_tick_height() {
self.unfinished_slot = None; self.unfinished_slot = None;
} }
@ -353,7 +353,7 @@ mod test {
entries: ticks.clone(), entries: ticks.clone(),
time_elapsed: Duration::new(3, 0), time_elapsed: Duration::new(3, 0),
bank: bank0.clone(), bank: bank0.clone(),
last_tick: (ticks.len() - 1) as u64, last_tick_height: (ticks.len() - 1) as u64,
}; };
// Step 1: Make an incomplete transmission for slot 0 // Step 1: Make an incomplete transmission for slot 0
@ -391,7 +391,7 @@ mod test {
entries: ticks.clone(), entries: ticks.clone(),
time_elapsed: Duration::new(2, 0), time_elapsed: Duration::new(2, 0),
bank: bank2.clone(), bank: bank2.clone(),
last_tick: (ticks.len() - 1) as u64, last_tick_height: (ticks.len() - 1) as u64,
}; };
standard_broadcast_run standard_broadcast_run
.process_receive_results(&cluster_info, &socket, &blocktree, receive_results) .process_receive_results(&cluster_info, &socket, &blocktree, receive_results)
@ -420,7 +420,7 @@ mod test {
entries: ticks.clone(), entries: ticks.clone(),
time_elapsed: Duration::new(3, 0), time_elapsed: Duration::new(3, 0),
bank: bank0.clone(), bank: bank0.clone(),
last_tick: (ticks.len() - 1) as u64, last_tick_height: ticks.len() as u64,
}; };
let mut standard_broadcast_run = StandardBroadcastRun::new(leader_keypair); let mut standard_broadcast_run = StandardBroadcastRun::new(leader_keypair);

View File

@ -30,7 +30,7 @@ pub fn slot_leader_at(slot: u64, bank: &Bank) -> Option<Pubkey> {
// Returns the number of ticks remaining from the specified tick_height to the end of the // Returns the number of ticks remaining from the specified tick_height to the end of the
// slot implied by the tick_height // slot implied by the tick_height
pub fn num_ticks_left_in_slot(bank: &Bank, tick_height: u64) -> u64 { pub fn num_ticks_left_in_slot(bank: &Bank, tick_height: u64) -> u64 {
bank.ticks_per_slot() - tick_height % bank.ticks_per_slot() - 1 bank.ticks_per_slot() - tick_height % bank.ticks_per_slot()
} }
fn sort_stakes(stakes: &mut Vec<(Pubkey, u64)>) { fn sort_stakes(stakes: &mut Vec<(Pubkey, u64)>) {

View File

@ -5,7 +5,7 @@
//! within the specified WorkingBank range. //! within the specified WorkingBank range.
//! //!
//! For Ticks: //! For Ticks:
//! * tick must be > WorkingBank::min_tick_height && tick must be <= WorkingBank::max_tick_height //! * new tick_height must be > WorkingBank::min_tick_height && new tick_height must be <= WorkingBank::max_tick_height
//! //!
//! For Entries: //! For Entries:
//! * recorded entry must be >= WorkingBank::min_tick_height && entry must be < WorkingBank::max_tick_height //! * recorded entry must be >= WorkingBank::min_tick_height && entry must be < WorkingBank::max_tick_height
@ -52,12 +52,12 @@ pub struct PohRecorder {
tick_height: u64, tick_height: u64,
clear_bank_signal: Option<SyncSender<bool>>, clear_bank_signal: Option<SyncSender<bool>>,
start_slot: Slot, // parent slot start_slot: Slot, // parent slot
start_tick: u64, // first tick this recorder will observe start_tick_height: u64, // first tick_height this recorder will observe
tick_cache: Vec<(Entry, u64)>, tick_cache: Vec<(Entry, u64)>, // cache of entry and its tick_height
working_bank: Option<WorkingBank>, working_bank: Option<WorkingBank>,
sender: Sender<WorkingBankEntry>, sender: Sender<WorkingBankEntry>,
start_leader_at_tick: Option<u64>, leader_first_tick_height: Option<u64>,
last_leader_tick: u64, // zero if none leader_last_tick_height: u64, // zero if none
grace_ticks: u64, grace_ticks: u64,
id: Pubkey, id: Pubkey,
blocktree: Arc<Blocktree>, blocktree: Arc<Blocktree>,
@ -77,11 +77,11 @@ impl PohRecorder {
Some(&self.blocktree), Some(&self.blocktree),
); );
assert_eq!(self.ticks_per_slot, bank.ticks_per_slot()); assert_eq!(self.ticks_per_slot, bank.ticks_per_slot());
let (start_leader_at_tick, last_leader_tick, grace_ticks) = let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_ticks(next_leader_slot, self.ticks_per_slot); Self::compute_leader_slot_tick_heights(next_leader_slot, self.ticks_per_slot);
self.grace_ticks = grace_ticks; self.grace_ticks = grace_ticks;
self.start_leader_at_tick = start_leader_at_tick; self.leader_first_tick_height = leader_first_tick_height;
self.last_leader_tick = last_leader_tick; self.leader_last_tick_height = leader_last_tick_height;
} }
if let Some(ref signal) = self.clear_bank_signal { if let Some(ref signal) = self.clear_bank_signal {
let _ = signal.try_send(true); let _ = signal.try_send(true);
@ -90,17 +90,20 @@ impl PohRecorder {
pub fn would_be_leader(&self, within_next_n_ticks: u64) -> bool { pub fn would_be_leader(&self, within_next_n_ticks: u64) -> bool {
self.has_bank() self.has_bank()
|| self.start_leader_at_tick.map_or(false, |leader_tick| { || self
let ideal_leader_tick = leader_tick.saturating_sub(self.grace_ticks); .leader_first_tick_height
.map_or(false, |leader_first_tick_height| {
self.tick_height <= self.last_leader_tick let ideal_leader_tick_height =
&& self.tick_height >= ideal_leader_tick.saturating_sub(within_next_n_ticks) leader_first_tick_height.saturating_sub(self.grace_ticks);
self.tick_height + within_next_n_ticks >= ideal_leader_tick_height
&& self.tick_height <= self.leader_last_tick_height
}) })
} }
pub fn leader_after_slots(&self, slots: u64) -> Option<Pubkey> { pub fn leader_after_slots(&self, slots: u64) -> Option<Pubkey> {
let current_slot = self.tick_height.saturating_sub(1) / self.ticks_per_slot;
self.leader_schedule_cache self.leader_schedule_cache
.slot_leader_at(self.tick_height / self.ticks_per_slot + slots, None) .slot_leader_at(current_slot + slots, None)
} }
pub fn next_slot_leader(&self) -> Option<Pubkey> { pub fn next_slot_leader(&self) -> Option<Pubkey> {
@ -123,53 +126,61 @@ impl PohRecorder {
self.ticks_per_slot self.ticks_per_slot
} }
/// returns if leader tick has reached, how many grace ticks were afforded, /// returns if leader slot has been reached, how many grace ticks were afforded,
/// imputed leader_slot and self.start_slot /// imputed leader_slot and self.start_slot
/// reached_leader_tick() == true means "ready for a bank" /// reached_leader_slot() == true means "ready for a bank"
pub fn reached_leader_tick(&self) -> (bool, u64, Slot, Slot) { pub fn reached_leader_slot(&self) -> (bool, u64, Slot, Slot) {
trace!( trace!(
"tick_height {}, start_tick {}, start_leader_at_tick {:?}, grace {}, has_bank {}", "tick_height {}, start_tick_height {}, leader_first_tick_height {:?}, grace_ticks {}, has_bank {}",
self.tick_height, self.tick_height,
self.start_tick, self.start_tick_height,
self.start_leader_at_tick, self.leader_first_tick_height,
self.grace_ticks, self.grace_ticks,
self.has_bank() self.has_bank()
); );
let next_tick = self.tick_height + 1; let next_tick_height = self.tick_height + 1;
let next_leader_slot = (next_tick_height - 1) / self.ticks_per_slot;
if let Some(target_tick) = self.start_leader_at_tick { if let Some(leader_first_tick_height) = self.leader_first_tick_height {
// we've reached target_tick OR poh was reset to run immediately let target_tick_height = leader_first_tick_height.saturating_sub(1);
if next_tick >= target_tick || self.start_tick + self.grace_ticks == target_tick { // we've approached target_tick_height OR poh was reset to run immediately
assert!(next_tick >= self.start_tick); if self.tick_height >= target_tick_height
let ideal_target_tick = target_tick.saturating_sub(self.grace_ticks); || self.start_tick_height + self.grace_ticks == leader_first_tick_height
{
assert!(next_tick_height >= self.start_tick_height);
let ideal_target_tick_height = target_tick_height.saturating_sub(self.grace_ticks);
return ( return (
true, true,
next_tick.saturating_sub(ideal_target_tick), self.tick_height.saturating_sub(ideal_target_tick_height),
next_tick / self.ticks_per_slot, next_leader_slot,
self.start_slot, self.start_slot,
); );
} }
} }
(false, 0, next_tick / self.ticks_per_slot, self.start_slot) (false, 0, next_leader_slot, self.start_slot)
} }
// returns (start_leader_at_tick, last_leader_tick, grace_ticks) given the next slot this // returns (leader_first_tick_height, leader_last_tick_height, grace_ticks) given the next
// recorder will lead // slot this recorder will lead
fn compute_leader_slot_ticks( fn compute_leader_slot_tick_heights(
next_leader_slot: Option<(Slot, Slot)>, next_leader_slot: Option<(Slot, Slot)>,
ticks_per_slot: u64, ticks_per_slot: u64,
) -> (Option<u64>, u64, u64) { ) -> (Option<u64>, u64, u64) {
next_leader_slot next_leader_slot
.map(|(first, last)| { .map(|(first_slot, last_slot)| {
let first_tick = first * ticks_per_slot; let leader_first_tick_height = first_slot * ticks_per_slot + 1;
let last_tick = (last + 1) * ticks_per_slot - 1; let last_tick_height = (last_slot + 1) * ticks_per_slot;
let num_slots = last_slot - first_slot + 1;
let grace_ticks = cmp::min( let grace_ticks = cmp::min(
MAX_GRACE_TICKS, MAX_GRACE_TICKS,
(last_tick - first_tick + 1) / GRACE_TICKS_FACTOR, ticks_per_slot * num_slots / GRACE_TICKS_FACTOR,
); );
(Some(first_tick + grace_ticks), last_tick, grace_ticks) (
Some(leader_first_tick_height + grace_ticks),
last_tick_height,
grace_ticks,
)
}) })
.unwrap_or(( .unwrap_or((
None, None,
@ -202,14 +213,14 @@ impl PohRecorder {
std::mem::swap(&mut cache, &mut self.tick_cache); std::mem::swap(&mut cache, &mut self.tick_cache);
self.start_slot = start_slot; self.start_slot = start_slot;
self.start_tick = (start_slot + 1) * self.ticks_per_slot; self.tick_height = (start_slot + 1) * self.ticks_per_slot;
self.tick_height = self.start_tick - 1; self.start_tick_height = self.tick_height + 1;
let (start_leader_at_tick, last_leader_tick, grace_ticks) = let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_ticks(next_leader_slot, self.ticks_per_slot); Self::compute_leader_slot_tick_heights(next_leader_slot, self.ticks_per_slot);
self.grace_ticks = grace_ticks; self.grace_ticks = grace_ticks;
self.start_leader_at_tick = start_leader_at_tick; self.leader_first_tick_height = leader_first_tick_height;
self.last_leader_tick = last_leader_tick; self.leader_last_tick_height = leader_last_tick_height;
} }
pub fn set_working_bank(&mut self, working_bank: WorkingBank) { pub fn set_working_bank(&mut self, working_bank: WorkingBank) {
@ -279,8 +290,10 @@ impl PohRecorder {
working_bank.max_tick_height, working_bank.max_tick_height,
working_bank.bank.slot() working_bank.bank.slot()
); );
self.start_slot = working_bank.max_tick_height / self.ticks_per_slot; let working_slot =
self.start_tick = (self.start_slot + 1) * self.ticks_per_slot; (working_bank.max_tick_height / self.ticks_per_slot).saturating_sub(1);
self.start_slot = working_slot;
self.start_tick_height = working_slot * self.ticks_per_slot + 1;
self.clear_bank(); self.clear_bank();
} }
if send_result.is_err() { if send_result.is_err() {
@ -305,9 +318,9 @@ impl PohRecorder {
let now = Instant::now(); let now = Instant::now();
if let Some(poh_entry) = poh_entry { if let Some(poh_entry) = poh_entry {
self.tick_height += 1; self.tick_height += 1;
trace!("tick {}", self.tick_height); trace!("tick_height {}", self.tick_height);
if self.start_leader_at_tick.is_none() { if self.leader_first_tick_height.is_none() {
inc_new_counter_warn!( inc_new_counter_warn!(
"poh_recorder-tick_overhead", "poh_recorder-tick_overhead",
timing::duration_as_ms(&now.elapsed()) as usize timing::duration_as_ms(&now.elapsed()) as usize
@ -389,8 +402,8 @@ impl PohRecorder {
poh_config.hashes_per_tick, poh_config.hashes_per_tick,
))); )));
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let (start_leader_at_tick, last_leader_tick, grace_ticks) = let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_ticks(next_leader_slot, ticks_per_slot); Self::compute_leader_slot_tick_heights(next_leader_slot, ticks_per_slot);
( (
Self { Self {
poh, poh,
@ -400,9 +413,9 @@ impl PohRecorder {
sender, sender,
clear_bank_signal, clear_bank_signal,
start_slot, start_slot,
start_tick: tick_height + 1, start_tick_height: tick_height + 1,
start_leader_at_tick, leader_first_tick_height,
last_leader_tick, leader_last_tick_height,
grace_ticks, grace_ticks,
id: *id, id: *id,
blocktree: blocktree.clone(), blocktree: blocktree.clone(),
@ -944,12 +957,13 @@ mod tests {
poh_recorder.tick(); poh_recorder.tick();
poh_recorder.tick(); poh_recorder.tick();
poh_recorder.tick(); poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 3); poh_recorder.tick();
assert_eq!(poh_recorder.tick_height, 3); assert_eq!(poh_recorder.tick_cache.len(), 4);
assert_eq!(poh_recorder.tick_height, 4);
poh_recorder.reset(hash(b"hello"), 0, Some((4, 4))); // parent slot 0 implies tick_height of 3 poh_recorder.reset(hash(b"hello"), 0, Some((4, 4))); // parent slot 0 implies tick_height of 3
assert_eq!(poh_recorder.tick_cache.len(), 0); assert_eq!(poh_recorder.tick_cache.len(), 0);
poh_recorder.tick(); poh_recorder.tick();
assert_eq!(poh_recorder.tick_height, 4); assert_eq!(poh_recorder.tick_height, 5);
} }
Blocktree::destroy(&ledger_path).unwrap(); Blocktree::destroy(&ledger_path).unwrap();
} }
@ -1015,6 +1029,7 @@ mod tests {
#[test] #[test]
fn test_poh_recorder_reset_start_slot() { fn test_poh_recorder_reset_start_slot() {
solana_logger::setup();
let ledger_path = get_tmp_ledger_path!(); let ledger_path = get_tmp_ledger_path!();
{ {
let blocktree = let blocktree =
@ -1040,7 +1055,7 @@ mod tests {
); );
let end_slot = 3; let end_slot = 3;
let max_tick_height = (end_slot + 1) * ticks_per_slot - 1; let max_tick_height = (end_slot + 1) * ticks_per_slot;
let working_bank = WorkingBank { let working_bank = WorkingBank {
bank: bank.clone(), bank: bank.clone(),
min_tick_height: 1, min_tick_height: 1,
@ -1065,7 +1080,7 @@ mod tests {
} }
#[test] #[test]
fn test_reached_leader_tick() { fn test_reached_leader_slot() {
solana_logger::setup(); solana_logger::setup();
let ledger_path = get_tmp_ledger_path!(); let ledger_path = get_tmp_ledger_path!();
@ -1087,15 +1102,14 @@ mod tests {
&Arc::new(PohConfig::default()), &Arc::new(PohConfig::default()),
); );
// Test that with no leader slot, we don't reach the leader tick // Test that with no next leader slot, we don't reach the leader slot
assert_eq!(poh_recorder.reached_leader_tick().0, false); assert_eq!(poh_recorder.reached_leader_slot().0, false);
// Test that with no next leader slot in reset(), we don't reach the leader slot
poh_recorder.reset(bank.last_blockhash(), 0, None); poh_recorder.reset(bank.last_blockhash(), 0, None);
assert_eq!(poh_recorder.reached_leader_slot().0, false);
// Test that with no leader slot in reset(), we don't reach the leader tick // Provide a leader slot one slot down
assert_eq!(poh_recorder.reached_leader_tick().0, false);
// Provide a leader slot 1 slot down
poh_recorder.reset(bank.last_blockhash(), 0, Some((2, 2))); poh_recorder.reset(bank.last_blockhash(), 0, Some((2, 2)));
let init_ticks = poh_recorder.tick_height(); let init_ticks = poh_recorder.tick_height();
@ -1111,16 +1125,19 @@ mod tests {
init_ticks + bank.ticks_per_slot() init_ticks + bank.ticks_per_slot()
); );
// Test that we don't reach the leader tick because of grace ticks // Test that we don't reach the leader slot because of grace ticks
assert_eq!(poh_recorder.reached_leader_tick().0, false); assert_eq!(poh_recorder.reached_leader_slot().0, false);
// reset poh now. we should immediately be leader // reset poh now. we should immediately be leader
poh_recorder.reset(bank.last_blockhash(), 1, Some((2, 2))); poh_recorder.reset(bank.last_blockhash(), 1, Some((2, 2)));
assert_eq!(poh_recorder.reached_leader_tick().0, true); let (reached_leader_slot, grace_ticks, leader_slot, ..) =
assert_eq!(poh_recorder.reached_leader_tick().1, 0); poh_recorder.reached_leader_slot();
assert_eq!(reached_leader_slot, true);
assert_eq!(grace_ticks, 0);
assert_eq!(leader_slot, 2);
// Now test that with grace ticks we can reach leader ticks // Now test that with grace ticks we can reach leader slot
// Set the leader slot 1 slot down // Set the leader slot one slot down
poh_recorder.reset(bank.last_blockhash(), 1, Some((3, 3))); poh_recorder.reset(bank.last_blockhash(), 1, Some((3, 3)));
// Send one slot worth of ticks ("skips" slot 2) // Send one slot worth of ticks ("skips" slot 2)
@ -1129,22 +1146,22 @@ mod tests {
} }
// We are not the leader yet, as expected // We are not the leader yet, as expected
assert_eq!(poh_recorder.reached_leader_tick().0, false); assert_eq!(poh_recorder.reached_leader_slot().0, false);
// Send 1 less tick than the grace ticks // Send the grace ticks
for _ in 0..bank.ticks_per_slot() * NUM_CONSECUTIVE_LEADER_SLOTS / GRACE_TICKS_FACTOR { for _ in 0..bank.ticks_per_slot() / GRACE_TICKS_FACTOR {
poh_recorder.tick(); poh_recorder.tick();
} }
// We should be the leader now // We should be the leader now
assert_eq!(poh_recorder.reached_leader_tick().0, true); let (reached_leader_slot, grace_ticks, leader_slot, ..) =
assert_eq!( poh_recorder.reached_leader_slot();
poh_recorder.reached_leader_tick().1, assert_eq!(reached_leader_slot, true);
bank.ticks_per_slot() * NUM_CONSECUTIVE_LEADER_SLOTS / GRACE_TICKS_FACTOR assert_eq!(grace_ticks, bank.ticks_per_slot() / GRACE_TICKS_FACTOR);
); assert_eq!(leader_slot, 3);
// Let's test that correct grace ticks are reported // Let's test that correct grace ticks are reported
// Set the leader slot 1 slot down // Set the leader slot one slot down
poh_recorder.reset(bank.last_blockhash(), 2, Some((4, 4))); poh_recorder.reset(bank.last_blockhash(), 2, Some((4, 4)));
// send ticks for a slot // send ticks for a slot
@ -1153,25 +1170,33 @@ mod tests {
} }
// We are not the leader yet, as expected // We are not the leader yet, as expected
assert_eq!(poh_recorder.reached_leader_tick().0, false); assert_eq!(poh_recorder.reached_leader_slot().0, false);
poh_recorder.reset(bank.last_blockhash(), 3, Some((4, 4))); poh_recorder.reset(bank.last_blockhash(), 3, Some((4, 4)));
// without sending more ticks, we should be leader now // without sending more ticks, we should be leader now
assert_eq!(poh_recorder.reached_leader_tick().0, true); let (reached_leader_slot, grace_ticks, leader_slot, ..) =
assert_eq!(poh_recorder.reached_leader_tick().1, 0); poh_recorder.reached_leader_slot();
assert_eq!(reached_leader_slot, true);
assert_eq!(grace_ticks, 0);
assert_eq!(leader_slot, 4);
// Let's test that if a node overshoots the ticks for its target // Let's test that if a node overshoots the ticks for its target
// leader slot, reached_leader_tick() will return true, because it's overdue // leader slot, reached_leader_slot() will return true, because it's overdue
// Set the leader slot 1 slot down // Set the leader slot one slot down
poh_recorder.reset(bank.last_blockhash(), 4, Some((5, 5))); poh_recorder.reset(bank.last_blockhash(), 4, Some((5, 5)));
// Send remaining ticks for the slot (remember we sent extra ticks in the previous part of the test) // Overshoot ticks for the slot
for _ in 0..4 * bank.ticks_per_slot() { let overshoot_factor = 4;
for _ in 0..overshoot_factor * bank.ticks_per_slot() {
poh_recorder.tick(); poh_recorder.tick();
} }
// We are overdue to lead // We are overdue to lead
assert_eq!(poh_recorder.reached_leader_tick().0, true); let (reached_leader_slot, grace_ticks, leader_slot, ..) =
poh_recorder.reached_leader_slot();
assert_eq!(reached_leader_slot, true);
assert_eq!(grace_ticks, overshoot_factor * bank.ticks_per_slot());
assert_eq!(leader_slot, 9);
} }
Blocktree::destroy(&ledger_path).unwrap(); Blocktree::destroy(&ledger_path).unwrap();
} }
@ -1276,30 +1301,30 @@ mod tests {
} }
#[test] #[test]
fn test_compute_leader_slots() { fn test_compute_leader_slot_tick_heights() {
assert_eq!( assert_eq!(
PohRecorder::compute_leader_slot_ticks(None, 0), PohRecorder::compute_leader_slot_tick_heights(None, 0),
(None, 0, 0) (None, 0, 0)
); );
assert_eq!( assert_eq!(
PohRecorder::compute_leader_slot_ticks(Some((4, 4)), 8), PohRecorder::compute_leader_slot_tick_heights(Some((4, 4)), 8),
(Some(36), 39, 4) (Some(37), 40, 4)
); );
assert_eq!( assert_eq!(
PohRecorder::compute_leader_slot_ticks(Some((4, 7)), 8), PohRecorder::compute_leader_slot_tick_heights(Some((4, 7)), 8),
(Some(44), 63, MAX_GRACE_TICKS) (Some(45), 64, MAX_GRACE_TICKS)
); );
assert_eq!( assert_eq!(
PohRecorder::compute_leader_slot_ticks(Some((6, 7)), 8), PohRecorder::compute_leader_slot_tick_heights(Some((6, 7)), 8),
(Some(56), 63, 8) (Some(57), 64, 8)
); );
assert_eq!( assert_eq!(
PohRecorder::compute_leader_slot_ticks(Some((6, 7)), 4), PohRecorder::compute_leader_slot_tick_heights(Some((6, 7)), 4),
(Some(28), 31, 4) (Some(29), 32, 4)
); );
} }
} }

View File

@ -288,14 +288,14 @@ impl ReplayStage {
assert!(!poh_recorder.lock().unwrap().has_bank()); assert!(!poh_recorder.lock().unwrap().has_bank());
let (reached_leader_tick, _grace_ticks, poh_slot, parent_slot) = let (reached_leader_slot, _grace_ticks, poh_slot, parent_slot) =
poh_recorder.lock().unwrap().reached_leader_tick(); poh_recorder.lock().unwrap().reached_leader_slot();
if !reached_leader_tick { if !reached_leader_slot {
trace!("{} poh_recorder hasn't reached_leader_tick", my_pubkey); trace!("{} poh_recorder hasn't reached_leader_slot", my_pubkey);
return; return;
} }
trace!("{} reached_leader_tick", my_pubkey,); trace!("{} reached_leader_slot", my_pubkey);
let parent = bank_forks let parent = bank_forks
.read() .read()
@ -1008,7 +1008,7 @@ mod test {
genesis_block.epoch_schedule.warmup = false; genesis_block.epoch_schedule.warmup = false;
genesis_block.ticks_per_slot = 4; genesis_block.ticks_per_slot = 4;
let bank0 = Bank::new(&genesis_block); let bank0 = Bank::new(&genesis_block);
for _ in 1..genesis_block.ticks_per_slot { for _ in 0..genesis_block.ticks_per_slot {
bank0.register_tick(&Hash::default()); bank0.register_tick(&Hash::default());
} }
bank0.freeze(); bank0.freeze();

View File

@ -323,7 +323,7 @@ impl Bank {
slots_per_year: parent.slots_per_year, slots_per_year: parent.slots_per_year,
epoch_schedule, epoch_schedule,
rent_collector: parent.rent_collector.clone_with_epoch(epoch), rent_collector: parent.rent_collector.clone_with_epoch(epoch),
max_tick_height: (slot + 1) * parent.ticks_per_slot - 1, max_tick_height: (slot + 1) * parent.ticks_per_slot,
block_height: parent.block_height + 1, block_height: parent.block_height + 1,
fee_calculator: FeeCalculator::new_derived( fee_calculator: FeeCalculator::new_derived(
&parent.fee_calculator, &parent.fee_calculator,
@ -660,7 +660,7 @@ impl Bank {
self.ticks_per_slot = genesis_block.ticks_per_slot; self.ticks_per_slot = genesis_block.ticks_per_slot;
self.slots_per_segment = genesis_block.slots_per_segment; self.slots_per_segment = genesis_block.slots_per_segment;
self.max_tick_height = (self.slot + 1) * self.ticks_per_slot - 1; self.max_tick_height = (self.slot + 1) * self.ticks_per_slot;
// ticks/year = seconds/year ... // ticks/year = seconds/year ...
self.slots_per_year = SECONDS_PER_YEAR self.slots_per_year = SECONDS_PER_YEAR
// * (ns/s)/(ns/tick) / ticks/slot = 1/s/1/tick = ticks/s // * (ns/s)/(ns/tick) / ticks/slot = 1/s/1/tick = ticks/s
@ -807,7 +807,7 @@ impl Bank {
let should_register_hash = { let should_register_hash = {
if self.ticks_per_slot() != 1 || self.slot() != 0 { if self.ticks_per_slot() != 1 || self.slot() != 0 {
let lock = { let lock = {
if (current_tick_height + 1) % self.ticks_per_slot == self.ticks_per_slot - 1 { if current_tick_height % self.ticks_per_slot == self.ticks_per_slot - 1 {
Some(self.blockhash_queue.write().unwrap()) Some(self.blockhash_queue.write().unwrap())
} else { } else {
None None
@ -816,7 +816,7 @@ impl Bank {
self.tick_height.fetch_add(1, Ordering::Relaxed); self.tick_height.fetch_add(1, Ordering::Relaxed);
inc_new_counter_debug!("bank-register_tick-registered", 1); inc_new_counter_debug!("bank-register_tick-registered", 1);
lock lock
} else if current_tick_height % self.ticks_per_slot == self.ticks_per_slot - 1 { } else if current_tick_height % self.ticks_per_slot == 0 {
// Register a new block hash if at the last tick in the slot // Register a new block hash if at the last tick in the slot
Some(self.blockhash_queue.write().unwrap()) Some(self.blockhash_queue.write().unwrap())
} else { } else {
@ -3236,7 +3236,7 @@ mod tests {
assert_eq!(bank.is_votable(), false); assert_eq!(bank.is_votable(), false);
// Register enough ticks to hit max tick height // Register enough ticks to hit max tick height
for i in 0..genesis_block.ticks_per_slot - 1 { for i in 0..genesis_block.ticks_per_slot {
bank.register_tick(&hash::hash(format!("hello world {}", i).as_bytes())); bank.register_tick(&hash::hash(format!("hello world {}", i).as_bytes()));
} }
@ -3249,7 +3249,7 @@ mod tests {
assert_eq!(bank.is_votable(), false); assert_eq!(bank.is_votable(), false);
// Register enough ticks to hit max tick height // Register enough ticks to hit max tick height
for i in 0..genesis_block.ticks_per_slot - 1 { for i in 0..genesis_block.ticks_per_slot {
bank.register_tick(&hash::hash(format!("hello world {}", i).as_bytes())); bank.register_tick(&hash::hash(format!("hello world {}", i).as_bytes()));
} }
// empty banks aren't votable even at max tick height // empty banks aren't votable even at max tick height
@ -3360,7 +3360,7 @@ mod tests {
let (genesis_block, _) = create_genesis_block(500); let (genesis_block, _) = create_genesis_block(500);
let bank = Arc::new(Bank::new(&genesis_block)); let bank = Arc::new(Bank::new(&genesis_block));
//set tick height to max //set tick height to max
let max_tick_height = (bank.slot + 1) * bank.ticks_per_slot - 1; let max_tick_height = (bank.slot + 1) * bank.ticks_per_slot;
bank.tick_height.store(max_tick_height, Ordering::Relaxed); bank.tick_height.store(max_tick_height, Ordering::Relaxed);
assert!(bank.is_votable()); assert!(bank.is_votable());
} }