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)
|
||||
* [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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"]);
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue