#[cfg(feature = "dev-context-only-utils")]
use qualifier_attr::qualifiers;
use {
crate::{
poh_controller::PohController, poh_service::PohService, record_channels::record_channels,
transaction_recorder::TransactionRecorder,
},
agave_votor_messages::{
migration::MigrationStatus, reward_certificate::BuildRewardCertsRespError,
},
arc_swap::ArcSwap,
crossbeam_channel::{Receiver, SendError, Sender, TrySendError, bounded, unbounded},
log::*,
solana_clock::{BankId, Slot},
solana_entry::{
block_component::{BlockFooterV1, VersionedBlockMarker},
entry::Entry,
entry_or_marker::EntryOrMarker,
poh::Poh,
},
solana_hash::Hash,
solana_leader_schedule::NUM_CONSECUTIVE_LEADER_SLOTS,
solana_ledger::{blockstore::Blockstore, leader_schedule_cache::LeaderScheduleCache},
solana_measure::measure_us,
solana_poh_config::PohConfig,
solana_pubkey::Pubkey,
solana_runtime::{
bank::Bank, block_component_processor::BankFooterError,
installed_scheduler_pool::BankWithScheduler,
validated_reward_certificate::Error as ValidatedRewardCertError,
},
solana_transaction::versioned::VersionedTransaction,
std::{
cmp,
sync::{
Arc, Mutex, RwLock,
atomic::{AtomicBool, AtomicU64, Ordering},
},
time::{Duration, Instant},
},
thiserror::Error,
};
pub const GRACE_TICKS_FACTOR: u64 = 2;
pub const MAX_GRACE_SLOTS: u64 = 2;
#[derive(Debug, Error)]
pub enum PohRecorderError {
#[error("max height reached")]
MaxHeightReached,
#[error("min height not reached")]
MinHeightNotReached,
#[error("send WorkingBankEntry error")]
SendError(#[from] Box<SendError<WorkingBankEntryOrMarker>>),
#[error("channel full")]
ChannelFull,
#[error("channel disconnected")]
ChannelDisconnected,
#[error("producing reward certs failed with {0}")]
BuildRewardCerts(#[from] BuildRewardCertsRespError),
#[error("constructing validated reward cert failed with {0}")]
ValidatedRewardCert(#[from] ValidatedRewardCertError),
#[error("updating bank footer failed with \"{0}\"")]
UpdateBankFooter(#[from] BankFooterError),
#[error("bank {0} did not freeze before block footer timeout")]
BankFreezeTimeout(Slot),
#[error("couldn't reset bank during fast leader handover slot {0} -> slot {1}")]
ResetBankError(Slot, Slot),
#[error("couldn't reschedule pre-UpdateParent transactions for slot {0}")]
RescheduleTransactionsError(Slot),
#[error("leader window moved past slot {0}")]
WindowMovedOn(Slot),
}
pub(crate) type Result<T> = std::result::Result<T, PohRecorderError>;
pub type WorkingBankEntryOrMarker = (Arc<Bank>, (EntryOrMarker, u64));
#[derive(Debug)]
pub struct RecordSummary {
pub remaining_hashes_in_slot: u64,
}
pub struct Record {
pub mixin: Hash,
pub transactions: Vec<VersionedTransaction>,
pub bank_id: BankId,
}
impl Record {
pub fn new(mixin: Hash, transactions: Vec<VersionedTransaction>, bank_id: BankId) -> Self {
Self {
mixin,
transactions,
bank_id,
}
}
}
pub struct WorkingBank {
pub bank: BankWithScheduler,
pub start: Arc<Instant>,
pub min_tick_height: u64,
pub max_tick_height: u64,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PohLeaderStatus {
NotReached,
Reached { poh_slot: Slot, parent_slot: Slot },
}
struct PohRecorderMetrics {
flush_cache_tick_us: u64,
flush_cache_no_tick_us: u64,
record_us: u64,
record_lock_contention_us: u64,
report_metrics_us: u64,
send_entry_us: u64,
tick_lock_contention_us: u64,
ticks_from_record: u64,
total_sleep_us: u64,
last_metric: Instant,
}
impl Default for PohRecorderMetrics {
fn default() -> Self {
Self {
flush_cache_tick_us: 0,
flush_cache_no_tick_us: 0,
record_us: 0,
record_lock_contention_us: 0,
report_metrics_us: 0,
send_entry_us: 0,
tick_lock_contention_us: 0,
ticks_from_record: 0,
total_sleep_us: 0,
last_metric: Instant::now(),
}
}
}
impl PohRecorderMetrics {
fn report(&mut self, bank_slot: Slot) {
if self.last_metric.elapsed().as_millis() > 1000 {
datapoint_info!(
"poh_recorder",
("slot", bank_slot, i64),
("flush_cache_tick_us", self.flush_cache_tick_us, i64),
("flush_cache_no_tick_us", self.flush_cache_no_tick_us, i64),
("record_us", self.record_us, i64),
(
"record_lock_contention_us",
self.record_lock_contention_us,
i64
),
("report_metrics_us", self.report_metrics_us, i64),
("send_entry_us", self.send_entry_us, i64),
("tick_lock_contention", self.tick_lock_contention_us, i64),
("ticks_from_record", self.ticks_from_record, i64),
("total_sleep_us", self.total_sleep_us, i64),
);
*self = Self::default();
}
}
}
pub struct PohRecorder {
pub(crate) poh: Arc<Mutex<Poh>>,
clear_bank_signal: Option<Sender<bool>>,
start_bank: Arc<Bank>, start_bank_active_descendants: Vec<Slot>,
start_tick_height: u64, tick_cache: Vec<(Entry, u64)>, working_bank: Option<WorkingBank>,
shared_leader_state: SharedLeaderState,
working_bank_sender: Sender<WorkingBankEntryOrMarker>,
leader_last_tick_height: u64, grace_ticks: u64,
blockstore: Arc<Blockstore>,
leader_schedule_cache: Arc<LeaderScheduleCache>,
ticks_per_slot: u64,
metrics: PohRecorderMetrics,
delay_leader_block_for_pending_fork: bool,
last_reported_slot_for_pending_fork: Arc<Mutex<Slot>>,
pub is_exited: Arc<AtomicBool>,
}
impl PohRecorder {
#[allow(clippy::too_many_arguments)]
pub fn new(
tick_height: u64,
last_entry_hash: Hash,
start_bank: Arc<Bank>,
next_leader_slot: Option<(Slot, Slot)>,
ticks_per_slot: u64,
blockstore: Arc<Blockstore>,
leader_schedule_cache: &Arc<LeaderScheduleCache>,
poh_config: &PohConfig,
is_exited: Arc<AtomicBool>,
) -> (Self, Receiver<WorkingBankEntryOrMarker>) {
let delay_leader_block_for_pending_fork = false;
Self::new_with_clear_signal(
tick_height,
last_entry_hash,
start_bank,
next_leader_slot,
ticks_per_slot,
delay_leader_block_for_pending_fork,
blockstore,
None,
leader_schedule_cache,
poh_config,
is_exited,
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_clear_signal(
tick_height: u64,
last_entry_hash: Hash,
start_bank: Arc<Bank>,
next_leader_slot: Option<(Slot, Slot)>,
ticks_per_slot: u64,
delay_leader_block_for_pending_fork: bool,
blockstore: Arc<Blockstore>,
clear_bank_signal: Option<Sender<bool>>,
leader_schedule_cache: &Arc<LeaderScheduleCache>,
poh_config: &PohConfig,
is_exited: Arc<AtomicBool>,
) -> (Self, Receiver<WorkingBankEntryOrMarker>) {
let tick_number = 0;
let poh = Arc::new(Mutex::new(Poh::new_with_slot_info(
last_entry_hash,
poh_config.hashes_per_tick,
tick_number,
)));
let (working_bank_sender, working_bank_receiver) = unbounded();
let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_tick_heights(next_leader_slot, ticks_per_slot);
(
Self {
poh,
tick_cache: vec![],
working_bank: None,
shared_leader_state: SharedLeaderState::new(
tick_height,
leader_first_tick_height,
next_leader_slot,
),
working_bank_sender,
clear_bank_signal,
start_bank,
start_bank_active_descendants: vec![],
start_tick_height: tick_height + 1,
leader_last_tick_height,
grace_ticks,
blockstore,
leader_schedule_cache: leader_schedule_cache.clone(),
ticks_per_slot,
metrics: PohRecorderMetrics::default(),
delay_leader_block_for_pending_fork,
last_reported_slot_for_pending_fork: Arc::default(),
is_exited,
},
working_bank_receiver,
)
}
pub fn reset(&mut self, reset_bank: Arc<Bank>, next_leader_slot: Option<(Slot, Slot)>) {
self.clear_bank(false);
let tick_height = self.reset_poh(reset_bank, true);
let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_tick_heights(next_leader_slot, self.ticks_per_slot);
self.grace_ticks = grace_ticks;
self.shared_leader_state.store(Arc::new(LeaderState::new(
None,
tick_height,
leader_first_tick_height,
next_leader_slot,
)));
self.leader_last_tick_height = leader_last_tick_height;
}
pub fn send_marker(&mut self, marker: VersionedBlockMarker) -> Result<()> {
let tick_height = self.tick_height();
let working_bank = self
.working_bank
.as_mut()
.ok_or(PohRecorderError::MaxHeightReached)?;
self.working_bank_sender
.send((
working_bank.bank.clone(),
(EntryOrMarker::Marker(marker), tick_height),
))
.map_err(Box::new)?;
Ok(())
}
pub fn record(
&mut self,
bank_id: BankId,
mixin: Hash,
transactions: Vec<VersionedTransaction>,
) -> Result<RecordSummary> {
assert!(!transactions.is_empty(), "No transactions provided");
if let Some(working_bank) = self.working_bank.as_ref() {
let ((), report_metrics_us) =
measure_us!(self.metrics.report(working_bank.bank.slot()));
self.metrics.report_metrics_us += report_metrics_us;
}
loop {
let (flush_cache_res, flush_cache_us) = measure_us!(self.flush_cache(false, None));
self.metrics.flush_cache_no_tick_us += flush_cache_us;
flush_cache_res?;
let tick_height = self.tick_height(); let working_bank = self
.working_bank
.as_mut()
.ok_or(PohRecorderError::MaxHeightReached)?;
if bank_id != working_bank.bank.bank_id() {
return Err(PohRecorderError::MaxHeightReached);
}
let (mut poh_lock, poh_lock_us) = measure_us!(self.poh.lock().unwrap());
self.metrics.record_lock_contention_us += poh_lock_us;
let (entry, record_mixin_us) = measure_us!(poh_lock.record(mixin));
self.metrics.record_us += record_mixin_us;
let remaining_hashes_in_slot =
poh_lock.remaining_hashes_in_slot(working_bank.bank.ticks_per_slot());
drop(poh_lock);
if let Some(entry) = entry {
let (send_entry_res, send_entry_us) = measure_us!(
self.working_bank_sender.send((
working_bank.bank.clone(),
(
Entry {
num_hashes: entry.num_hashes,
hash: entry.hash,
transactions,
}
.into(),
tick_height,
),
))
);
self.metrics.send_entry_us += send_entry_us;
send_entry_res.map_err(Box::new)?;
return Ok(RecordSummary {
remaining_hashes_in_slot,
});
}
self.metrics.ticks_from_record += 1;
self.tick();
}
}
#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
pub(crate) fn tick(&mut self) {
let (poh_entry, tick_lock_contention_us) = measure_us!({
let mut poh_l = self.poh.lock().unwrap();
poh_l.tick()
});
self.metrics.tick_lock_contention_us += tick_lock_contention_us;
if let Some(poh_entry) = poh_entry {
self.shared_leader_state.increment_tick_height();
trace!("tick_height {}", self.tick_height());
if self
.shared_leader_state
.load()
.leader_first_tick_height
.is_none()
{
return;
}
self.tick_cache.push((
Entry {
num_hashes: poh_entry.num_hashes,
hash: poh_entry.hash,
transactions: vec![],
},
self.tick_height(),
));
let (_flush_res, flush_cache_and_tick_us) = measure_us!(self.flush_cache(true, None));
self.metrics.flush_cache_tick_us += flush_cache_and_tick_us;
}
}
pub fn set_bank(&mut self, bank: BankWithScheduler) {
assert!(self.working_bank.is_none());
let working_bank = WorkingBank {
min_tick_height: bank.tick_height(),
max_tick_height: bank.max_tick_height(),
bank,
start: Arc::new(Instant::now()),
};
trace!("new working bank");
assert_eq!(working_bank.bank.ticks_per_slot(), self.ticks_per_slot());
let mut tick_height = self.tick_height();
let hashes_per_tick = working_bank.bank.hashes_per_tick();
if self.poh.lock().unwrap().hashes_per_tick_config() != hashes_per_tick {
info!(
"resetting poh due to hashes per tick change detected at {}",
working_bank.bank.slot()
);
tick_height = self.reset_poh(working_bank.bank.clone(), false);
}
let leader_state = self.shared_leader_state.load();
let leader_first_tick_height = leader_state.leader_first_tick_height();
let next_leader_slot = leader_state.next_leader_slot_range();
drop(leader_state);
self.shared_leader_state.store(Arc::new(LeaderState::new(
Some(working_bank.bank.clone_without_scheduler()),
tick_height,
leader_first_tick_height,
next_leader_slot,
)));
self.working_bank = Some(working_bank);
let _ = self.flush_cache(false, None);
}
pub(crate) fn notify_replay_wakeup(&self) {
if let Some(signal) = &self.clear_bank_signal {
match signal.try_send(true) {
Ok(()) => {}
Err(TrySendError::Full(_)) => {
trace!("replay wake up signal channel is full.")
}
Err(TrySendError::Disconnected(_)) => {
trace!("replay wake up signal channel is disconnected.")
}
}
}
}
pub fn clear_bank(&mut self, set_shared_state: bool) {
if let Some(WorkingBank { bank, start, .. }) = self.working_bank.take() {
let next_leader_slot = self.leader_schedule_cache.next_leader_slot(
bank.leader_id(),
bank.slot(),
&bank,
Some(&self.blockstore),
GRACE_TICKS_FACTOR * MAX_GRACE_SLOTS,
);
assert_eq!(self.ticks_per_slot, bank.ticks_per_slot());
let (leader_first_tick_height, leader_last_tick_height, grace_ticks) =
Self::compute_leader_slot_tick_heights(next_leader_slot, self.ticks_per_slot);
self.grace_ticks = grace_ticks;
self.leader_last_tick_height = leader_last_tick_height;
if set_shared_state {
self.shared_leader_state.store(Arc::new(LeaderState::new(
None,
self.tick_height(),
leader_first_tick_height,
next_leader_slot,
)));
}
datapoint_info!(
"leader-slot-start-to-cleared-elapsed-ms",
("slot", bank.slot(), i64),
("elapsed", start.elapsed().as_millis(), i64),
);
}
self.notify_replay_wakeup();
}
#[must_use]
fn reset_poh(&mut self, reset_bank: Arc<Bank>, reset_start_bank: bool) -> u64 {
let blockhash = reset_bank.last_blockhash();
let hashes_per_tick = reset_bank.hashes_per_tick();
let poh_hash = {
let mut poh = self.poh.lock().unwrap();
poh.reset(blockhash, hashes_per_tick);
poh.hash
};
info!(
"reset poh from: {},{},{} to: {},{}",
poh_hash,
self.tick_height(),
self.start_slot(),
blockhash,
reset_bank.slot()
);
self.tick_cache = vec![];
if reset_start_bank {
self.start_bank = reset_bank;
self.start_bank_active_descendants = vec![];
}
let tick_height = (self.start_slot() + 1) * self.ticks_per_slot;
self.start_tick_height = tick_height + 1;
tick_height
}
fn wait_for_freeze_and_send_footer(
&self,
mut footer: BlockFooterV1,
working_bank: &WorkingBank,
) -> Result<()> {
self.notify_replay_wakeup();
let start = Instant::now();
let delta_block = Duration::from_nanos_u128(working_bank.bank.ns_per_slot);
while !working_bank.bank.is_frozen() && !self.is_exited.load(Ordering::Relaxed) {
if start.elapsed() > delta_block {
break;
}
std::hint::spin_loop();
}
if !working_bank.bank.is_frozen() {
if self.is_exited.load(Ordering::Relaxed) {
return Err(PohRecorderError::ChannelDisconnected);
}
error!(
"slot = {} block production failure. bank freezing timed out.",
working_bank.bank.slot()
);
return Err(PohRecorderError::BankFreezeTimeout(
working_bank.bank.slot(),
));
}
footer.bank_hash = working_bank.bank.hash();
let footer = VersionedBlockMarker::from_block_footer(footer);
let footer_entry_marker = (
EntryOrMarker::Marker(footer),
working_bank.max_tick_height - 1,
);
self.working_bank_sender
.send((working_bank.bank.clone(), footer_entry_marker))
.map_err(|err| {
error!(
"slot = {} block production failure. failed to broadcast footer",
working_bank.bank.slot()
);
PohRecorderError::SendError(Box::new(err))
})
}
fn flush_cache(&mut self, tick: bool, footer: Option<BlockFooterV1>) -> Result<()> {
let working_bank = self
.working_bank
.as_ref()
.ok_or(PohRecorderError::MaxHeightReached)?;
if self.tick_height() < working_bank.min_tick_height {
return Err(PohRecorderError::MinHeightNotReached);
}
if tick && self.tick_height() == working_bank.min_tick_height {
return Err(PohRecorderError::MinHeightNotReached);
}
let entry_count = self
.tick_cache
.iter()
.take_while(|x| x.1 <= working_bank.max_tick_height)
.count();
let has_footer = footer.is_some();
let mut send_result = Ok(());
if entry_count > 0 {
trace!(
"flush_cache: bank_slot: {} tick_height: {} max: {} sending: {}",
working_bank.bank.slot(),
working_bank.bank.tick_height(),
working_bank.max_tick_height,
entry_count,
);
for (entry, tick_height) in &self.tick_cache[..entry_count] {
working_bank.bank.register_tick(&entry.hash);
if let Some(footer) = footer.clone()
&& let Err(err) = self.wait_for_freeze_and_send_footer(footer, working_bank)
{
send_result = Err(err);
break;
}
let tick = (EntryOrMarker::from(entry.clone()), *tick_height);
send_result = self
.working_bank_sender
.send((working_bank.bank.clone(), tick))
.map_err(|err| PohRecorderError::SendError(Box::new(err)));
if send_result.is_err() {
break;
}
}
}
if send_result.is_ok() {
if self.tick_height() >= working_bank.max_tick_height {
info!(
"poh_record: max_tick_height {} reached, clearing working_bank {}",
working_bank.max_tick_height,
working_bank.bank.slot()
);
self.start_bank = working_bank.bank.clone();
let working_slot = self.start_slot();
self.start_tick_height = working_slot * self.ticks_per_slot + 1;
self.clear_bank(true);
}
let _ = self.tick_cache.drain(..entry_count);
} else {
info!("WorkingBank::sender disconnected or footer failed {send_result:?}");
if !has_footer {
self.clear_bank(true);
}
}
send_result
}
pub fn would_be_leader(&self, within_next_n_ticks: u64) -> bool {
self.has_bank()
|| self
.leader_first_tick_height()
.is_some_and(|leader_first_tick_height| {
let tick_height = self.tick_height();
tick_height + within_next_n_ticks >= leader_first_tick_height
&& tick_height <= self.leader_last_tick_height
})
}
fn slot_for_tick_height(&self, tick_height: u64) -> Slot {
tick_height.saturating_sub(1) / self.ticks_per_slot
}
fn current_poh_slot(&self) -> Slot {
let next_tick_height = self.tick_height().saturating_add(1);
self.slot_for_tick_height(next_tick_height)
}
pub fn leader_after_n_slots(&self, slots: u64) -> Option<Pubkey> {
self.leader_schedule_cache
.slot_leader_at(self.current_poh_slot() + slots, None)
.map(|leader| leader.id)
}
pub fn leader_and_slot_after_n_slots(
&self,
slots_in_the_future: u64,
) -> Option<(Pubkey, Slot)> {
let target_slot = self.current_poh_slot().checked_add(slots_in_the_future)?;
self.leader_schedule_cache
.slot_leader_at(target_slot, None)
.map(|leader| (leader.id, target_slot))
}
pub fn shared_leader_state(&self) -> SharedLeaderState {
self.shared_leader_state.clone()
}
pub fn bank(&self) -> Option<Arc<Bank>> {
self.working_bank.as_ref().map(|w| w.bank.clone())
}
pub fn has_bank(&self) -> bool {
self.working_bank.is_some()
}
pub fn tick_height(&self) -> u64 {
self.shared_leader_state.load().tick_height()
}
fn leader_first_tick_height(&self) -> Option<u64> {
self.shared_leader_state.load().leader_first_tick_height()
}
pub fn ticks_per_slot(&self) -> u64 {
self.ticks_per_slot
}
pub fn target_tick_ns(&self) -> u64 {
let ns_per_slot = self
.working_bank
.as_ref()
.map(|working_bank| working_bank.bank.ns_per_slot)
.unwrap_or(self.start_bank.ns_per_slot);
let ticks_per_slot = u128::from(self.ticks_per_slot.max(1));
let target_tick_ns = ns_per_slot.saturating_div(ticks_per_slot);
u64::try_from(target_tick_ns).unwrap_or(u64::MAX)
}
pub fn start_slot(&self) -> Slot {
self.start_bank.slot()
}
pub fn start_bank_id(&self) -> BankId {
self.start_bank.bank_id()
}
pub fn reached_leader_slot(&self, my_pubkey: &Pubkey) -> PohLeaderStatus {
trace!(
"tick_height {}, start_tick_height {}, leader_first_tick_height {:?}, grace_ticks {}, \
has_bank {}",
self.tick_height(),
self.start_tick_height,
self.leader_first_tick_height(),
self.grace_ticks,
self.has_bank()
);
let current_poh_slot = self.current_poh_slot();
let Some(leader_first_tick_height) = self.leader_first_tick_height() else {
return PohLeaderStatus::NotReached;
};
if !self.reached_leader_tick(my_pubkey, leader_first_tick_height) {
return PohLeaderStatus::NotReached;
}
if self
.blockstore
.has_existing_shreds_for_slot(current_poh_slot)
{
return PohLeaderStatus::NotReached;
}
let poh_slot = current_poh_slot;
let parent_slot = self.start_slot();
PohLeaderStatus::Reached {
poh_slot,
parent_slot,
}
}
fn reached_leader_tick(&self, my_pubkey: &Pubkey, leader_first_tick_height: u64) -> bool {
if self.start_tick_height == leader_first_tick_height {
return true;
}
let ideal_target_tick_height = leader_first_tick_height.saturating_sub(1);
if self.tick_height() < ideal_target_tick_height {
return false;
}
if self.tick_height() >= ideal_target_tick_height.saturating_add(self.grace_ticks) {
return true;
}
let next_leader_slot = self.current_poh_slot();
self.can_skip_grace_ticks(my_pubkey, next_leader_slot)
}
fn can_skip_grace_ticks(&self, my_pubkey: &Pubkey, next_leader_slot: Slot) -> bool {
if self.start_slot_was_mine(my_pubkey) {
return true;
}
if self.start_slot_was_mine_or_previous_leader(next_leader_slot) {
return self.building_off_previous_leader_last_block(my_pubkey, next_leader_slot);
}
if !self.is_new_reset_bank_pending(next_leader_slot) {
return true;
}
self.report_pending_fork_was_detected(next_leader_slot);
if !self.delay_leader_block_for_pending_fork {
return true;
}
false
}
fn start_slot_was_mine_or_previous_leader(&self, next_leader_slot: Slot) -> bool {
(next_leader_slot.saturating_sub(NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64)
..next_leader_slot)
.any(|slot| {
slot == self.start_slot()
})
}
fn building_off_previous_leader_last_block(
&self,
my_pubkey: &Pubkey,
next_leader_slot: Slot,
) -> bool {
for slot in (next_leader_slot.saturating_sub(NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64)
..next_leader_slot)
.rev()
{
let leader_for_slot = self.leader_schedule_cache.slot_leader_at(slot, None);
let Some(leader_for_slot) = leader_for_slot else {
continue;
};
if leader_for_slot.id != *my_pubkey {
return slot == self.start_slot();
}
}
false
}
fn start_slot_was_mine(&self, my_pubkey: &Pubkey) -> bool {
self.start_bank.leader_id() == my_pubkey
}
fn is_new_reset_bank_pending(&self, next_leader_slot: Slot) -> bool {
self.start_bank_active_descendants
.iter()
.any(|pending_slot| *pending_slot < next_leader_slot)
}
fn report_pending_fork_was_detected(&self, next_leader_slot: Slot) {
let mut last_slot = self.last_reported_slot_for_pending_fork.lock().unwrap();
if *last_slot == next_leader_slot {
return;
}
*last_slot = next_leader_slot;
datapoint_info!(
"poh_recorder-detected_pending_fork",
("next_leader_slot", next_leader_slot, i64),
(
"did_delay_leader_slot",
self.delay_leader_block_for_pending_fork,
bool
),
);
}
fn compute_leader_slot_tick_heights(
next_leader_slot: Option<(Slot, Slot)>,
ticks_per_slot: u64,
) -> (Option<u64>, u64, u64) {
next_leader_slot
.map(|(first_slot, last_slot)| {
let leader_first_tick_height = first_slot * 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(
ticks_per_slot * MAX_GRACE_SLOTS,
ticks_per_slot * num_slots / GRACE_TICKS_FACTOR,
);
(
Some(leader_first_tick_height),
last_tick_height,
grace_ticks,
)
})
.unwrap_or((
None,
0,
cmp::min(
ticks_per_slot * MAX_GRACE_SLOTS,
ticks_per_slot * NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64 / GRACE_TICKS_FACTOR,
),
))
}
pub fn update_start_bank_active_descendants(&mut self, active_descendants: &[Slot]) {
self.start_bank_active_descendants = active_descendants.to_vec();
}
#[cfg(feature = "dev-context-only-utils")]
pub fn set_bank_for_test(&mut self, bank: Arc<Bank>) {
self.set_bank(BankWithScheduler::new_without_scheduler(bank))
}
#[cfg(feature = "dev-context-only-utils")]
pub fn clear_bank_for_test(&mut self) {
self.clear_bank(true);
}
pub fn tick_alpenglow(
&mut self,
slot_max_tick_height: u64,
footer: BlockFooterV1,
) -> Result<()> {
let (poh_entry, tick_lock_contention_us) = measure_us!({
let mut poh_l = self.poh.lock().unwrap();
poh_l.tick()
});
self.metrics.tick_lock_contention_us += tick_lock_contention_us;
if let Some(poh_entry) = poh_entry {
self.shared_leader_state
.0
.load()
.tick_height
.store(slot_max_tick_height, Ordering::Release);
self.tick_cache = vec![];
self.tick_cache.push((
Entry {
num_hashes: poh_entry.num_hashes,
hash: poh_entry.hash,
transactions: vec![],
},
self.shared_leader_state
.0
.load()
.tick_height
.load(Ordering::Acquire),
));
let (flush_res, flush_cache_and_tick_us) =
measure_us!(self.flush_cache(true, Some(footer)));
self.metrics.flush_cache_tick_us += flush_cache_and_tick_us;
flush_res?;
}
Ok(())
}
pub fn enable_alpenglow(&mut self) {
info!("Enabling Alpenglow, migrating poh to low power mode");
self.tick_cache = vec![];
{
let mut poh = self.poh.lock().unwrap();
let hashes_per_tick = None;
let current_hash = poh.hash;
poh.reset(current_hash, hashes_per_tick);
}
}
}
#[allow(clippy::type_complexity)]
fn do_create_test_recorder(
bank: Arc<Bank>,
blockstore: Arc<Blockstore>,
poh_config: Option<PohConfig>,
leader_schedule_cache: Option<Arc<LeaderScheduleCache>>,
track_transaction_indexes: bool,
) -> (
Arc<AtomicBool>,
Arc<RwLock<PohRecorder>>,
PohController,
TransactionRecorder,
PohService,
Receiver<WorkingBankEntryOrMarker>,
) {
let leader_schedule_cache = match leader_schedule_cache {
Some(provided_cache) => provided_cache,
None => Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
};
let exit = Arc::new(AtomicBool::new(false));
let poh_config = poh_config.unwrap_or_default();
let (poh_recorder, entry_receiver) = PohRecorder::new(
bank.tick_height(),
bank.last_blockhash(),
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
blockstore,
&leader_schedule_cache,
&poh_config,
exit.clone(),
);
let ticks_per_slot = bank.ticks_per_slot();
let (record_sender, record_receiver) = record_channels(track_transaction_indexes);
let transaction_recorder = TransactionRecorder::new(record_sender);
let poh_recorder = Arc::new(RwLock::new(poh_recorder));
let (mut poh_controller, poh_service_message_receiver) = PohController::new();
let (record_receiver_sender, _record_receiver_receiver) = bounded(1);
let poh_service = PohService::new(
poh_recorder.clone(),
&poh_config,
exit.clone(),
ticks_per_slot,
crate::poh_service::DEFAULT_PINNED_CPU_CORE,
crate::poh_service::DEFAULT_HASHES_PER_BATCH,
record_receiver,
poh_service_message_receiver,
Arc::new(MigrationStatus::default()),
record_receiver_sender,
);
poh_controller
.set_bank_sync(BankWithScheduler::new_without_scheduler(bank))
.unwrap();
(
exit,
poh_recorder,
poh_controller,
transaction_recorder,
poh_service,
entry_receiver,
)
}
#[allow(clippy::type_complexity)]
pub fn create_test_recorder(
bank: Arc<Bank>,
blockstore: Arc<Blockstore>,
poh_config: Option<PohConfig>,
leader_schedule_cache: Option<Arc<LeaderScheduleCache>>,
) -> (
Arc<AtomicBool>,
Arc<RwLock<PohRecorder>>,
PohController,
TransactionRecorder,
PohService,
Receiver<WorkingBankEntryOrMarker>,
) {
do_create_test_recorder(bank, blockstore, poh_config, leader_schedule_cache, false)
}
#[derive(Clone)]
pub struct SharedLeaderState(Arc<ArcSwap<LeaderState>>);
impl SharedLeaderState {
#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
fn new(
tick_height: u64,
leader_first_tick_height: Option<u64>,
next_leader_slot_range: Option<(Slot, Slot)>,
) -> Self {
let inner = LeaderState {
working_bank: None,
tick_height: AtomicU64::new(tick_height),
leader_first_tick_height,
next_leader_slot_range,
};
Self(Arc::new(ArcSwap::from_pointee(inner)))
}
pub fn load(&self) -> arc_swap::Guard<Arc<LeaderState>> {
self.0.load()
}
#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
fn store(&mut self, state: Arc<LeaderState>) {
self.0.store(state)
}
#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
fn increment_tick_height(&self) {
let inner = self.0.load();
inner.tick_height.fetch_add(1, Ordering::Release);
}
}
pub struct LeaderState {
working_bank: Option<Arc<Bank>>,
tick_height: AtomicU64,
leader_first_tick_height: Option<u64>,
next_leader_slot_range: Option<(Slot, Slot)>,
}
impl LeaderState {
#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
fn new(
working_bank: Option<Arc<Bank>>,
tick_height: u64,
leader_first_tick_height: Option<u64>,
next_leader_slot_range: Option<(u64, u64)>,
) -> Self {
Self {
working_bank,
tick_height: AtomicU64::new(tick_height),
leader_first_tick_height,
next_leader_slot_range,
}
}
pub fn working_bank(&self) -> Option<&Arc<Bank>> {
self.working_bank.as_ref()
}
pub fn tick_height(&self) -> u64 {
self.tick_height.load(Ordering::Acquire)
}
pub fn leader_first_tick_height(&self) -> Option<u64> {
self.leader_first_tick_height
}
pub fn next_leader_slot_range(&self) -> Option<(Slot, Slot)> {
self.next_leader_slot_range
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crossbeam_channel::bounded,
solana_clock::DEFAULT_TICKS_PER_SLOT,
solana_entry::entry::{self, EntrySlice},
solana_leader_schedule::SlotLeader,
solana_ledger::{
blockstore::Blockstore,
blockstore_meta::SlotMeta,
genesis_utils::{GenesisConfigInfo, create_genesis_config},
get_tmp_ledger_path_auto_delete,
},
solana_perf::test_tx::test_tx,
solana_sha256_hasher::hash,
solana_signer::Signer,
};
#[test]
fn test_poh_recorder_no_zero_tick() {
let prev_hash = Hash::default();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank,
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 1);
assert_eq!(poh_recorder.tick_cache[0].1, 1);
assert_eq!(poh_recorder.tick_height(), 1);
}
#[test]
fn test_poh_recorder_tick_height_is_last_tick() {
let prev_hash = Hash::default();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank,
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 2);
assert_eq!(poh_recorder.tick_cache[1].1, 2);
assert_eq!(poh_recorder.tick_height(), 2);
}
#[test]
fn test_poh_recorder_reset_clears_cache() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
Hash::default(),
bank0.clone(),
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 1);
poh_recorder.reset(bank0, Some((4, 4)));
assert_eq!(poh_recorder.tick_cache.len(), 0);
}
#[test]
fn test_poh_recorder_clear() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank);
assert!(poh_recorder.working_bank.is_some());
poh_recorder.clear_bank(true);
assert!(poh_recorder.working_bank.is_none());
}
#[test]
fn test_send_marker_error() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank);
drop(entry_receiver);
let marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
bank_hash: Hash::new_unique(),
block_producer_time_nanos: 0,
block_user_agent: vec![],
block_final_cert: None,
skip_reward_cert: None,
notar_reward_cert: None,
});
let err = poh_recorder.send_marker(marker).unwrap_err();
assert!(matches!(err, PohRecorderError::SendError(_)));
}
fn test_footer() -> BlockFooterV1 {
BlockFooterV1 {
bank_hash: Hash::default(),
block_producer_time_nanos: 0,
block_user_agent: vec![],
block_final_cert: None,
skip_reward_cert: None,
notar_reward_cert: None,
}
}
fn new_alpenglow_recorder_for_bank(
bank: Arc<Bank>,
blockstore: Arc<Blockstore>,
) -> (PohRecorder, Receiver<WorkingBankEntryOrMarker>) {
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
bank.tick_height(),
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
blockstore,
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.enable_alpenglow();
bank.set_tick_height(bank.max_tick_height() - 1);
poh_recorder.set_bank_for_test(bank);
(poh_recorder, entry_receiver)
}
#[test]
fn test_alpenglow_tick_send_error() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Arc::new(
Blockstore::open(ledger_path.path()).expect("Expected to be able to open ledger"),
);
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, entry_receiver) =
new_alpenglow_recorder_for_bank(bank.clone(), blockstore);
drop(entry_receiver);
let bank_to_freeze = bank.clone();
let freezer = std::thread::spawn(move || {
while bank_to_freeze.tick_height() < bank_to_freeze.max_tick_height() {
std::hint::spin_loop();
}
bank_to_freeze.freeze();
});
let err = poh_recorder
.tick_alpenglow(bank.max_tick_height(), test_footer())
.unwrap_err();
freezer.join().unwrap();
assert!(matches!(err, PohRecorderError::SendError(_)));
assert!(poh_recorder.has_bank());
assert!(!poh_recorder.tick_cache.is_empty());
}
#[test]
fn test_alpenglow_tick_timeout() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Arc::new(
Blockstore::open(ledger_path.path()).expect("Expected to be able to open ledger"),
);
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) =
new_alpenglow_recorder_for_bank(bank.clone(), blockstore);
let err = poh_recorder
.tick_alpenglow(bank.max_tick_height(), test_footer())
.unwrap_err();
assert!(matches!(
err,
PohRecorderError::BankFreezeTimeout(slot) if slot == bank.slot()
));
assert!(poh_recorder.has_bank());
assert!(!poh_recorder.tick_cache.is_empty());
}
#[test]
fn test_set_bank_resets_to_low_power_mode() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Arc::new(
Blockstore::open(ledger_path.path()).expect("Expected to be able to open ledger"),
);
let GenesisConfigInfo {
mut genesis_config, ..
} = create_genesis_config(2);
genesis_config.poh_config.hashes_per_tick = Some(4);
let (parent, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let child = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
parent.clone(),
SlotLeader::default(),
parent.slot() + 1,
);
child.set_hashes_per_tick(None);
child.set_tick_height(child.max_tick_height() - 1);
let prev_hash = parent.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
parent.tick_height(),
prev_hash,
parent.clone(),
Some((child.slot(), child.slot())),
parent.ticks_per_slot(),
blockstore,
&Arc::new(LeaderScheduleCache::new_from_bank(&parent)),
&genesis_config.poh_config,
Arc::new(AtomicBool::default()),
);
assert_eq!(
poh_recorder.poh.lock().unwrap().hashes_per_tick_config(),
Some(4)
);
poh_recorder.set_bank_for_test(child.clone());
assert_eq!(
poh_recorder.poh.lock().unwrap().hashes_per_tick_config(),
None
);
let bank_to_freeze = child.clone();
let freezer = std::thread::spawn(move || {
while bank_to_freeze.tick_height() < bank_to_freeze.max_tick_height() {
std::hint::spin_loop();
}
bank_to_freeze.freeze();
});
poh_recorder
.tick_alpenglow(child.max_tick_height(), test_footer())
.unwrap();
freezer.join().unwrap();
assert!(!poh_recorder.has_bank());
}
#[test]
fn test_poh_recorder_tick_verifies() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
Some((4, 4)),
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank0.clone());
let num_new_ticks = poh_recorder.tick_height() + bank0.ticks_per_slot();
for _ in 0..num_new_ticks {
poh_recorder.tick();
}
let mut entries = vec![];
while let Ok((_bank, (entry_or_marker, _tick_height))) = entry_receiver.try_recv() {
if let EntryOrMarker::Entry(entry) = entry_or_marker {
assert!(entry.is_tick());
entries.push(entry);
}
}
assert_eq!(entries.len(), num_new_ticks as usize);
assert!(
entries
.verify(&prev_hash, &entry::thread_pool_for_tests())
.status()
);
assert_eq!(entries[entries.len() - 1].hash, bank0.last_blockhash());
}
#[test]
fn test_poh_recorder_tick_sent_after_min() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank0, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
Some((4, 4)),
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
bank0.fill_bank_with_ticks_for_tests();
let bank1 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank0,
SlotLeader::default(),
1,
);
poh_recorder.set_bank_for_test(bank1.clone());
let num_new_ticks = bank1.tick_height() - poh_recorder.tick_height();
println!("{} {}", bank1.tick_height(), poh_recorder.tick_height());
assert!(num_new_ticks > 0);
for _ in 0..num_new_ticks {
poh_recorder.tick();
}
let min_tick_height = poh_recorder.working_bank.as_ref().unwrap().min_tick_height;
assert_eq!(min_tick_height, bank1.tick_height());
assert_eq!(poh_recorder.tick_height(), min_tick_height);
assert_eq!(poh_recorder.tick_cache.last().unwrap().1, num_new_ticks);
assert!(entry_receiver.try_recv().is_err());
let tick_height_before = poh_recorder.tick_height();
poh_recorder.tick();
assert_eq!(poh_recorder.tick_height(), tick_height_before + 1);
assert_eq!(poh_recorder.tick_cache.len(), 0);
let mut num_entries = 0;
while let Ok((wbank, (_entry, _tick_height))) = entry_receiver.try_recv() {
assert_eq!(wbank.slot(), bank1.slot());
num_entries += 1;
}
assert_eq!(num_entries, num_new_ticks + 1);
}
#[test]
fn test_poh_recorder_tick_sent_upto_and_including_max() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
for _ in 0..bank.max_tick_height() + 1 {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.tick_cache.last().unwrap().1,
bank.max_tick_height() + 1
);
assert_eq!(poh_recorder.tick_height(), bank.max_tick_height() + 1);
poh_recorder.set_bank_for_test(bank.clone());
poh_recorder.tick();
assert_eq!(poh_recorder.tick_height(), bank.max_tick_height() + 2);
assert!(poh_recorder.working_bank.is_none());
let mut num_entries = 0;
while entry_receiver.try_recv().is_ok() {
num_entries += 1;
}
assert_eq!(num_entries, bank.max_tick_height());
}
#[test]
fn test_poh_recorder_record_to_early() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank0, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
Some((4, 4)),
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
bank0.fill_bank_with_ticks_for_tests();
let bank1 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank0,
SlotLeader::default(),
1,
);
poh_recorder.set_bank_for_test(bank1.clone());
for _ in 0..bank1.tick_height() - 1 {
poh_recorder.tick()
}
let tx = test_tx();
let h1 = hash(b"hello world!");
assert_matches!(
poh_recorder.record(bank1.slot(), h1, vec![tx.into()]),
Err(PohRecorderError::MinHeightNotReached)
);
assert!(entry_receiver.try_recv().is_err());
}
#[test]
fn test_poh_recorder_record_bad_slot() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank.clone());
let tx = test_tx();
let h1 = hash(b"hello world!");
assert_eq!(
poh_recorder.tick_height(),
poh_recorder.working_bank.as_ref().unwrap().min_tick_height
);
let bad_slot = bank.slot() + 1;
assert_matches!(
poh_recorder.record(bad_slot, h1, vec![tx.into()]),
Err(PohRecorderError::MaxHeightReached)
);
}
#[test]
fn test_poh_recorder_record_at_min_passes() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank0, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
Some((4, 4)),
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
bank0.fill_bank_with_ticks_for_tests();
let bank1 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank0,
SlotLeader::default(),
1,
);
poh_recorder.set_bank_for_test(bank1.clone());
let min_tick_height = poh_recorder.working_bank.as_ref().unwrap().min_tick_height;
while poh_recorder.tick_height() < min_tick_height {
poh_recorder.tick();
}
assert_eq!(poh_recorder.tick_cache.len() as u64, min_tick_height);
assert_eq!(poh_recorder.tick_height(), min_tick_height);
let tx = test_tx();
let h1 = hash(b"hello world!");
assert!(
poh_recorder
.record(bank1.slot(), h1, vec![tx.into()])
.is_ok()
);
assert_eq!(poh_recorder.tick_cache.len(), 0);
for _ in 0..min_tick_height {
let (_bank, (e, _tick_height)) = entry_receiver.recv().unwrap();
assert!(e.unwrap_entry().is_tick());
}
let (_bank, (e, _tick_height)) = entry_receiver.recv().unwrap();
assert!(!e.unwrap_entry().is_tick());
}
#[test]
fn test_poh_recorder_record_at_max_fails() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank.clone());
let num_ticks_to_max = bank.max_tick_height() - poh_recorder.tick_height();
for _ in 0..num_ticks_to_max {
poh_recorder.tick();
}
let tx = test_tx();
let h1 = hash(b"hello world!");
assert!(
poh_recorder
.record(bank.slot(), h1, vec![tx.into()])
.is_err()
);
for _ in 0..num_ticks_to_max {
let (_bank, (entry, _tick_height)) = entry_receiver.recv().unwrap();
assert!(entry.unwrap_entry().is_tick());
}
}
#[test]
fn test_poh_cache_on_disconnect() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank0, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
Some((4, 4)),
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
bank0.fill_bank_with_ticks_for_tests();
let bank1 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank0,
SlotLeader::default(),
1,
);
poh_recorder.set_bank_for_test(bank1);
let remaining_ticks_to_min = poh_recorder.working_bank.as_ref().unwrap().min_tick_height
- poh_recorder.tick_height();
for _ in 0..remaining_ticks_to_min {
poh_recorder.tick();
}
assert_eq!(poh_recorder.tick_height(), remaining_ticks_to_min);
assert_eq!(
poh_recorder.tick_cache.len(),
remaining_ticks_to_min as usize
);
assert!(poh_recorder.working_bank.is_some());
drop(entry_receiver);
poh_recorder.tick();
assert!(poh_recorder.working_bank.is_none());
assert_eq!(
poh_recorder.tick_cache.len(),
remaining_ticks_to_min as usize + 1
);
}
#[test]
fn test_reset_current() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
Hash::default(),
bank.clone(),
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 2);
poh_recorder.reset(bank, Some((4, 4)));
assert_eq!(poh_recorder.tick_cache.len(), 0);
}
#[test]
fn test_reset_with_cached() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
Hash::default(),
bank.clone(),
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 2);
poh_recorder.reset(bank, Some((4, 4)));
assert_eq!(poh_recorder.tick_cache.len(), 0);
}
#[test]
fn test_reset_to_new_value() {
agave_logger::setup();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
Hash::default(),
bank.clone(),
Some((4, 4)),
DEFAULT_TICKS_PER_SLOT,
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.tick();
poh_recorder.tick();
poh_recorder.tick();
poh_recorder.tick();
assert_eq!(poh_recorder.tick_cache.len(), 4);
assert_eq!(poh_recorder.tick_height(), 4);
poh_recorder.reset(bank, Some((4, 4))); assert_eq!(poh_recorder.tick_cache.len(), 0);
poh_recorder.tick();
assert_eq!(poh_recorder.tick_height(), DEFAULT_TICKS_PER_SLOT + 1);
}
#[test]
fn test_reset_clear_bank() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
Hash::default(),
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank.clone());
assert_eq!(bank.slot(), 0);
poh_recorder.reset(bank, Some((4, 4)));
assert!(poh_recorder.working_bank.is_none());
}
#[test]
pub fn test_clear_signal() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let (sender, receiver) = bounded(1);
let (mut poh_recorder, _entry_receiver) = PohRecorder::new_with_clear_signal(
0,
Hash::default(),
bank.clone(),
None,
bank.ticks_per_slot(),
false,
Arc::new(blockstore),
Some(sender),
&Arc::new(LeaderScheduleCache::default()),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank);
let shared_leader_state = poh_recorder.shared_leader_state();
assert!(shared_leader_state.load().working_bank().is_some());
poh_recorder.clear_bank(true);
assert!(shared_leader_state.load().working_bank().is_none());
assert!(receiver.try_recv().is_ok());
}
#[test]
fn test_poh_recorder_record_sets_start_slot() {
agave_logger::setup();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let ticks_per_slot = 5;
let GenesisConfigInfo {
mut genesis_config, ..
} = create_genesis_config(2);
genesis_config.ticks_per_slot = ticks_per_slot;
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
poh_recorder.set_bank_for_test(bank.clone());
let max_tick_height = poh_recorder.working_bank.as_ref().unwrap().max_tick_height;
for _ in 0..3 * max_tick_height {
poh_recorder.tick();
}
let tx = test_tx();
let h1 = hash(b"hello world!");
assert!(
poh_recorder
.record(bank.slot(), h1, vec![tx.into()])
.is_err()
);
assert!(poh_recorder.working_bank.is_none());
assert_eq!(poh_recorder.start_slot(), bank.slot());
}
#[test]
fn test_current_poh_slot() {
let genesis_config = create_genesis_config(2).genesis_config;
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let last_entry_hash = bank.last_blockhash();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank);
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
last_entry_hash,
bank.clone(),
None,
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(leader_schedule_cache),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
assert_eq!(0, poh_recorder.current_poh_slot());
poh_recorder.reset(bank.clone(), None);
assert_eq!(bank.slot() + 1, poh_recorder.current_poh_slot());
for _ in 0..bank.ticks_per_slot() - 1 {
poh_recorder.tick();
assert_eq!(bank.slot() + 1, poh_recorder.current_poh_slot());
}
poh_recorder.tick();
assert_eq!(bank.slot() + 2, poh_recorder.current_poh_slot());
}
#[test]
fn test_reached_leader_tick() {
agave_logger::setup();
let GenesisConfigInfo {
genesis_config,
validator_pubkey,
voting_keypair,
..
} = create_genesis_config(2);
let (mut bank, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank.last_blockhash();
let leader_a = SlotLeader {
id: validator_pubkey,
vote_address: voting_keypair.pubkey(),
};
let leader_b = SlotLeader::new_unique();
let leader_c = SlotLeader::new_unique();
let leader_d = SlotLeader::new_unique();
let slot_leaders = vec![leader_a, leader_b, leader_c, leader_d];
let mut leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank);
let fixed_schedule = solana_leader_schedule::FixedSchedule {
leader_schedule: Arc::new(solana_leader_schedule::LeaderSchedule::new_from_schedule(
slot_leaders,
NUM_CONSECUTIVE_LEADER_SLOTS,
)),
};
leader_schedule_cache.set_fixed_leader_schedule(Some(fixed_schedule));
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
None,
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(leader_schedule_cache),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
let ticks_per_slot = bank.ticks_per_slot();
let grace_ticks = ticks_per_slot * MAX_GRACE_SLOTS;
poh_recorder.grace_ticks = grace_ticks;
let num_consecutive_leader_slots = NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64;
let leader_a_start_slot = 0;
let leader_a_end_slot = leader_a_start_slot + num_consecutive_leader_slots - 1;
let leader_b_start_slot = leader_a_end_slot + 1;
let leader_b_end_slot = leader_b_start_slot + num_consecutive_leader_slots - 1;
let leader_c_start_slot = leader_b_end_slot + 1;
let leader_c_end_slot = leader_c_start_slot + num_consecutive_leader_slots - 1;
let leader_d_start_slot = leader_c_end_slot + 1;
let leader_d_end_slot = leader_d_start_slot + num_consecutive_leader_slots - 1;
poh_recorder.reset(
bank.clone(),
Some((leader_a_start_slot + 1, leader_a_end_slot)),
);
let ticks_in_leader_slot_set = ticks_per_slot * num_consecutive_leader_slots;
let leader_a_start_tick = 1;
let leader_b_start_tick = leader_a_start_tick + ticks_in_leader_slot_set;
let leader_c_start_tick = leader_b_start_tick + ticks_in_leader_slot_set;
let leader_d_start_tick = leader_c_start_tick + ticks_in_leader_slot_set;
assert!(poh_recorder.reached_leader_tick(&leader_a.id, leader_a_start_tick));
for _ in 0..grace_ticks {
poh_recorder.tick();
}
assert!(poh_recorder.reached_leader_tick(&leader_a.id, leader_a_start_tick));
poh_recorder.reset(bank.clone(), Some((leader_b_start_slot, leader_b_end_slot)));
assert!(!poh_recorder.reached_leader_tick(&leader_b.id, leader_b_start_tick));
for _ in poh_recorder.tick_height()..ticks_in_leader_slot_set {
poh_recorder.tick();
}
assert!(!poh_recorder.reached_leader_tick(&leader_b.id, leader_b_start_tick));
for _ in leader_a_start_slot + 1..leader_b_start_slot {
let child_slot = bank.slot() + 1;
bank = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank,
leader_a,
child_slot,
);
}
poh_recorder.reset(bank.clone(), Some((leader_b_start_slot, leader_b_end_slot)));
assert!(poh_recorder.reached_leader_tick(&leader_b.id, leader_b_start_tick));
poh_recorder.reset(
bank.clone(),
Some((leader_b_start_slot + 1, leader_b_end_slot)),
);
for _ in 0..ticks_per_slot {
poh_recorder.tick();
}
assert!(poh_recorder.reached_leader_tick(&leader_b.id, leader_b_start_tick));
let child_slot = bank.slot() + 1;
bank =
Bank::new_from_parent_with_bank_forks(bank_forks.as_ref(), bank, leader_b, child_slot);
poh_recorder.reset(bank, Some((leader_d_start_slot, leader_d_end_slot)));
for _ in ticks_per_slot..ticks_in_leader_slot_set {
poh_recorder.tick();
}
for _ in 0..ticks_in_leader_slot_set {
poh_recorder.tick();
}
assert!(poh_recorder.reached_leader_tick(&leader_d.id, leader_d_start_tick));
let active_descendants = vec![NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64];
poh_recorder.update_start_bank_active_descendants(&active_descendants);
assert!(poh_recorder.reached_leader_tick(&leader_d.id, leader_d_start_tick));
poh_recorder.delay_leader_block_for_pending_fork = true;
assert!(!poh_recorder.reached_leader_tick(&leader_d.id, leader_d_start_tick));
}
#[test]
fn test_reached_leader_slot() {
agave_logger::setup();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo {
genesis_config,
validator_pubkey,
..
} = create_genesis_config(2);
let (bank0, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank0.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank0.clone(),
None,
bank0.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank0)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
assert_eq!(
poh_recorder.reached_leader_slot(&validator_pubkey),
PohLeaderStatus::NotReached
);
assert_eq!(bank0.slot(), 0);
poh_recorder.reset(bank0.clone(), None);
assert_eq!(
poh_recorder.reached_leader_slot(&validator_pubkey),
PohLeaderStatus::NotReached
);
poh_recorder.reset(bank0.clone(), Some((2, 2)));
let init_ticks = poh_recorder.tick_height();
for _ in 0..bank0.ticks_per_slot() {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.tick_height(),
init_ticks + bank0.ticks_per_slot()
);
let parent_meta = SlotMeta {
received: 1,
..SlotMeta::default()
};
poh_recorder.blockstore.put_meta(0, &parent_meta).unwrap();
let test_validator_pubkey = Pubkey::new_unique();
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::NotReached
);
let bank1 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank0.clone(),
SlotLeader::default(),
1,
);
assert_eq!(bank1.slot(), 1);
poh_recorder.reset(bank1.clone(), Some((2, 2)));
assert_eq!(
poh_recorder.reached_leader_slot(&validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 2,
parent_slot: 1,
}
);
poh_recorder.reset(bank1.clone(), Some((3, 3)));
for _ in 0..bank1.ticks_per_slot() {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::NotReached
);
assert_eq!(
poh_recorder.reached_leader_slot(bank1.leader_id()),
PohLeaderStatus::Reached {
poh_slot: 3,
parent_slot: 1
}
);
for _ in 0..bank1.ticks_per_slot() / GRACE_TICKS_FACTOR {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 3,
parent_slot: 1,
}
);
let bank2 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank1.clone(),
SlotLeader::default(),
2,
);
poh_recorder.reset(bank2.clone(), Some((4, 4)));
for _ in 0..bank1.ticks_per_slot() {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::NotReached
);
let bank3 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank2.clone(),
SlotLeader::default(),
3,
);
assert_eq!(bank3.slot(), 3);
poh_recorder.reset(bank3.clone(), Some((4, 4)));
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 4,
parent_slot: 3,
}
);
let bank4 = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank3.clone(),
SlotLeader::default(),
4,
);
poh_recorder.reset(bank4.clone(), Some((5, 5)));
let overshoot_factor = 4;
for _ in 0..overshoot_factor * bank4.ticks_per_slot() {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 9,
parent_slot: 4,
}
);
{
poh_recorder.reset(bank4.clone(), Some((9, 9)));
for _ in 0..4 * bank4.ticks_per_slot() {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 9,
parent_slot: 4,
}
);
poh_recorder.update_start_bank_active_descendants(&[5]);
assert!(poh_recorder.is_new_reset_bank_pending(8));
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 9,
parent_slot: 4,
}
);
poh_recorder.delay_leader_block_for_pending_fork = true;
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::NotReached,
);
for _ in 0..poh_recorder.grace_ticks {
poh_recorder.tick();
}
assert_eq!(
poh_recorder.reached_leader_slot(&test_validator_pubkey),
PohLeaderStatus::Reached {
poh_slot: 9,
parent_slot: 4,
}
);
}
}
#[test]
fn test_would_be_leader_soon() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let prev_hash = bank.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
prev_hash,
bank.clone(),
None,
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
assert!(!poh_recorder.would_be_leader(2 * bank.ticks_per_slot()));
assert_eq!(bank.slot(), 0);
poh_recorder.reset(bank.clone(), None);
assert!(!poh_recorder.would_be_leader(2 * bank.ticks_per_slot()));
let bank_slot = bank.slot() + 3;
poh_recorder.reset(bank.clone(), Some((bank_slot, bank_slot)));
assert!(!poh_recorder.would_be_leader(2 * bank.ticks_per_slot()));
assert!(poh_recorder.would_be_leader(3 * bank.ticks_per_slot()));
assert!(!poh_recorder.would_be_leader(2 * bank.ticks_per_slot()));
let bank = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank,
SlotLeader::default(),
1,
); poh_recorder.set_bank_for_test(bank.clone());
assert!(poh_recorder.would_be_leader(2 * bank.ticks_per_slot()));
}
#[test]
fn test_flush_virtual_ticks() {
let ledger_path = get_tmp_ledger_path_auto_delete!();
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2);
let (bank, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let genesis_hash = bank.last_blockhash();
let (mut poh_recorder, _entry_receiver) = PohRecorder::new(
0,
bank.last_blockhash(),
bank.clone(),
Some((2, 2)),
bank.ticks_per_slot(),
Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&PohConfig::default(),
Arc::new(AtomicBool::default()),
);
let bank = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank,
SlotLeader::default(),
2,
); for _ in 0..(bank.ticks_per_slot() * 3) {
poh_recorder.tick();
}
poh_recorder.set_bank_for_test(bank.clone());
assert!(!bank.is_hash_valid_for_age(&genesis_hash, 0));
assert!(bank.is_hash_valid_for_age(&genesis_hash, 1));
}
#[test]
fn test_compute_leader_slot_tick_heights() {
assert_eq!(
PohRecorder::compute_leader_slot_tick_heights(None, 0),
(None, 0, 0)
);
assert_eq!(
PohRecorder::compute_leader_slot_tick_heights(Some((4, 4)), 8),
(Some(33), 40, 4)
);
assert_eq!(
PohRecorder::compute_leader_slot_tick_heights(Some((4, 7)), 8),
(Some(33), 64, 2 * 8)
);
assert_eq!(
PohRecorder::compute_leader_slot_tick_heights(Some((6, 7)), 8),
(Some(49), 64, 8)
);
assert_eq!(
PohRecorder::compute_leader_slot_tick_heights(Some((6, 7)), 4),
(Some(25), 32, 4)
);
}
}