Add getBlockProduction RPC method
This commit is contained in:
@ -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(
|
pub fn get_stake_activation(
|
||||||
&self,
|
&self,
|
||||||
stake_account: Pubkey,
|
stake_account: Pubkey,
|
||||||
|
@ -49,6 +49,22 @@ pub struct RpcLeaderScheduleConfig {
|
|||||||
pub commitment: Option<CommitmentConfig>,
|
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)]
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RpcGetVoteAccountsConfig {
|
pub struct RpcGetVoteAccountsConfig {
|
||||||
|
@ -12,6 +12,7 @@ pub enum RpcRequest {
|
|||||||
GetAccountInfo,
|
GetAccountInfo,
|
||||||
GetBalance,
|
GetBalance,
|
||||||
GetBlock,
|
GetBlock,
|
||||||
|
GetBlockProduction,
|
||||||
GetBlocks,
|
GetBlocks,
|
||||||
GetBlocksWithLimit,
|
GetBlocksWithLimit,
|
||||||
GetBlockTime,
|
GetBlockTime,
|
||||||
@ -94,6 +95,7 @@ impl fmt::Display for RpcRequest {
|
|||||||
RpcRequest::GetAccountInfo => "getAccountInfo",
|
RpcRequest::GetAccountInfo => "getAccountInfo",
|
||||||
RpcRequest::GetBalance => "getBalance",
|
RpcRequest::GetBalance => "getBalance",
|
||||||
RpcRequest::GetBlock => "getBlock",
|
RpcRequest::GetBlock => "getBlock",
|
||||||
|
RpcRequest::GetBlockProduction => "getBlockProduction",
|
||||||
RpcRequest::GetBlocks => "getBlocks",
|
RpcRequest::GetBlocks => "getBlocks",
|
||||||
RpcRequest::GetBlocksWithLimit => "getBlocksWithLimit",
|
RpcRequest::GetBlocksWithLimit => "getBlocksWithLimit",
|
||||||
RpcRequest::GetBlockTime => "getBlockTime",
|
RpcRequest::GetBlockTime => "getBlockTime",
|
||||||
|
@ -211,6 +211,21 @@ pub struct RpcContactInfo {
|
|||||||
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
|
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
|
||||||
pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
|
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)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct RpcVersionInfo {
|
pub struct RpcVersionInfo {
|
||||||
|
166
core/src/rpc.rs
166
core/src/rpc.rs
@ -604,6 +604,43 @@ impl JsonRpcRequestProcessor {
|
|||||||
self.bank(commitment).collector_id().to_string()
|
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> {
|
fn minimum_ledger_slot(&self) -> Result<Slot> {
|
||||||
match self.blockstore.slot_meta_iterator(0) {
|
match self.blockstore.slot_meta_iterator(0) {
|
||||||
Ok(mut metas) => match metas.next() {
|
Ok(mut metas) => match metas.next() {
|
||||||
@ -2498,6 +2535,13 @@ pub mod rpc_full {
|
|||||||
config: Option<RpcEpochConfig>,
|
config: Option<RpcEpochConfig>,
|
||||||
) -> Result<RpcStakeActivation>;
|
) -> 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
|
// SPL Token-specific RPC endpoints
|
||||||
// See https://github.com/solana-labs/solana-program-library/releases/tag/token-v2.0.0 for
|
// See https://github.com/solana-labs/solana-program-library/releases/tag/token-v2.0.0 for
|
||||||
// program details
|
// program details
|
||||||
@ -3009,35 +3053,11 @@ pub mod rpc_full {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bank = meta.bank(None);
|
Ok(meta
|
||||||
let (mut epoch, mut slot_index) =
|
.get_slot_leaders(None, start_slot, limit)?
|
||||||
bank.epoch_schedule().get_epoch_and_slot_index(start_slot);
|
.into_iter()
|
||||||
|
.map(|identity| identity.to_string())
|
||||||
let mut slot_leaders = Vec::with_capacity(limit);
|
.collect())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result<Slot> {
|
fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result<Slot> {
|
||||||
@ -3154,6 +3174,94 @@ pub mod rpc_full {
|
|||||||
meta.get_stake_activation(&pubkey, config)
|
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(
|
fn get_inflation_reward(
|
||||||
&self,
|
&self,
|
||||||
meta: Self::Metadata,
|
meta: Self::Metadata,
|
||||||
|
@ -21,6 +21,7 @@ gives a convenient interface for the RPC methods.
|
|||||||
- [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
- [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
||||||
- [getBalance](jsonrpc-api.md#getbalance)
|
- [getBalance](jsonrpc-api.md#getbalance)
|
||||||
- [getBlock](jsonrpc-api.md#getblock)
|
- [getBlock](jsonrpc-api.md#getblock)
|
||||||
|
- [getBlockProduction](jsonrpc-api.md#getblockproduction)
|
||||||
- [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
- [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
||||||
- [getBlocks](jsonrpc-api.md#getblocks)
|
- [getBlocks](jsonrpc-api.md#getblocks)
|
||||||
- [getBlocksWithLimit](jsonrpc-api.md#getblockswithlimit)
|
- [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**
|
- `uiAmount: <number | null>` - Token amount as a float, accounting for decimals. **DEPRECATED**
|
||||||
- `uiAmountString: <string>` - Token amount as a string, accounting for decimals.
|
- `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
|
### getBlockCommitment
|
||||||
|
|
||||||
Returns commitment for particular block
|
Returns commitment for particular block
|
||||||
|
Reference in New Issue
Block a user