Skip to content

Commit d88a7b3

Browse files
authored
feat: update clock sysvar in BlockComponentProcessor (#595)
#### Problem and Summary of Changes Now that we can process `BlockComponent`s during replay, update the clock sysvar when we observe the block footer. This PR exposes the Alpenglow clock to `BlockComponentProcessor`, which we'll next modify to enforce the Alpenglow clock bounds outlined in solana-foundation/solana-improvement-documents#363.
1 parent 63f2784 commit d88a7b3

File tree

4 files changed

+245
-86
lines changed

4 files changed

+245
-86
lines changed

core/src/block_creation_loop.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use {
1111
},
1212
crossbeam_channel::Receiver,
1313
solana_clock::Slot,
14+
solana_entry::block_component::BlockFooterV1,
1415
solana_gossip::cluster_info::ClusterInfo,
1516
solana_hash::Hash,
1617
solana_ledger::{
@@ -28,7 +29,9 @@ use {
2829
solana_runtime::{
2930
bank::{Bank, NewBankOptions},
3031
bank_forks::BankForks,
32+
block_component_processor::BlockComponentProcessor,
3133
},
34+
solana_version::version,
3235
solana_votor::{common::block_timeout, event::LeaderWindowInfo},
3336
stats::{BlockCreationLoopMetrics, SlotMetrics},
3437
std::{
@@ -37,7 +40,7 @@ use {
3740
Arc, Condvar, Mutex, RwLock,
3841
},
3942
thread::{self, Builder, JoinHandle},
40-
time::{Duration, Instant},
43+
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
4144
},
4245
thiserror::Error,
4346
};
@@ -344,6 +347,21 @@ fn produce_window(
344347
Ok(())
345348
}
346349

350+
/// Produces a block footer with the current timestamp and version information.
351+
/// The bank_hash field is left as default and will be filled in after the bank freezes.
352+
fn produce_block_footer(block_producer_start_time: SystemTime) -> BlockFooterV1 {
353+
let block_producer_time_nanos = block_producer_start_time
354+
.duration_since(UNIX_EPOCH)
355+
.expect("Misconfigured system clock; couldn't measure block producer time.")
356+
.as_nanos() as u64;
357+
358+
BlockFooterV1 {
359+
bank_hash: Hash::default(),
360+
block_producer_time_nanos,
361+
block_user_agent: format!("agave/{}", version!()).into_bytes(),
362+
}
363+
}
364+
347365
/// Records incoming transactions until we reach the block timeout.
348366
/// Afterwards:
349367
/// - Shutdown the record receiver
@@ -404,8 +422,18 @@ fn record_and_complete_block(
404422
// will properly increment the tick_height to max_tick_height.
405423
bank.set_tick_height(max_tick_height - 1);
406424
// Write the single tick for this slot
425+
426+
// Produce the footer with the current timestamp
427+
let working_bank = w_poh_recorder.working_bank().unwrap();
428+
let footer = produce_block_footer(*working_bank.start);
429+
430+
BlockComponentProcessor::update_bank_with_footer(
431+
working_bank.bank.clone_without_scheduler(),
432+
&footer,
433+
);
434+
407435
drop(bank);
408-
w_poh_recorder.tick_alpenglow(max_tick_height);
436+
w_poh_recorder.tick_alpenglow(max_tick_height, footer);
409437

410438
Ok(())
411439
}

poh/src/poh_recorder.rs

Lines changed: 72 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@ use {
3636
solana_pubkey::Pubkey,
3737
solana_runtime::{bank::Bank, installed_scheduler_pool::BankWithScheduler},
3838
solana_transaction::versioned::VersionedTransaction,
39-
solana_version::version,
4039
solana_votor_messages::migration::MigrationStatus,
4140
std::{
4241
cmp,
4342
sync::{
4443
atomic::{AtomicBool, AtomicU64, Ordering},
4544
Arc, Mutex, RwLock,
4645
},
47-
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
46+
time::{Duration, Instant, SystemTime},
4847
},
4948
thiserror::Error,
5049
};
@@ -350,7 +349,7 @@ impl PohRecorder {
350349
self.metrics.report_metrics_us += report_metrics_us;
351350

352351
loop {
353-
let (flush_cache_res, flush_cache_us) = measure_us!(self.flush_cache(false, false));
352+
let (flush_cache_res, flush_cache_us) = measure_us!(self.flush_cache(false, None));
354353
self.metrics.flush_cache_no_tick_us += flush_cache_us;
355354
flush_cache_res?;
356355

@@ -437,7 +436,7 @@ impl PohRecorder {
437436
self.tick_height(),
438437
));
439438

440-
let (_flush_res, flush_cache_and_tick_us) = measure_us!(self.flush_cache(true, false));
439+
let (_flush_res, flush_cache_and_tick_us) = measure_us!(self.flush_cache(true, None));
441440
self.metrics.flush_cache_tick_us += flush_cache_and_tick_us;
442441

443442
let (_, sleep_us) = measure_us!({
@@ -484,7 +483,7 @@ impl PohRecorder {
484483

485484
// TODO: adjust the working_bank.start time based on number of ticks
486485
// that have already elapsed based on current tick height.
487-
let _ = self.flush_cache(false, false);
486+
let _ = self.flush_cache(false, None);
488487
}
489488

490489
fn clear_bank(&mut self) {
@@ -568,10 +567,67 @@ impl PohRecorder {
568567
self.start_tick_height = self.tick_height() + 1;
569568
}
570569

570+
/// Waits for the bank to freeze and sends the block footer with the bank hash.
571+
/// Returns:
572+
/// - Ok(()): Footer sent successfully
573+
/// - Err(None): Bank freeze timeout (caller should break without updating send_result)
574+
/// - Err(Some(e)): Send failed (caller should update send_result and break)
575+
fn wait_for_freeze_and_send_footer(
576+
&self,
577+
footer: &BlockFooterV1,
578+
working_bank: &WorkingBank,
579+
) -> std::result::Result<(), Option<SendError<WorkingBankEntryMarker>>> {
580+
// Wait for the bank to be frozen with timeout
581+
// TODO: change this to use DELTA_BLOCK from votor instead.
582+
let start = Instant::now();
583+
while !working_bank.bank.is_frozen() {
584+
if start.elapsed() > Duration::from_millis(400) {
585+
break;
586+
}
587+
std::hint::spin_loop();
588+
}
589+
590+
// If the bank still isn't frozen, we've timed out
591+
if !working_bank.bank.is_frozen() {
592+
error!(
593+
"slot = {} block production failure. bank freezing timed out.",
594+
working_bank.bank.slot()
595+
);
596+
return Err(None);
597+
}
598+
599+
// Send out the block footer - we now have the bank hash
600+
let mut footer = footer.clone();
601+
footer.bank_hash = working_bank.bank.hash();
602+
603+
let footer = VersionedBlockFooter::Current(footer.clone());
604+
let footer = BlockMarkerV1::BlockFooter(footer);
605+
let footer = VersionedBlockMarker::Current(footer);
606+
607+
let footer_entry_marker = (
608+
EntryMarker::Marker(footer),
609+
working_bank.max_tick_height - 1,
610+
);
611+
612+
let send_result = self
613+
.working_bank_sender
614+
.send((working_bank.bank.clone(), footer_entry_marker));
615+
616+
if send_result.is_err() {
617+
error!(
618+
"slot = {} block production failure. failed to broadcast footer",
619+
working_bank.bank.slot()
620+
);
621+
return Err(send_result.err());
622+
}
623+
624+
Ok(())
625+
}
626+
571627
// Flush cache will delay flushing the cache for a bank until it past the WorkingBank::min_tick_height
572628
// On a record flush will flush the cache at the WorkingBank::min_tick_height, since a record
573629
// occurs after the min_tick_height was generated
574-
fn flush_cache(&mut self, tick: bool, is_alpentick: bool) -> Result<()> {
630+
fn flush_cache(&mut self, tick: bool, footer: Option<BlockFooterV1>) -> Result<()> {
575631
// check_tick_height is called before flush cache, so it cannot overrun the bank
576632
// so a bank that is so late that it's slot fully generated before it starts recording
577633
// will fail instead of broadcasting any ticks
@@ -605,43 +661,15 @@ impl PohRecorder {
605661
for (entry, tick_height) in &self.tick_cache[..entry_count] {
606662
working_bank.bank.register_tick(&entry.hash);
607663

608-
if is_alpentick {
609-
// Wait for the bank to be frozen with timeout
610-
// TODO: change this to use DELTA_BLOCK from votor instead.
611-
let start = Instant::now();
612-
while !working_bank.bank.is_frozen() {
613-
if start.elapsed() > Duration::from_millis(400) {
664+
if let Some(footer) = footer.as_ref() {
665+
match self.wait_for_freeze_and_send_footer(footer, working_bank) {
666+
Ok(()) => {} // Continue processing
667+
Err(None) => break, // Timeout - break without updating send_result
668+
Err(Some(e)) => {
669+
// Send failed - update send_result and break
670+
send_result = Err(e);
614671
break;
615672
}
616-
std::hint::spin_loop();
617-
}
618-
619-
// If the bank still isn't frozen, we've timed out
620-
if !working_bank.bank.is_frozen() {
621-
error!(
622-
"slot = {} block production failure. bank freezing timed out.",
623-
working_bank.bank.slot()
624-
);
625-
break;
626-
}
627-
628-
// Send out the block footer
629-
let footer = self.produce_block_footer(working_bank);
630-
let footer_entry_marker = (
631-
EntryMarker::Marker(footer),
632-
working_bank.max_tick_height - 1,
633-
);
634-
635-
send_result = self
636-
.working_bank_sender
637-
.send((working_bank.bank.clone(), footer_entry_marker));
638-
639-
if send_result.is_err() {
640-
error!(
641-
"slot = {} block production failure. failed to broadcast footer",
642-
working_bank.bank.slot()
643-
);
644-
break;
645673
}
646674
}
647675

@@ -977,34 +1005,7 @@ impl PohRecorder {
9771005
self.clear_bank();
9781006
}
9791007

980-
pub fn working_bank_block_producer_time_nanos(&self) -> u64 {
981-
self.working_bank()
982-
.unwrap()
983-
.start
984-
.duration_since(UNIX_EPOCH)
985-
.expect("Misconfigured system clock; couldn't measure block producer time.")
986-
.as_nanos() as u64
987-
}
988-
989-
fn produce_block_footer(&self, working_bank: &WorkingBank) -> VersionedBlockMarker {
990-
if !working_bank.bank.is_frozen() {
991-
let slot = working_bank.bank.slot();
992-
error!("slot = {slot} creating a block footer with a non-frozen bank! ");
993-
}
994-
995-
let footer = BlockFooterV1 {
996-
bank_hash: working_bank.bank.hash(),
997-
block_producer_time_nanos: self.working_bank_block_producer_time_nanos(),
998-
block_user_agent: format!("agave/{}", version!()).into_bytes(),
999-
};
1000-
1001-
let footer = VersionedBlockFooter::Current(footer);
1002-
let footer = BlockMarkerV1::BlockFooter(footer);
1003-
1004-
VersionedBlockMarker::Current(footer)
1005-
}
1006-
1007-
pub fn tick_alpenglow(&mut self, slot_max_tick_height: u64) {
1008+
pub fn tick_alpenglow(&mut self, slot_max_tick_height: u64, footer: BlockFooterV1) {
10081009
let (poh_entry, tick_lock_contention_us) = measure_us!({
10091010
let mut poh_l = self.poh.lock().unwrap();
10101011
poh_l.tick()
@@ -1027,7 +1028,8 @@ impl PohRecorder {
10271028
self.tick_height.load(),
10281029
));
10291030

1030-
let (_flush_res, flush_cache_and_tick_us) = measure_us!(self.flush_cache(true, true));
1031+
let (_flush_res, flush_cache_and_tick_us) =
1032+
measure_us!(self.flush_cache(true, Some(footer)));
10311033
self.metrics.flush_cache_tick_us += flush_cache_and_tick_us;
10321034
}
10331035
}

runtime/src/bank.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,12 @@ impl Bank {
13911391
let (_, update_sysvars_time_us) = measure_us!({
13921392
new.update_slot_hashes();
13931393
new.update_stake_history(Some(parent.epoch()));
1394-
new.update_clock(Some(parent.epoch()));
1394+
1395+
// If Alpenglow is enabled, update the clock from the footer.
1396+
if new.get_alpenglow_genesis_certificate().is_none() {
1397+
new.update_clock(Some(parent.epoch()));
1398+
}
1399+
13951400
new.update_last_restart_slot()
13961401
});
13971402

@@ -2021,6 +2026,37 @@ impl Bank {
20212026
.unwrap_or_default()
20222027
}
20232028

2029+
pub fn update_clock_from_footer(&self, unix_timestamp_nanos: i64) {
2030+
let mut epoch_start_timestamp =
2031+
// On epoch boundaries, update epoch_start_timestamp
2032+
if self.parent().is_some() && self.parent().unwrap().epoch() != self.epoch() {
2033+
unix_timestamp_nanos / 1_000_000_000
2034+
} else {
2035+
self.clock().epoch_start_timestamp
2036+
};
2037+
2038+
if self.slot == 0 {
2039+
epoch_start_timestamp = self.unix_timestamp_from_genesis();
2040+
}
2041+
2042+
// Update clock sysvar
2043+
// NOTE: block footer UNIX timestamps are in nanoseconds, but clock sysvar stores timestamps
2044+
// in seconds
2045+
let clock = sysvar::clock::Clock {
2046+
slot: self.slot,
2047+
epoch_start_timestamp,
2048+
epoch: self.epoch_schedule().get_epoch(self.slot),
2049+
leader_schedule_epoch: self.epoch_schedule().get_leader_schedule_epoch(self.slot),
2050+
unix_timestamp: unix_timestamp_nanos / 1_000_000_000,
2051+
};
2052+
self.update_sysvar_account(&sysvar::clock::id(), |account| {
2053+
create_account(
2054+
&clock,
2055+
self.inherit_specially_retained_account_fields(account),
2056+
)
2057+
});
2058+
}
2059+
20242060
fn update_clock(&self, parent_epoch: Option<Epoch>) {
20252061
let mut unix_timestamp = self.clock().unix_timestamp;
20262062
// set epoch_start_timestamp to None to warp timestamp

0 commit comments

Comments
 (0)