From 37b89448a6ceb931f574cfb82b69ae092205658e Mon Sep 17 00:00:00 2001 From: ksn6 <2163784+ksn6@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:05:58 -0400 Subject: [PATCH 1/2] feat(alpenglow): introduce alpenclock account to the bank --- runtime/src/bank.rs | 59 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0b5efa051f4c21..c7770e5030c9b6 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -129,10 +129,11 @@ use { loaded_programs::{ProgramCacheEntry, ProgramRuntimeEnvironments}, }, solana_pubkey::{Pubkey, PubkeyHasherBuilder}, + solana_rent::Rent, solana_runtime_transaction::{ runtime_transaction::RuntimeTransaction, transaction_with_meta::TransactionWithMeta, }, - solana_sdk_ids::{bpf_loader_upgradeable, incinerator, native_loader}, + solana_sdk_ids::{bpf_loader_upgradeable, incinerator, native_loader, system_program}, solana_sha256_hasher::hashv, solana_signature::Signature, solana_slot_hashes::SlotHashes, @@ -190,7 +191,7 @@ use { AtomicBool, AtomicI64, AtomicU64, Ordering::{self, AcqRel, Acquire, Relaxed}, }, - Arc, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak, + Arc, LazyLock, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak, }, time::{Duration, Instant}, }, @@ -234,6 +235,14 @@ pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; /// only the top 2000 validators by stake will be present in vote account structures. pub const MAX_ALPENGLOW_VOTE_ACCOUNTS: usize = 2000; +/// The off-curve account where we store the Alpenglow clock. The clock sysvar has seconds +/// resolution while the Alpenglow clock has nanosecond resolution. +static NANOSECOND_CLOCK_ACCOUNT: LazyLock = LazyLock::new(|| { + let (pubkey, _) = + Pubkey::find_program_address(&[b"alpenclock"], &agave_feature_set::alpenglow::id()); + pubkey +}); + pub type BankStatusCache = StatusCache>; #[cfg_attr( feature = "frozen-abi", @@ -2150,6 +2159,52 @@ impl Bank { .unwrap_or_default() } + pub fn update_clock_from_footer(&self, unix_timestamp_nanos: i64) { + // On epoch boundaries, update epoch_start_timestamp + let epoch_start_timestamp = match (self.slot, self.parent()) { + (0, _) => self.unix_timestamp_from_genesis(), + (_, Some(parent)) if parent.epoch() != self.epoch() => { + unix_timestamp_nanos / 1_000_000_000 + } + _ => self.clock().epoch_start_timestamp, + }; + + // Update clock sysvar + // NOTE: block footer UNIX timestamps are in nanoseconds, but clock sysvar stores timestamps + // in seconds + let clock = sysvar::clock::Clock { + slot: self.slot, + epoch_start_timestamp, + epoch: self.epoch_schedule().get_epoch(self.slot), + leader_schedule_epoch: self.epoch_schedule().get_leader_schedule_epoch(self.slot), + unix_timestamp: unix_timestamp_nanos / 1_000_000_000, + }; + self.update_sysvar_account(&sysvar::clock::id(), |account| { + create_account( + &clock, + self.inherit_specially_retained_account_fields(account), + ) + }); + + // Update Alpenglow clock + let alpenclock_size = wincode::serialized_size(&unix_timestamp_nanos).unwrap(); + let lamports = Rent::default().minimum_balance(alpenclock_size as usize); + let alpenclock_account_data = + AccountSharedData::new_data(lamports, &unix_timestamp_nanos, &system_program::ID) + .unwrap(); + self.store_account_and_update_capitalization( + &NANOSECOND_CLOCK_ACCOUNT, + &alpenclock_account_data, + ); + } + + pub fn get_nanosecond_clock(&self) -> Option { + self.get_account(&NANOSECOND_CLOCK_ACCOUNT).map(|acct| { + acct.deserialize_data() + .expect("Couldn't deserialize nanosecond resolution clock") + }) + } + fn update_clock(&self, parent_epoch: Option) { let mut unix_timestamp = self.clock().unix_timestamp; // set epoch_start_timestamp to None to warp timestamp From 689566338dd5eb1baa4d99a32b3aea0bca9f47bd Mon Sep 17 00:00:00 2001 From: ksn6 <2163784+ksn6@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:24:26 -0400 Subject: [PATCH 2/2] wincode dependency --- Cargo.lock | 1 + dev-bins/Cargo.lock | 1 + programs/sbf/Cargo.lock | 1 + runtime/Cargo.toml | 1 + runtime/src/bank.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 5dfe36772fc162..935b17d340abf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10387,6 +10387,7 @@ dependencies = [ "tempfile", "test-case", "thiserror 2.0.18", + "wincode 0.4.4", ] [[package]] diff --git a/dev-bins/Cargo.lock b/dev-bins/Cargo.lock index 1177608e689b6e..95e6dd55789dcd 100644 --- a/dev-bins/Cargo.lock +++ b/dev-bins/Cargo.lock @@ -8661,6 +8661,7 @@ dependencies = [ "symlink", "tempfile", "thiserror 2.0.18", + "wincode 0.4.4", ] [[package]] diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index c3bc4210e102b7..3804f05b474e94 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -8514,6 +8514,7 @@ dependencies = [ "symlink", "tempfile", "thiserror 2.0.18", + "wincode 0.4.4", ] [[package]] diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index a123d6fab3afe5..ef5cd65e82f57d 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -188,6 +188,7 @@ strum_macros = { workspace = true } symlink = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } +wincode = { workspace = true } [dev-dependencies] agave-logger = { workspace = true } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index c7770e5030c9b6..42b72cdb0e48ff 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -195,6 +195,7 @@ use { }, time::{Duration, Instant}, }, + wincode, }; #[cfg(feature = "dev-context-only-utils")] use {