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
This commit is contained in:
Tyera Eulberg 2019-11-26 00:40:36 -07:00 committed by GitHub
parent 280315a314
commit 58c144ee55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 336 additions and 47 deletions

View File

@ -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

View File

@ -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<CliCommandInfo, Box<dyn
command: CliCommand::Fees,
require_keypair: false,
}),
("get-block-time", Some(matches)) => 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)

View File

@ -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<CliCommandInfo, Cl
})
}
pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
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<CliCommandInfo, CliError> {
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"]);

View File

@ -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<UnixTimestamp> {
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<RpcEpochInfo> {
self.get_epoch_info_with_commitment(CommitmentConfig::default())
}

View File

@ -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",

View File

@ -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<Option<RpcConfirmedBlock>> {
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<Option<UnixTimestamp>> {
// 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<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
@ -513,6 +529,9 @@ pub trait RpcSol {
meta: Self::Metadata,
slot: Slot,
) -> Result<Option<RpcConfirmedBlock>>;
#[rpc(meta, name = "getBlockTime")]
fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result<Option<UnixTimestamp>>;
}
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<Option<UnixTimestamp>> {
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);
}
}

View File

@ -116,7 +116,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
)
};
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<dyn error::Error>> {
.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<dyn error::Error>> {
);
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

View File

@ -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<UnixTimestamp> {
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<RpcConfirmedBlock> {
if self.is_root(slot) {
let slot_meta_cf = self.db.column::<cf::SlotMeta>();

View File

@ -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<Keypair>,
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);

View File

@ -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

View File

@ -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,
))
}
}

View File

@ -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
);
}
}