diff --git a/Cargo.lock b/Cargo.lock index 92dcf3455b..79f2df15ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3469,6 +3469,7 @@ name = "solana-genesis" version = "0.22.0" dependencies = [ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index d4d244b99e..e88cd2205c 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -11,6 +11,7 @@ homepage = "https://solana.com/" [dependencies] base64 = "0.11.0" clap = "2.33.0" +chrono = "0.4" hex = "0.4.0" serde = "1.0.103" serde_derive = "1.0.103" diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 9c873fd54d..a9e1d0a8b8 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -1,8 +1,8 @@ //! A command-line executable for generating the chain's genesis config. +use chrono::DateTime; use clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches}; -use solana_clap_utils::input_parsers::pubkey_of; -use solana_clap_utils::input_validators::is_valid_percentage; +use solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_valid_percentage}; use solana_genesis::{genesis_accounts::add_genesis_accounts, Base64Account}; use solana_ledger::{blocktree::create_new_ledger, poh::compute_hashes_per_tick}; use solana_sdk::{ @@ -134,6 +134,13 @@ fn main() -> Result<(), Box> { let matches = App::new(crate_name!()) .about(crate_description!()) .version(solana_clap_utils::version!()) + .arg( + Arg::with_name("creation_time") + .long("creation-time") + .value_name("RFC3339 DATE TIME") + .takes_value(true) + .help("Time when the bootrap leader will start, defaults to current system time"), + ) .arg( Arg::with_name("bootstrap_leader_pubkey_file") .short("b") @@ -477,6 +484,10 @@ fn main() -> Result<(), Box> { ..GenesisConfig::default() }; + if let Some(creation_time) = matches.value_of("creation_time") { + genesis_config.creation_time = DateTime::parse_from_rfc3339(creation_time)?.timestamp(); + } + if let Some(faucet_pubkey) = faucet_pubkey { genesis_config.add_account( faucet_pubkey, diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index cc0dfd0c2c..c911b6f453 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -380,7 +380,7 @@ mod tests { .iter() .map(|meta| { if sysvar::clock::check_id(&meta.pubkey) { - sysvar::clock::create_account(1, 0, 0, 0, 0) + sysvar::clock::Clock::default().create_account(1) } else if sysvar::rewards::check_id(&meta.pubkey) { sysvar::rewards::create_account(1, 0.0, 0.0) } else if sysvar::stake_history::check_id(&meta.pubkey) { @@ -604,7 +604,7 @@ mod tests { KeyedAccount::new( &sysvar::clock::id(), false, - &mut sysvar::clock::create_account(1, 0, 0, 0, 0) + &mut sysvar::clock::Clock::default().create_account(1) ), KeyedAccount::new( &config::id(), diff --git a/programs/storage/src/storage_processor.rs b/programs/storage/src/storage_processor.rs index 49c5d155ee..0e20d13dc1 100644 --- a/programs/storage/src/storage_processor.rs +++ b/programs/storage/src/storage_processor.rs @@ -159,7 +159,7 @@ mod tests { Hash::default(), ); // the proof is for segment 0, need to move the slot into segment 2 - let mut clock_account = clock::create_account(1, 0, 0, 0, 0); + let mut clock_account = Clock::default().create_account(1); Clock::to_account( &Clock { slot: DEFAULT_SLOTS_PER_SEGMENT * 2, @@ -186,7 +186,7 @@ mod tests { let clock_id = clock::id(); let mut keyed_accounts = Vec::new(); let mut user_account = Account::default(); - let mut clock_account = clock::create_account(1, 0, 0, 0, 0); + let mut clock_account = Clock::default().create_account(1); keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account)); keyed_accounts.push(KeyedAccount::new(&clock_id, false, &mut clock_account)); @@ -211,7 +211,7 @@ mod tests { Hash::default(), ); // move tick height into segment 1 - let mut clock_account = clock::create_account(1, 0, 0, 0, 0); + let mut clock_account = Clock::default().create_account(1); Clock::to_account( &Clock { slot: 16, @@ -270,7 +270,7 @@ mod tests { Hash::default(), ); // move slot into segment 1 - let mut clock_account = clock::create_account(1, 0, 0, 0, 0); + let mut clock_account = Clock::default().create_account(1); Clock::to_account( &Clock { slot: DEFAULT_SLOTS_PER_SEGMENT, diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 48a4930c70..6f1dc982ec 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -206,7 +206,7 @@ mod tests { .iter() .map(|meta| { if sysvar::clock::check_id(&meta.pubkey) { - sysvar::clock::create_account(1, 0, 0, 0, 0) + sysvar::clock::Clock::default().create_account(1) } else if sysvar::slot_hashes::check_id(&meta.pubkey) { sysvar::slot_hashes::create_account(1, &[]) } else if sysvar::rent::check_id(&meta.pubkey) { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 741ab52824..a26b0dcb98 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -30,7 +30,7 @@ use solana_metrics::{ }; use solana_sdk::{ account::Account, - clock::{get_segment_from_slot, Epoch, Slot, MAX_RECENT_BLOCKHASHES}, + clock::{get_segment_from_slot, Epoch, Slot, UnixTimestamp, MAX_RECENT_BLOCKHASHES}, epoch_schedule::EpochSchedule, fee_calculator::FeeCalculator, genesis_config::GenesisConfig, @@ -190,6 +190,7 @@ pub struct Bank { /// Hash of this Bank's parent's state parent_hash: Hash, + /// parent's slot parent_slot: Slot, /// The number of transactions processed without error @@ -221,6 +222,12 @@ pub struct Bank { /// The number of ticks in each slot. ticks_per_slot: u64, + /// length of a slot in ns + ns_per_slot: u128, + + /// genesis time, used for computed clock + genesis_creation_time: UnixTimestamp, + /// The number of slots per year, used for inflation slots_per_year: f64, @@ -352,6 +359,8 @@ impl Bank { // TODO: clean this up, soo much special-case copying... hashes_per_tick: parent.hashes_per_tick, ticks_per_slot: parent.ticks_per_slot, + ns_per_slot: parent.ns_per_slot, + genesis_creation_time: parent.genesis_creation_time, slots_per_segment: parent.slots_per_segment, slots_per_year: parent.slots_per_year, epoch_schedule, @@ -474,16 +483,22 @@ impl Bank { ancestors } + /// computed unix_timestamp at this slot height + pub fn unix_timestamp(&self) -> i64 { + self.genesis_creation_time + ((self.slot as u128 * self.ns_per_slot) / 1_000_000_000) as i64 + } + fn update_clock(&self) { self.store_account( &sysvar::clock::id(), - &sysvar::clock::create_account( - 1, - self.slot, - get_segment_from_slot(self.slot, self.slots_per_segment), - self.epoch_schedule.get_epoch(self.slot), - self.epoch_schedule.get_leader_schedule_epoch(self.slot), - ), + &sysvar::clock::Clock { + slot: self.slot, + segment: get_segment_from_slot(self.slot, self.slots_per_segment), + epoch: self.epoch_schedule.get_epoch(self.slot), + leader_schedule_epoch: self.epoch_schedule.get_leader_schedule_epoch(self.slot), + unix_timestamp: self.unix_timestamp(), + } + .create_account(1), ); } @@ -714,6 +729,9 @@ impl Bank { self.hashes_per_tick = genesis_config.poh_config.hashes_per_tick; self.ticks_per_slot = genesis_config.ticks_per_slot; + self.ns_per_slot = genesis_config.poh_config.target_tick_duration.as_nanos() + * genesis_config.ticks_per_slot as u128; + self.genesis_creation_time = genesis_config.creation_time; self.slots_per_segment = genesis_config.slots_per_segment; self.max_tick_height = (self.slot + 1) * self.ticks_per_slot; self.slots_per_year = years_as_slots( @@ -1801,6 +1819,7 @@ mod tests { signature::{Keypair, KeypairUtil}, system_instruction, system_program, sysvar::{fees::Fees, rewards::Rewards}, + timing::duration_as_s, }; use solana_stake_program::{ stake_instruction, @@ -1813,6 +1832,23 @@ mod tests { use std::{io::Cursor, result, time::Duration}; use tempfile::TempDir; + #[test] + fn test_bank_unix_timestamp() { + let (genesis_config, _mint_keypair) = create_genesis_config(1); + let mut bank = Arc::new(Bank::new(&genesis_config)); + + assert_eq!(genesis_config.creation_time, bank.unix_timestamp()); + let slots_per_sec = 1.0 + / (duration_as_s(&genesis_config.poh_config.target_tick_duration) + * genesis_config.ticks_per_slot as f32); + + for _i in 0..slots_per_sec as usize + 1 { + bank = Arc::new(new_from_parent(&bank)); + } + + assert!(bank.unix_timestamp() - genesis_config.creation_time >= 1); + } + #[test] fn test_bank_new() { let dummy_leader_pubkey = Pubkey::new_rand(); diff --git a/sdk/src/clock.rs b/sdk/src/clock.rs index 617fb3d5e9..0024562b97 100644 --- a/sdk/src/clock.rs +++ b/sdk/src/clock.rs @@ -108,6 +108,9 @@ pub struct Clock { /// the future Epoch for which the leader schedule has /// most recently been calculated pub leader_schedule_epoch: Epoch, + /// computed from genesis creation time and network time + /// in slots, drifts! + pub unix_timestamp: UnixTimestamp, } #[cfg(test)] diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 74dc913b00..300afde80f 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -2,7 +2,7 @@ use crate::{ account::Account, - clock::{DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT}, + clock::{UnixTimestamp, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT}, epoch_schedule::EpochSchedule, fee_calculator::FeeCalculator, hash::{hash, Hash}, @@ -21,6 +21,7 @@ use std::{ fs::{File, OpenOptions}, io::Write, path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, }; #[derive(Serialize, Deserialize, Debug, Clone, Copy)] @@ -31,16 +32,27 @@ pub enum OperatingMode { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GenesisConfig { + /// when the network (bootstrap leader) was started relative to the UNIX Epoch + pub creation_time: UnixTimestamp, + /// initial accounts pub accounts: BTreeMap, + /// built-in programs pub native_instruction_processors: Vec<(String, Pubkey)>, + /// accounts for network rewards, these do not count towards capitalization pub rewards_pools: BTreeMap, pub ticks_per_slot: u64, pub slots_per_segment: u64, + /// network speed configuration pub poh_config: PohConfig, + /// transaction fee config pub fee_calculator: FeeCalculator, + /// rent config pub rent: Rent, + /// inflation config pub inflation: Inflation, + /// how slots map to epochs pub epoch_schedule: EpochSchedule, + /// network runlevel pub operating_mode: OperatingMode, } @@ -62,6 +74,10 @@ pub fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) { impl Default for GenesisConfig { fn default() -> Self { Self { + creation_time: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as UnixTimestamp, accounts: BTreeMap::default(), native_instruction_processors: Vec::default(), rewards_pools: BTreeMap::default(), diff --git a/sdk/src/sysvar/clock.rs b/sdk/src/sysvar/clock.rs index 725d0572ba..a6af0573bb 100644 --- a/sdk/src/sysvar/clock.rs +++ b/sdk/src/sysvar/clock.rs @@ -2,40 +2,8 @@ //! pub use crate::clock::Clock; -use crate::{ - account::Account, - clock::{Epoch, Segment, Slot}, - sysvar::Sysvar, -}; +use crate::sysvar::Sysvar; crate::declare_sysvar_id!("SysvarC1ock11111111111111111111111111111111", Clock); impl Sysvar for Clock {} - -pub fn create_account( - lamports: u64, - slot: Slot, - segment: Segment, - epoch: Epoch, - leader_schedule_epoch: Epoch, -) -> Account { - Clock { - slot, - segment, - epoch, - leader_schedule_epoch, - } - .create_account(lamports) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new() { - let account = create_account(1, 0, 0, 0, 0); - let clock = Clock::from_account(&account).unwrap(); - assert_eq!(clock, Clock::default()); - } -}