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:
parent
280315a314
commit
58c144ee55
|
@ -18,6 +18,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||||
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
||||||
* [getBalance](jsonrpc-api.md#getbalance)
|
* [getBalance](jsonrpc-api.md#getbalance)
|
||||||
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
||||||
|
* [getBlockTime](jsonrpc-api.md#getblocktime)
|
||||||
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
|
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
|
||||||
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
|
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
|
||||||
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
|
* [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}
|
{"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
|
### getClusterNodes
|
||||||
|
|
||||||
Returns information about all the nodes participating in the cluster
|
Returns information about all the nodes participating in the cluster
|
||||||
|
|
|
@ -20,6 +20,7 @@ use solana_drone::drone::request_airdrop_transaction;
|
||||||
use solana_drone::drone_mock::request_airdrop_transaction;
|
use solana_drone::drone_mock::request_airdrop_transaction;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
|
clock::Slot,
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
@ -79,6 +80,9 @@ pub enum CliCommand {
|
||||||
},
|
},
|
||||||
ClusterVersion,
|
ClusterVersion,
|
||||||
Fees,
|
Fees,
|
||||||
|
GetBlockTime {
|
||||||
|
slot: Slot,
|
||||||
|
},
|
||||||
GetEpochInfo {
|
GetEpochInfo {
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
},
|
},
|
||||||
|
@ -286,6 +290,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
|
||||||
command: CliCommand::Fees,
|
command: CliCommand::Fees,
|
||||||
require_keypair: false,
|
require_keypair: false,
|
||||||
}),
|
}),
|
||||||
|
("get-block-time", Some(matches)) => parse_get_block_time(matches),
|
||||||
("get-epoch-info", Some(matches)) => parse_get_epoch_info(matches),
|
("get-epoch-info", Some(matches)) => parse_get_epoch_info(matches),
|
||||||
("get-genesis-hash", Some(_matches)) => Ok(CliCommandInfo {
|
("get-genesis-hash", Some(_matches)) => Ok(CliCommandInfo {
|
||||||
command: CliCommand::GetGenesisHash,
|
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::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey),
|
||||||
CliCommand::ClusterVersion => process_cluster_version(&rpc_client),
|
CliCommand::ClusterVersion => process_cluster_version(&rpc_client),
|
||||||
CliCommand::Fees => process_fees(&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::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
||||||
CliCommand::GetEpochInfo { commitment_config } => {
|
CliCommand::GetEpochInfo { commitment_config } => {
|
||||||
process_get_epoch_info(&rpc_client, commitment_config)
|
process_get_epoch_info(&rpc_client, commitment_config)
|
||||||
|
|
|
@ -11,7 +11,7 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||||
use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
|
use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock,
|
clock::{self, Slot},
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
@ -53,6 +53,17 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||||
.about("Get the version of the cluster entrypoint"),
|
.about("Get the version of the cluster entrypoint"),
|
||||||
)
|
)
|
||||||
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
|
.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(
|
||||||
SubCommand::with_name("get-epoch-info")
|
SubCommand::with_name("get-epoch-info")
|
||||||
.about("Get information about the current epoch")
|
.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> {
|
pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||||
let commitment_config = if matches.is_present("confirmed") {
|
let commitment_config = if matches.is_present("confirmed") {
|
||||||
CommitmentConfig::default()
|
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(
|
pub fn process_get_epoch_info(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
commitment_config: &CommitmentConfig,
|
commitment_config: &CommitmentConfig,
|
||||||
|
@ -442,8 +466,7 @@ pub fn process_ping(
|
||||||
// Sleep for half a slot
|
// Sleep for half a slot
|
||||||
if signal_receiver
|
if signal_receiver
|
||||||
.recv_timeout(Duration::from_millis(
|
.recv_timeout(Duration::from_millis(
|
||||||
500 * solana_sdk::clock::DEFAULT_TICKS_PER_SLOT
|
500 * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||||
/ solana_sdk::clock::DEFAULT_TICKS_PER_SECOND,
|
|
||||||
))
|
))
|
||||||
.is_ok()
|
.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
|
let test_get_epoch_info = test_commands
|
||||||
.clone()
|
.clone()
|
||||||
.get_matches_from(vec!["test", "get-epoch-info"]);
|
.get_matches_from(vec!["test", "get-epoch-info"]);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use log::*;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
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,
|
commitment_config::CommitmentConfig,
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
fee_calculator::FeeCalculator,
|
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> {
|
pub fn get_epoch_info(&self) -> io::Result<RpcEpochInfo> {
|
||||||
self.get_epoch_info_with_commitment(CommitmentConfig::default())
|
self.get_epoch_info_with_commitment(CommitmentConfig::default())
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,7 @@ pub enum RpcRequest {
|
||||||
ValidatorExit,
|
ValidatorExit,
|
||||||
GetAccountInfo,
|
GetAccountInfo,
|
||||||
GetBalance,
|
GetBalance,
|
||||||
|
GetBlockTime,
|
||||||
GetClusterNodes,
|
GetClusterNodes,
|
||||||
GetEpochInfo,
|
GetEpochInfo,
|
||||||
GetEpochSchedule,
|
GetEpochSchedule,
|
||||||
|
@ -150,6 +151,7 @@ impl RpcRequest {
|
||||||
RpcRequest::ValidatorExit => "validatorExit",
|
RpcRequest::ValidatorExit => "validatorExit",
|
||||||
RpcRequest::GetAccountInfo => "getAccountInfo",
|
RpcRequest::GetAccountInfo => "getAccountInfo",
|
||||||
RpcRequest::GetBalance => "getBalance",
|
RpcRequest::GetBalance => "getBalance",
|
||||||
|
RpcRequest::GetBlockTime => "getBlockTime",
|
||||||
RpcRequest::GetClusterNodes => "getClusterNodes",
|
RpcRequest::GetClusterNodes => "getClusterNodes",
|
||||||
RpcRequest::GetEpochInfo => "getEpochInfo",
|
RpcRequest::GetEpochInfo => "getEpochInfo",
|
||||||
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
||||||
|
|
|
@ -20,7 +20,7 @@ use solana_ledger::{bank_forks::BankForks, blocktree::Blocktree};
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
clock::Slot,
|
clock::{Slot, UnixTimestamp},
|
||||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
|
@ -28,6 +28,7 @@ use solana_sdk::{
|
||||||
inflation::Inflation,
|
inflation::Inflation,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
|
timing::slot_duration_from_slots_per_year,
|
||||||
transaction::{self, Transaction},
|
transaction::{self, Transaction},
|
||||||
};
|
};
|
||||||
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
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>> {
|
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> {
|
||||||
Ok(self.blocktree.get_confirmed_block(slot).ok())
|
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> {
|
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
|
||||||
|
@ -513,6 +529,9 @@ pub trait RpcSol {
|
||||||
meta: Self::Metadata,
|
meta: Self::Metadata,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
) -> Result<Option<RpcConfirmedBlock>>;
|
) -> Result<Option<RpcConfirmedBlock>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getBlockTime")]
|
||||||
|
fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result<Option<UnixTimestamp>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcSolImpl;
|
pub struct RpcSolImpl;
|
||||||
|
@ -967,6 +986,10 @@ impl RpcSol for RpcSolImpl {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_confirmed_block(slot)
|
.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)]
|
#[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let default_target_tick_duration =
|
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_ticks_per_slot = &clock::DEFAULT_TICKS_PER_SLOT.to_string();
|
||||||
let default_operating_mode = "softlaunch";
|
let default_operating_mode = "softlaunch";
|
||||||
|
|
||||||
|
@ -261,7 +261,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.long("target-tick-duration")
|
.long("target-tick-duration")
|
||||||
.value_name("MILLIS")
|
.value_name("MILLIS")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value(default_target_tick_duration)
|
|
||||||
.help("The target tick rate of the cluster in milliseconds"),
|
.help("The target tick rate of the cluster in milliseconds"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -370,8 +369,11 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut poh_config = PohConfig::default();
|
let mut poh_config = PohConfig::default();
|
||||||
poh_config.target_tick_duration =
|
poh_config.target_tick_duration = if matches.is_present("target_tick_duration") {
|
||||||
Duration::from_millis(value_t_or_exit!(matches, "target_tick_duration", u64));
|
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" {
|
let operating_mode = if matches.value_of("operating_mode").unwrap() == "development" {
|
||||||
OperatingMode::Development
|
OperatingMode::Development
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub use crate::{
|
||||||
blocktree_meta::SlotMeta,
|
blocktree_meta::SlotMeta,
|
||||||
};
|
};
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
|
use chrono::{offset::TimeZone, Duration as ChronoDuration, Utc};
|
||||||
use log::*;
|
use log::*;
|
||||||
use rayon::{
|
use rayon::{
|
||||||
iter::{IntoParallelRefIterator, ParallelIterator},
|
iter::{IntoParallelRefIterator, ParallelIterator},
|
||||||
|
@ -27,17 +28,18 @@ use solana_measure::measure::Measure;
|
||||||
use solana_metrics::{datapoint_debug, datapoint_error};
|
use solana_metrics::{datapoint_debug, datapoint_error};
|
||||||
use solana_rayon_threadlimit::get_thread_count;
|
use solana_rayon_threadlimit::get_thread_count;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{Slot, DEFAULT_TICKS_PER_SECOND},
|
clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND},
|
||||||
genesis_config::GenesisConfig,
|
genesis_config::GenesisConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
signature::{Keypair, KeypairUtil, Signature},
|
signature::{Keypair, KeypairUtil, Signature},
|
||||||
timing::timestamp,
|
timing::{duration_as_ms, timestamp},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp,
|
cmp,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -45,6 +47,7 @@ use std::{
|
||||||
mpsc::{sync_channel, Receiver, SyncSender, TrySendError},
|
mpsc::{sync_channel, Receiver, SyncSender, TrySendError},
|
||||||
Arc, Mutex, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const BLOCKTREE_DIRECTORY: &str = "rocksdb";
|
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> {
|
pub fn get_confirmed_block(&self, slot: Slot) -> Result<RpcConfirmedBlock> {
|
||||||
if self.is_root(slot) {
|
if self.is_root(slot) {
|
||||||
let slot_meta_cf = self.db.column::<cf::SlotMeta>();
|
let slot_meta_cf = self.db.column::<cf::SlotMeta>();
|
||||||
|
|
|
@ -1472,6 +1472,11 @@ impl Bank {
|
||||||
self.ticks_per_slot
|
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
|
/// Return the number of slots per segment
|
||||||
pub fn slots_per_segment(&self) -> u64 {
|
pub fn slots_per_segment(&self) -> u64 {
|
||||||
self.slots_per_segment
|
self.slots_per_segment
|
||||||
|
@ -1944,23 +1949,40 @@ mod tests {
|
||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
keypairs: &mut Vec<Keypair>,
|
keypairs: &mut Vec<Keypair>,
|
||||||
mock_program_id: Pubkey,
|
mock_program_id: Pubkey,
|
||||||
|
generic_rent_due_for_system_account: u64,
|
||||||
) {
|
) {
|
||||||
let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1);
|
let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1);
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[0].pubkey(),
|
keypairs[0].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[1].pubkey(),
|
keypairs[1].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[2].pubkey(),
|
keypairs[2].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[3].pubkey(),
|
keypairs[3].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[4].pubkey(),
|
keypairs[4].pubkey(),
|
||||||
|
@ -1972,12 +1994,20 @@ mod tests {
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[6].pubkey(),
|
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((
|
account_pairs.push((
|
||||||
keypairs[8].pubkey(),
|
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((
|
account_pairs.push((
|
||||||
keypairs[9].pubkey(),
|
keypairs[9].pubkey(),
|
||||||
|
@ -1987,15 +2017,19 @@ mod tests {
|
||||||
// Feeding to MockProgram to test read only rent behaviour
|
// Feeding to MockProgram to test read only rent behaviour
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[10].pubkey(),
|
keypairs[10].pubkey(),
|
||||||
Account::new(51225, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 3,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[11].pubkey(),
|
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((
|
account_pairs.push((
|
||||||
keypairs[12].pubkey(),
|
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((
|
account_pairs.push((
|
||||||
keypairs[13].pubkey(),
|
keypairs[13].pubkey(),
|
||||||
|
@ -2055,7 +2089,25 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(bank.last_blockhash(), genesis_config.hash());
|
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(
|
let t1 = system_transaction::transfer(
|
||||||
&keypairs[0],
|
&keypairs[0],
|
||||||
|
@ -2078,7 +2130,7 @@ mod tests {
|
||||||
let t4 = system_transaction::transfer(
|
let t4 = system_transaction::transfer(
|
||||||
&keypairs[6],
|
&keypairs[6],
|
||||||
&keypairs[7].pubkey(),
|
&keypairs[7].pubkey(),
|
||||||
51223,
|
49373,
|
||||||
genesis_config.hash(),
|
genesis_config.hash(),
|
||||||
);
|
);
|
||||||
let t5 = system_transaction::transfer(
|
let t5 = system_transaction::transfer(
|
||||||
|
@ -2118,32 +2170,35 @@ mod tests {
|
||||||
|
|
||||||
let mut rent_collected = 0;
|
let mut rent_collected = 0;
|
||||||
|
|
||||||
// 51224 - 51222(Rent) - 1(transfer)
|
// 49374 - 49372(Rent) - 1(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[0].pubkey()), 1);
|
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);
|
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);
|
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);
|
assert_eq!(bank.get_balance(&keypairs[3].pubkey()), 3);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// No rent deducted
|
// No rent deducted
|
||||||
assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10);
|
assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10);
|
||||||
assert_eq!(bank.get_balance(&keypairs[5].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);
|
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)
|
// 0 + 49373(transfer) - 917(Rent)
|
||||||
assert_eq!(bank.get_balance(&keypairs[7].pubkey()), 50306);
|
assert_eq!(
|
||||||
|
bank.get_balance(&keypairs[7].pubkey()),
|
||||||
|
generic_rent_due_for_system_account + 1 - 917
|
||||||
|
);
|
||||||
// Epoch should be updated
|
// Epoch should be updated
|
||||||
// Rent deducted on store side
|
// Rent deducted on store side
|
||||||
let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap();
|
let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap();
|
||||||
|
@ -2151,9 +2206,9 @@ mod tests {
|
||||||
assert_eq!(account8.rent_epoch, bank.epoch + 1);
|
assert_eq!(account8.rent_epoch, bank.epoch + 1);
|
||||||
rent_collected += 917;
|
rent_collected += 917;
|
||||||
|
|
||||||
// 52153 - 51222(Rent) - 929(Transfer)
|
// 50303 - 49372(Rent) - 929(Transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[8].pubkey()), 2);
|
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();
|
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
|
// 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);
|
assert_eq!(account10.lamports, 12);
|
||||||
rent_collected += 927;
|
rent_collected += 927;
|
||||||
|
|
||||||
// 51225 - 51222(Rent)
|
// 49375 - 49372(Rent)
|
||||||
assert_eq!(bank.get_balance(&keypairs[10].pubkey()), 3);
|
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);
|
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);
|
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
|
// No rent for read-only account
|
||||||
assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14);
|
assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14);
|
||||||
|
|
|
@ -85,6 +85,10 @@ pub type Segment = u64;
|
||||||
/// some number of Slots.
|
/// some number of Slots.
|
||||||
pub type Epoch = u64;
|
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
|
/// 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
|
/// 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
|
/// current Slot, as Epochs vary in duration (they start short and grow
|
||||||
|
|
|
@ -28,6 +28,8 @@ impl PohConfig {
|
||||||
|
|
||||||
impl Default for PohConfig {
|
impl Default for PohConfig {
|
||||||
fn default() -> Self {
|
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,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,27 +38,58 @@ pub fn years_as_slots(years: f64, tick_duration: &Duration, ticks_per_slot: u64)
|
||||||
/ ticks_per_slot as f64
|
/ 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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_years_as_slots() {
|
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
|
// 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(0.0, &tick_duration, 4) as u64, 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
years_as_slots(1.0 / 12f64, &tick_duration, 4) as u64,
|
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
|
// one second in years with one tick per second + one tick per slot
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
years_as_slots(1.0 / SECONDS_PER_YEAR, &tick_duration, 1),
|
years_as_slots(1.0 / SECONDS_PER_YEAR, &tick_duration, 1),
|
||||||
1.0
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue