Add getBlockProduction RPC method

This commit is contained in:
Michael Vines 2021-04-28 09:57:05 -07:00
parent cfc1cb1aee
commit 542d88929f
6 changed files with 289 additions and 29 deletions

View File

@ -444,6 +444,18 @@ impl RpcClient {
})
}
/// Get block production for the current epoch
pub fn get_block_production(&self) -> RpcResult<RpcBlockProduction> {
self.send(RpcRequest::GetBlockProduction, Value::Null)
}
pub fn get_block_production_with_config(
&self,
config: RpcBlockProductionConfig,
) -> RpcResult<RpcBlockProduction> {
self.send(RpcRequest::GetBlockProduction, json!(config))
}
pub fn get_stake_activation(
&self,
stake_account: Pubkey,

View File

@ -49,6 +49,22 @@ pub struct RpcLeaderScheduleConfig {
pub commitment: Option<CommitmentConfig>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockProductionConfigRange {
pub first_slot: Slot,
pub last_slot: Option<Slot>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockProductionConfig {
pub identity: Option<String>, // validator identity, as a base-58 encoded string
pub range: Option<RpcBlockProductionConfigRange>, // current epoch if `None`
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcGetVoteAccountsConfig {

View File

@ -12,6 +12,7 @@ pub enum RpcRequest {
GetAccountInfo,
GetBalance,
GetBlock,
GetBlockProduction,
GetBlocks,
GetBlocksWithLimit,
GetBlockTime,
@ -94,6 +95,7 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetAccountInfo => "getAccountInfo",
RpcRequest::GetBalance => "getBalance",
RpcRequest::GetBlock => "getBlock",
RpcRequest::GetBlockProduction => "getBlockProduction",
RpcRequest::GetBlocks => "getBlocks",
RpcRequest::GetBlocksWithLimit => "getBlocksWithLimit",
RpcRequest::GetBlockTime => "getBlockTime",

View File

@ -211,6 +211,21 @@ pub struct RpcContactInfo {
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockProductionRange {
pub first_slot: Slot,
pub last_slot: Slot,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockProduction {
/// Map of leader base58 identity pubkeys to a tuple of `(number of leader slots, number of blocks produced)`
pub by_identity: HashMap<String, (usize, usize)>,
pub range: RpcBlockProductionRange,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct RpcVersionInfo {

View File

@ -604,6 +604,43 @@ impl JsonRpcRequestProcessor {
self.bank(commitment).collector_id().to_string()
}
fn get_slot_leaders(
&self,
commitment: Option<CommitmentConfig>,
start_slot: Slot,
limit: usize,
) -> Result<Vec<Pubkey>> {
let bank = self.bank(commitment);
let (mut epoch, mut slot_index) =
bank.epoch_schedule().get_epoch_and_slot_index(start_slot);
let mut slot_leaders = Vec::with_capacity(limit);
while slot_leaders.len() < limit {
if let Some(leader_schedule) =
self.leader_schedule_cache.get_epoch_leader_schedule(epoch)
{
slot_leaders.extend(
leader_schedule
.get_slot_leaders()
.iter()
.skip(slot_index as usize)
.take(limit.saturating_sub(slot_leaders.len())),
);
} else {
return Err(Error::invalid_params(format!(
"Invalid slot range: leader schedule for epoch {} is unavailable",
epoch
)));
}
epoch += 1;
slot_index = 0;
}
Ok(slot_leaders)
}
fn minimum_ledger_slot(&self) -> Result<Slot> {
match self.blockstore.slot_meta_iterator(0) {
Ok(mut metas) => match metas.next() {
@ -2498,6 +2535,13 @@ pub mod rpc_full {
config: Option<RpcEpochConfig>,
) -> Result<RpcStakeActivation>;
#[rpc(meta, name = "getBlockProduction")]
fn get_block_production(
&self,
meta: Self::Metadata,
config: Option<RpcBlockProductionConfig>,
) -> Result<RpcResponse<RpcBlockProduction>>;
// SPL Token-specific RPC endpoints
// See https://github.com/solana-labs/solana-program-library/releases/tag/token-v2.0.0 for
// program details
@ -3009,35 +3053,11 @@ pub mod rpc_full {
)));
}
let bank = meta.bank(None);
let (mut epoch, mut slot_index) =
bank.epoch_schedule().get_epoch_and_slot_index(start_slot);
let mut slot_leaders = Vec::with_capacity(limit);
while slot_leaders.len() < limit {
if let Some(leader_schedule) =
meta.leader_schedule_cache.get_epoch_leader_schedule(epoch)
{
slot_leaders.extend(
leader_schedule
.get_slot_leaders()
.iter()
.skip(slot_index as usize)
.take(limit.saturating_sub(slot_leaders.len()))
.map(|pubkey| pubkey.to_string()),
);
} else {
return Err(Error::invalid_params(format!(
"Invalid slot range: leader schedule for epoch {} is unavailable",
epoch
)));
}
epoch += 1;
slot_index = 0;
}
Ok(slot_leaders)
Ok(meta
.get_slot_leaders(None, start_slot, limit)?
.into_iter()
.map(|identity| identity.to_string())
.collect())
}
fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result<Slot> {
@ -3154,6 +3174,94 @@ pub mod rpc_full {
meta.get_stake_activation(&pubkey, config)
}
fn get_block_production(
&self,
meta: Self::Metadata,
config: Option<RpcBlockProductionConfig>,
) -> Result<RpcResponse<RpcBlockProduction>> {
debug!("get_block_production rpc request received");
let config = config.unwrap_or_default();
let filter_by_identity = if let Some(ref identity) = config.identity {
Some(verify_pubkey(identity)?)
} else {
None
};
let bank = meta.bank(config.commitment);
let (first_slot, last_slot) = match config.range {
None => (
bank.epoch_schedule().get_first_slot_in_epoch(bank.epoch()),
bank.slot(),
),
Some(range) => {
let first_slot = range.first_slot;
let last_slot = range.last_slot.unwrap_or_else(|| bank.slot());
if last_slot < first_slot {
return Err(Error::invalid_params(format!(
"lastSlot, {}, cannot be less than firstSlot, {}",
last_slot, first_slot
)));
}
(first_slot, last_slot)
}
};
let slot_history = bank.get_slot_history();
if first_slot < slot_history.oldest() {
return Err(Error::invalid_params(format!(
"firstSlot, {}, is too small; min {}",
first_slot,
slot_history.oldest()
)));
}
if last_slot > slot_history.newest() {
return Err(Error::invalid_params(format!(
"lastSlot, {}, is too large; max {}",
last_slot,
slot_history.newest()
)));
}
let slot_leaders = meta.get_slot_leaders(
config.commitment,
first_slot,
last_slot.saturating_sub(first_slot) as usize + 1, // +1 because last_slot is inclusive
)?;
let mut block_production: HashMap<_, (usize, usize)> = HashMap::new();
let mut slot = first_slot;
for identity in slot_leaders {
slot += 1;
if let Some(ref filter_by_identity) = filter_by_identity {
if identity != *filter_by_identity {
continue;
}
}
let mut entry = block_production.entry(identity).or_default();
if slot_history.check(slot) == solana_sdk::slot_history::Check::Found {
entry.1 += 1; // Increment blocks_produced
}
entry.0 += 1; // Increment leader_slots
}
Ok(new_response(
&bank,
RpcBlockProduction {
by_identity: block_production
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
range: RpcBlockProductionRange {
first_slot,
last_slot,
},
},
))
}
fn get_inflation_reward(
&self,
meta: Self::Metadata,

View File

@ -21,6 +21,7 @@ gives a convenient interface for the RPC methods.
- [getAccountInfo](jsonrpc-api.md#getaccountinfo)
- [getBalance](jsonrpc-api.md#getbalance)
- [getBlock](jsonrpc-api.md#getblock)
- [getBlockProduction](jsonrpc-api.md#getblockproduction)
- [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
- [getBlocks](jsonrpc-api.md#getblocks)
- [getBlocksWithLimit](jsonrpc-api.md#getblockswithlimit)
@ -573,6 +574,112 @@ The JSON structure of token balances is defined as a list of objects in the foll
- `uiAmount: <number | null>` - Token amount as a float, accounting for decimals. **DEPRECATED**
- `uiAmountString: <string>` - Token amount as a string, accounting for decimals.
### getBlockProduction
Returns recent block production information from the current or previous epoch.
#### Parameters:
- `<object>` - (optional) Configuration object containing the following optional fields:
- (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
- (optional) `range: <object>` - Slot range to return block production for. If parameter not provided, defaults to current epoch.
- `firstSlot: <u64>` - first slot to return block production information for (inclusive)
- (optional) `lastSlot: <u64>` - last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot
- (optional) `identity: <string>` - Only return results for this validator identity (base-58 encoded)
#### Results:
The result will be an RpcResponse JSON object with `value` equal to:
- `<object>`
- `byIdentity: <object>` - a dictionary of validator identities,
as base-58 encoded strings. Value is a two element array containing the
number of leader slots and the number of blocks produced.
- `range: <object>` - Slot range to return block production for. If parameter not provided, defaults to current epoch.
- `firstSlot: <u64>` - first slot of the block production information (inclusive)
- `lastSlot: <u64>` - last slot of block production information (inclusive)
#### Example:
Request:
```bash
curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '
{"jsonrpc":"2.0","id":1, "method":"getBlockProduction"}
'
```
Result:
```json
{
"jsonrpc": "2.0",
"result": {
"context": {
"slot": 9887
},
"value": {
"byIdentity": {
"85iYT5RuzRTDgjyRa3cP8SYhM2j21fj7NhfJ3peu1DPr": [
9888,
9886
]
},
"range": {
"firstSlot": 0,
"lastSlot": 9887,
}
}
},
"id": 1
}
```
#### Example:
Request:
```bash
curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockProduction",
"params": [
{
"identity": "85iYT5RuzRTDgjyRa3cP8SYhM2j21fj7NhfJ3peu1DPr",
"range": {
"firstSlot": 40,
"lastSlot": 50
}
}
]
}
'
```
Result:
```json
{
"jsonrpc": "2.0",
"result": {
"context": {
"slot": 10102
},
"value": {
"byIdentity": {
"85iYT5RuzRTDgjyRa3cP8SYhM2j21fj7NhfJ3peu1DPr": [
11,
11
]
},
"range": {
"firstSlot": 50,
"lastSlot": 40
}
}
},
"id": 1
}
```
### getBlockCommitment
Returns commitment for particular block