Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Add getBlockTime rpc api (#7130)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
CriesofCarrots authored Nov 26, 2019
1 parent 280315a commit 58c144e
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 47 deletions.
26 changes: 26 additions & 0 deletions book/src/api-reference/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -79,6 +80,9 @@ pub enum CliCommand {
},
ClusterVersion,
Fees,
GetBlockTime {
slot: Slot,
},
GetEpochInfo {
commitment_config: CommitmentConfig,
},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
43 changes: 40 additions & 3 deletions cli/src/cluster_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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"]);
Expand Down
28 changes: 27 additions & 1 deletion client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())
}
Expand Down
2 changes: 2 additions & 0 deletions client/src/rpc_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub enum RpcRequest {
ValidatorExit,
GetAccountInfo,
GetBalance,
GetBlockTime,
GetClusterNodes,
GetEpochInfo,
GetEpochSchedule,
Expand Down Expand Up @@ -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",
Expand Down
78 changes: 77 additions & 1 deletion core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ 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,
hash::Hash,
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};
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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);
}
}
10 changes: 6 additions & 4 deletions genesis/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 58c144e

Please sign in to comment.