From 58c144ee55d4204b12f6211a40c0b5b7f51b6603 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Tue, 26 Nov 2019 00:40:36 -0700 Subject: [PATCH] Add getBlockTime rpc api (#7130) * Add getBlockTime rpc api * Add getBlockTime to docs * Fix duration rounding for common tick/slot durations; add slot duration calculation * Expose slots_per_year * Use genesis values instead of clock defaults to calculate block offset * Add get-block-time cli subcommand * Fix test_rent: decrease magic number usage --- book/src/api-reference/jsonrpc-api.md | 26 ++++++ cli/src/cli.rs | 6 ++ cli/src/cluster_query.rs | 43 +++++++++- client/src/rpc_client.rs | 28 +++++- client/src/rpc_request.rs | 2 + core/src/rpc.rs | 78 ++++++++++++++++- genesis/src/main.rs | 10 ++- ledger/src/blocktree.rs | 26 +++++- runtime/src/bank.rs | 117 +++++++++++++++++++------- sdk/src/clock.rs | 4 + sdk/src/poh_config.rs | 4 +- sdk/src/timing.rs | 39 ++++++++- 12 files changed, 336 insertions(+), 47 deletions(-) diff --git a/book/src/api-reference/jsonrpc-api.md b/book/src/api-reference/jsonrpc-api.md index ecc08aa82..e0873f0b5 100644 --- a/book/src/api-reference/jsonrpc-api.md +++ b/book/src/api-reference/jsonrpc-api.md @@ -18,6 +18,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- * [getAccountInfo](jsonrpc-api.md#getaccountinfo) * [getBalance](jsonrpc-api.md#getbalance) * [getBlockCommitment](jsonrpc-api.md#getblockcommitment) +* [getBlockTime](jsonrpc-api.md#getblocktime) * [getClusterNodes](jsonrpc-api.md#getclusternodes) * [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock) * [getEpochInfo](jsonrpc-api.md#getepochinfo) @@ -209,6 +210,31 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m {"jsonrpc":"2.0","result":[{"commitment":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,32]},42],"id":1} ``` +### getBlockTime + +Returns the estimated production time of a block. Validators report their UTC +time to the ledger on a regular interval. A block's time is calculated as an +offset from the median value of the most recent validator time report. + +#### Parameters: + +* `u64` - block, identified by Slot + +#### Results: + +* `null` - block has not yet been produced +* `i64` - estimated production time, as Unix timestamp (seconds since the Unix epoch) + +#### Example: + +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockTime","params":[5]}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":1574721591,"id":1} +``` + ### getClusterNodes Returns information about all the nodes participating in the cluster diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 47c10f371..e3d71f6c8 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -20,6 +20,7 @@ use solana_drone::drone::request_airdrop_transaction; use solana_drone::drone_mock::request_airdrop_transaction; use solana_sdk::{ bpf_loader, + clock::Slot, commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, hash::Hash, @@ -79,6 +80,9 @@ pub enum CliCommand { }, ClusterVersion, Fees, + GetBlockTime { + slot: Slot, + }, GetEpochInfo { commitment_config: CommitmentConfig, }, @@ -286,6 +290,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result parse_get_block_time(matches), ("get-epoch-info", Some(matches)) => parse_get_epoch_info(matches), ("get-genesis-hash", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::GetGenesisHash, @@ -964,6 +969,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey), CliCommand::ClusterVersion => process_cluster_version(&rpc_client), CliCommand::Fees => process_fees(&rpc_client), + CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, *slot), CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client), CliCommand::GetEpochInfo { commitment_config } => { process_get_epoch_info(&rpc_client, commitment_config) diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index c98614358..7fae5bf6a 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -11,7 +11,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use solana_clap_utils::{input_parsers::*, input_validators::*}; use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo}; use solana_sdk::{ - clock, + clock::{self, Slot}, commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey, @@ -53,6 +53,17 @@ impl ClusterQuerySubCommands for App<'_, '_> { .about("Get the version of the cluster entrypoint"), ) .subcommand(SubCommand::with_name("fees").about("Display current cluster fees")) + .subcommand(SubCommand::with_name("get-block-time") + .about("Get estimated production time of a block") + .arg( + Arg::with_name("slot") + .index(1) + .takes_value(true) + .value_name("SLOT") + .required(true) + .help("Slot number of the block to query") + ) + ) .subcommand( SubCommand::with_name("get-epoch-info") .about("Get information about the current epoch") @@ -187,6 +198,14 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result) -> Result { + let slot = value_t_or_exit!(matches, "slot", u64); + Ok(CliCommandInfo { + command: CliCommand::GetBlockTime { slot }, + require_keypair: false, + }) +} + pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result { let commitment_config = if matches.is_present("confirmed") { CommitmentConfig::default() @@ -313,6 +332,11 @@ pub fn process_fees(rpc_client: &RpcClient) -> ProcessResult { )) } +pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResult { + let timestamp = rpc_client.get_block_time(slot)?; + Ok(timestamp.to_string()) +} + pub fn process_get_epoch_info( rpc_client: &RpcClient, commitment_config: &CommitmentConfig, @@ -442,8 +466,7 @@ pub fn process_ping( // Sleep for half a slot if signal_receiver .recv_timeout(Duration::from_millis( - 500 * solana_sdk::clock::DEFAULT_TICKS_PER_SLOT - / solana_sdk::clock::DEFAULT_TICKS_PER_SECOND, + 500 * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND, )) .is_ok() { @@ -656,6 +679,20 @@ mod tests { } ); + let slot = 100; + let test_get_block_time = test_commands.clone().get_matches_from(vec![ + "test", + "get-block-time", + &slot.to_string(), + ]); + assert_eq!( + parse_command(&test_get_block_time).unwrap(), + CliCommandInfo { + command: CliCommand::GetBlockTime { slot }, + require_keypair: false + } + ); + let test_get_epoch_info = test_commands .clone() .get_matches_from(vec!["test", "get-epoch-info"]); diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 132089982..4154c4e6b 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -11,7 +11,7 @@ use log::*; use serde_json::{json, Value}; use solana_sdk::{ account::Account, - clock::{Slot, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT}, + clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT}, commitment_config::CommitmentConfig, epoch_schedule::EpochSchedule, fee_calculator::FeeCalculator, @@ -196,6 +196,32 @@ impl RpcClient { }) } + pub fn get_block_time(&self, slot: Slot) -> io::Result { + let params = json!(slot); + let response = self + .client + .send(&RpcRequest::GetBlockTime, Some(params), 0, None); + + response + .map(|result_json| { + if result_json.is_null() { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Block Not Found: slot={}", slot), + )); + } + let result = serde_json::from_value(result_json)?; + trace!("Response block timestamp {:?} {:?}", slot, result); + Ok(result) + }) + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("GetBlockTime request failure: {:?}", err), + ) + })? + } + pub fn get_epoch_info(&self) -> io::Result { self.get_epoch_info_with_commitment(CommitmentConfig::default()) } diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index f9a14785b..1605491bd 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -111,6 +111,7 @@ pub enum RpcRequest { ValidatorExit, GetAccountInfo, GetBalance, + GetBlockTime, GetClusterNodes, GetEpochInfo, GetEpochSchedule, @@ -150,6 +151,7 @@ impl RpcRequest { RpcRequest::ValidatorExit => "validatorExit", RpcRequest::GetAccountInfo => "getAccountInfo", RpcRequest::GetBalance => "getBalance", + RpcRequest::GetBlockTime => "getBlockTime", RpcRequest::GetClusterNodes => "getClusterNodes", RpcRequest::GetEpochInfo => "getEpochInfo", RpcRequest::GetEpochSchedule => "getEpochSchedule", diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 6ba102b42..a9ed2649b 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -20,7 +20,7 @@ use solana_ledger::{bank_forks::BankForks, blocktree::Blocktree}; use solana_runtime::bank::Bank; use solana_sdk::{ account::Account, - clock::Slot, + clock::{Slot, UnixTimestamp}, commitment_config::{CommitmentConfig, CommitmentLevel}, epoch_schedule::EpochSchedule, fee_calculator::FeeCalculator, @@ -28,6 +28,7 @@ use solana_sdk::{ inflation::Inflation, pubkey::Pubkey, signature::Signature, + timing::slot_duration_from_slots_per_year, transaction::{self, Transaction}, }; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; @@ -304,6 +305,21 @@ impl JsonRpcRequestProcessor { pub fn get_confirmed_block(&self, slot: Slot) -> Result> { Ok(self.blocktree.get_confirmed_block(slot).ok()) } + + // The `get_block_time` method is not fully implemented. It currently returns `slot` * + // DEFAULT_MS_PER_SLOT offset from 0 for all requests, and null for any values that would + // overflow. + pub fn get_block_time(&self, slot: Slot) -> Result> { + // This calculation currently assumes that bank.ticks_per_slot and bank.slots_per_year will + // remain unchanged after genesis. If these values will be variable in the future, those + // timing parameters will need to be stored persistently, and this calculation will likely + // need to be moved upstream into blocktree. Also, an explicit commitment level will need + // to be set. + let bank = self.bank(None); + let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year()); + + Ok(self.blocktree.get_block_time(slot, slot_duration)) + } } fn get_tpu_addr(cluster_info: &Arc>) -> Result { @@ -513,6 +529,9 @@ pub trait RpcSol { meta: Self::Metadata, slot: Slot, ) -> Result>; + + #[rpc(meta, name = "getBlockTime")] + fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result>; } pub struct RpcSolImpl; @@ -967,6 +986,10 @@ impl RpcSol for RpcSolImpl { .unwrap() .get_confirmed_block(slot) } + + fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result> { + meta.request_processor.read().unwrap().get_block_time(slot) + } } #[cfg(test)] @@ -1818,4 +1841,57 @@ pub mod tests { } } } + + #[test] + fn test_get_block_time() { + let bob_pubkey = Pubkey::new_rand(); + let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey); + + let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year()); + + let slot = 100; + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, + slot + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected = format!( + r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, + (slot * slot_duration).as_secs() + ); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + let slot = 12345; + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, + slot + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected = format!( + r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, + (slot * slot_duration).as_secs() + ); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + + let slot = 123450000000000000u64; + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, + slot + ); + let res = io.handle_request_sync(&req, meta); + let expected = format!(r#"{{"jsonrpc":"2.0","result":null,"id":1}}"#); + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 1961f069c..d6c85f4c6 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -116,7 +116,7 @@ fn main() -> Result<(), Box> { ) }; let default_target_tick_duration = - &timing::duration_as_ms(&PohConfig::default().target_tick_duration).to_string(); + timing::duration_as_us(&PohConfig::default().target_tick_duration); let default_ticks_per_slot = &clock::DEFAULT_TICKS_PER_SLOT.to_string(); let default_operating_mode = "softlaunch"; @@ -261,7 +261,6 @@ fn main() -> Result<(), Box> { .long("target-tick-duration") .value_name("MILLIS") .takes_value(true) - .default_value(default_target_tick_duration) .help("The target tick rate of the cluster in milliseconds"), ) .arg( @@ -370,8 +369,11 @@ fn main() -> Result<(), Box> { ); let mut poh_config = PohConfig::default(); - poh_config.target_tick_duration = - Duration::from_millis(value_t_or_exit!(matches, "target_tick_duration", u64)); + poh_config.target_tick_duration = if matches.is_present("target_tick_duration") { + Duration::from_micros(value_t_or_exit!(matches, "target_tick_duration", u64)) + } else { + Duration::from_micros(default_target_tick_duration) + }; let operating_mode = if matches.value_of("operating_mode").unwrap() == "development" { OperatingMode::Development diff --git a/ledger/src/blocktree.rs b/ledger/src/blocktree.rs index dfb30a164..a52e45c86 100644 --- a/ledger/src/blocktree.rs +++ b/ledger/src/blocktree.rs @@ -16,6 +16,7 @@ pub use crate::{ blocktree_meta::SlotMeta, }; use bincode::deserialize; +use chrono::{offset::TimeZone, Duration as ChronoDuration, Utc}; use log::*; use rayon::{ iter::{IntoParallelRefIterator, ParallelIterator}, @@ -27,17 +28,18 @@ use solana_measure::measure::Measure; use solana_metrics::{datapoint_debug, datapoint_error}; use solana_rayon_threadlimit::get_thread_count; use solana_sdk::{ - clock::{Slot, DEFAULT_TICKS_PER_SECOND}, + clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND}, genesis_config::GenesisConfig, hash::Hash, signature::{Keypair, KeypairUtil, Signature}, - timing::timestamp, + timing::{duration_as_ms, timestamp}, transaction::Transaction, }; use std::{ cell::RefCell, cmp, collections::HashMap, + convert::TryFrom, fs, path::{Path, PathBuf}, rc::Rc, @@ -45,6 +47,7 @@ use std::{ mpsc::{sync_channel, Receiver, SyncSender, TrySendError}, Arc, Mutex, RwLock, }, + time::Duration, }; pub const BLOCKTREE_DIRECTORY: &str = "rocksdb"; @@ -1130,6 +1133,25 @@ impl Blocktree { } } + // The `get_block_time` method is not fully implemented (depends on validator timestamp + // transactions). It currently returns Some(`slot` * DEFAULT_MS_PER_SLOT) offset from 0 for all + // transactions, and None for any values that would overflow any step. + pub fn get_block_time(&self, slot: Slot, slot_duration: Duration) -> Option { + let ms_per_slot = duration_as_ms(&slot_duration); + let (offset_millis, overflow) = slot.overflowing_mul(ms_per_slot); + if !overflow { + i64::try_from(offset_millis) + .ok() + .and_then(|millis| { + let median_datetime = Utc.timestamp(0, 0); + median_datetime.checked_add_signed(ChronoDuration::milliseconds(millis)) + }) + .map(|dt| dt.timestamp()) + } else { + None + } + } + pub fn get_confirmed_block(&self, slot: Slot) -> Result { if self.is_root(slot) { let slot_meta_cf = self.db.column::(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 4a7131376..19036dd6d 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1472,6 +1472,11 @@ impl Bank { self.ticks_per_slot } + /// Return the number of slots per year + pub fn slots_per_year(&self) -> f64 { + self.slots_per_year + } + /// Return the number of slots per segment pub fn slots_per_segment(&self) -> u64 { self.slots_per_segment @@ -1944,23 +1949,40 @@ mod tests { bank: &Bank, keypairs: &mut Vec, mock_program_id: Pubkey, + generic_rent_due_for_system_account: u64, ) { let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1); account_pairs.push(( keypairs[0].pubkey(), - Account::new(51224, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 2, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[1].pubkey(), - Account::new(51224, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 2, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[2].pubkey(), - Account::new(51224, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 2, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[3].pubkey(), - Account::new(51224, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 2, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[4].pubkey(), @@ -1972,12 +1994,20 @@ mod tests { )); account_pairs.push(( keypairs[6].pubkey(), - Account::new(102_468, 1, &Pubkey::default()), + Account::new( + (2 * generic_rent_due_for_system_account) + 24, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[8].pubkey(), - Account::new(52153, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 2 + 929, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[9].pubkey(), @@ -1987,15 +2017,19 @@ mod tests { // Feeding to MockProgram to test read only rent behaviour account_pairs.push(( keypairs[10].pubkey(), - Account::new(51225, 1, &Pubkey::default()), + Account::new( + generic_rent_due_for_system_account + 3, + 1, + &Pubkey::default(), + ), )); account_pairs.push(( keypairs[11].pubkey(), - Account::new(51225, 1, &mock_program_id), + Account::new(generic_rent_due_for_system_account + 3, 1, &mock_program_id), )); account_pairs.push(( keypairs[12].pubkey(), - Account::new(51225, 1, &mock_program_id), + Account::new(generic_rent_due_for_system_account + 3, 1, &mock_program_id), )); account_pairs.push(( keypairs[13].pubkey(), @@ -2055,7 +2089,25 @@ mod tests { assert_eq!(bank.last_blockhash(), genesis_config.hash()); - store_accounts_for_rent_test(&bank, &mut keypairs, mock_program_id); + let slots_elapsed: u64 = (0..=bank.epoch) + .map(|epoch| { + bank.rent_collector + .epoch_schedule + .get_slots_in_epoch(epoch + 1) + }) + .sum(); + let (generic_rent_due_for_system_account, _) = bank.rent_collector.rent.due( + bank.get_minimum_balance_for_rent_exemption(1) - 1, + 1, + slots_elapsed as f64 / bank.rent_collector.slots_per_year, + ); + + store_accounts_for_rent_test( + &bank, + &mut keypairs, + mock_program_id, + generic_rent_due_for_system_account, + ); let t1 = system_transaction::transfer( &keypairs[0], @@ -2078,7 +2130,7 @@ mod tests { let t4 = system_transaction::transfer( &keypairs[6], &keypairs[7].pubkey(), - 51223, + 49373, genesis_config.hash(), ); let t5 = system_transaction::transfer( @@ -2118,32 +2170,35 @@ mod tests { let mut rent_collected = 0; - // 51224 - 51222(Rent) - 1(transfer) + // 49374 - 49372(Rent) - 1(transfer) assert_eq!(bank.get_balance(&keypairs[0].pubkey()), 1); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 51224 - 51222(Rent) - 1(transfer) + // 49374 - 49372(Rent) - 1(transfer) assert_eq!(bank.get_balance(&keypairs[1].pubkey()), 3); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 51224 - 51222(Rent) - 1(transfer) + // 49374 - 49372(Rent) - 1(transfer) assert_eq!(bank.get_balance(&keypairs[2].pubkey()), 1); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 51224 - 51222(Rent) - 1(transfer) + // 49374 - 49372(Rent) - 1(transfer) assert_eq!(bank.get_balance(&keypairs[3].pubkey()), 3); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; // No rent deducted assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10); assert_eq!(bank.get_balance(&keypairs[5].pubkey()), 10); - // 102_468 - 51222(Rent) - 51223(transfer) + // 98768 - 49372(Rent) - 49373(transfer) assert_eq!(bank.get_balance(&keypairs[6].pubkey()), 23); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 0 + 51223(transfer) - 917(Rent) - assert_eq!(bank.get_balance(&keypairs[7].pubkey()), 50306); + // 0 + 49373(transfer) - 917(Rent) + assert_eq!( + bank.get_balance(&keypairs[7].pubkey()), + generic_rent_due_for_system_account + 1 - 917 + ); // Epoch should be updated // Rent deducted on store side let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap(); @@ -2151,9 +2206,9 @@ mod tests { assert_eq!(account8.rent_epoch, bank.epoch + 1); rent_collected += 917; - // 52153 - 51222(Rent) - 929(Transfer) + // 50303 - 49372(Rent) - 929(Transfer) assert_eq!(bank.get_balance(&keypairs[8].pubkey()), 2); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; let account10 = bank.get_account(&keypairs[9].pubkey()).unwrap(); // Account was overwritten at load time, since it didn't have sufficient balance to pay rent @@ -2165,17 +2220,17 @@ mod tests { assert_eq!(account10.lamports, 12); rent_collected += 927; - // 51225 - 51222(Rent) + // 49375 - 49372(Rent) assert_eq!(bank.get_balance(&keypairs[10].pubkey()), 3); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 51225 - 51222(Rent) + 1(Addition by program) + // 49375 - 49372(Rent) + 1(Addition by program) assert_eq!(bank.get_balance(&keypairs[11].pubkey()), 4); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; - // 51225 - 51222(Rent) - 1(Deduction by program) + // 49375 - 49372(Rent) - 1(Deduction by program) assert_eq!(bank.get_balance(&keypairs[12].pubkey()), 2); - rent_collected += 51222; + rent_collected += generic_rent_due_for_system_account; // No rent for read-only account assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14); diff --git a/sdk/src/clock.rs b/sdk/src/clock.rs index f3667f126..0121dc038 100644 --- a/sdk/src/clock.rs +++ b/sdk/src/clock.rs @@ -85,6 +85,10 @@ pub type Segment = u64; /// some number of Slots. pub type Epoch = u64; +/// UnixTimestamp is an approximate measure of real-world time, +/// expressed as Unix time (ie. seconds since the Unix epoch) +pub type UnixTimestamp = i64; + /// Clock represents network time. Members of Clock start from 0 upon /// network boot. The best way to map Clock to wallclock time is to use /// current Slot, as Epochs vary in duration (they start short and grow diff --git a/sdk/src/poh_config.rs b/sdk/src/poh_config.rs index e9e45b420..76334e20b 100644 --- a/sdk/src/poh_config.rs +++ b/sdk/src/poh_config.rs @@ -28,6 +28,8 @@ impl PohConfig { impl Default for PohConfig { fn default() -> Self { - Self::new_sleep(Duration::from_millis(1000 / DEFAULT_TICKS_PER_SECOND)) + Self::new_sleep(Duration::from_micros( + 1000 * 1000 / DEFAULT_TICKS_PER_SECOND, + )) } } diff --git a/sdk/src/timing.rs b/sdk/src/timing.rs index 448faadd6..14ff86bff 100644 --- a/sdk/src/timing.rs +++ b/sdk/src/timing.rs @@ -38,27 +38,58 @@ pub fn years_as_slots(years: f64, tick_duration: &Duration, ticks_per_slot: u64) / ticks_per_slot as f64 } +/// From slots per year to tick_duration +pub fn slot_duration_from_slots_per_year(slots_per_year: f64) -> Duration { + // Regarding division by zero potential below: for some reason, if Rust stores an `inf` f64 and + // then converts it to a u64 on use, it always returns 0, as opposed to std::u64::MAX or any + // other huge value + let slot_in_ns = (SECONDS_PER_YEAR * 1_000_000_000.0) / slots_per_year; + Duration::from_nanos(slot_in_ns as u64) +} + #[cfg(test)] mod test { use super::*; #[test] fn test_years_as_slots() { - let tick_duration = Duration::from_millis(1000 / 160); + let tick_duration = Duration::from_micros(1000 * 1000 / 160); // interestingly large numbers with 160 ticks/second assert_eq!(years_as_slots(0.0, &tick_duration, 4) as u64, 0); assert_eq!( years_as_slots(1.0 / 12f64, &tick_duration, 4) as u64, - 109_572_659 + 105_189_753 ); - assert_eq!(years_as_slots(1.0, &tick_duration, 4) as u64, 1_314_871_916); + assert_eq!(years_as_slots(1.0, &tick_duration, 4) as u64, 1_262_277_039); - let tick_duration = Duration::from_millis(1000); + let tick_duration = Duration::from_micros(1000 * 1000); // one second in years with one tick per second + one tick per slot assert_eq!( years_as_slots(1.0 / SECONDS_PER_YEAR, &tick_duration, 1), 1.0 ); } + + #[test] + fn test_slot_duration_from_slots_per_year() { + let slots_per_year = 1_262_277_039.0; + let ticks_per_slot = 4; + + assert_eq!( + slot_duration_from_slots_per_year(slots_per_year), + Duration::from_micros(1000 * 1000 / 160) * ticks_per_slot + ); + assert_eq!( + slot_duration_from_slots_per_year(0.0), + Duration::from_micros(0) * ticks_per_slot + ); + + let slots_per_year = SECONDS_PER_YEAR; + let ticks_per_slot = 1; + assert_eq!( + slot_duration_from_slots_per_year(slots_per_year), + Duration::from_millis(1000) * ticks_per_slot + ); + } }