Rpc: introduce get_inflation_reward rpc call (#16278)

* feat: introduce get_inflation_reward rpc call

* fix: style suggestions

* fix: more style changes and match how other rpc functions are defined

* feat: get reward for a single epoch

* feat: default to the most recent epoch

* fix: don't factor out get_confirmed_block

* style: introduce from impl for RpcEncodingConfigWrapper

* style: bring commitment into variable

* feat: support multiple pubkeys for get_inflation_reward

* feat: add get_inflation_reward to rpc client

* feat: return rewards in order

* fix: rename pubkeys to addresses

* docs: introduce jsonrpc docs for get_inflation_reward

* style: early return in map (not sure which is more idiomatic)

* fix: call the rpc client function args addresses as well

* fix: style

* fix: filter out only addresses we care about

* style: make this more idiomatic

* fix: change rpc client epoch to optional and include some docs edits

* feat: filter out rent rewards in get_inflation_reward

* feat: add option epoch config param to get_inflation_reward

* feat: rpc client get_inflation_reward takes epoch instead of config and some filter staking and voting rewards
This commit is contained in:
Josh
2021-04-06 18:10:53 -07:00
committed by GitHub
parent 92f4018b07
commit e501fa5f0b
6 changed files with 213 additions and 9 deletions

View File

@ -5,10 +5,10 @@ use {
mock_sender::{MockSender, Mocks}, mock_sender::{MockSender, Mocks},
rpc_config::RpcAccountInfoConfig, rpc_config::RpcAccountInfoConfig,
rpc_config::{ rpc_config::{
RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcEpochConfig,
RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig, RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig,
RpcStakeConfig, RpcTokenAccountsFilter, RpcTokenAccountsFilter,
}, },
rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter},
rpc_response::*, rpc_response::*,
@ -436,7 +436,7 @@ impl RpcClient {
RpcRequest::GetStakeActivation, RpcRequest::GetStakeActivation,
json!([ json!([
stake_account.to_string(), stake_account.to_string(),
RpcStakeConfig { RpcEpochConfig {
epoch, epoch,
commitment: Some(self.commitment_config), commitment: Some(self.commitment_config),
} }
@ -764,6 +764,27 @@ impl RpcClient {
self.send(RpcRequest::GetInflationRate, Value::Null) self.send(RpcRequest::GetInflationRate, Value::Null)
} }
pub fn get_inflation_reward(
&self,
addresses: &[Pubkey],
epoch: Option<Epoch>,
) -> ClientResult<Vec<Option<RpcInflationReward>>> {
let addresses: Vec<_> = addresses
.iter()
.map(|address| address.to_string())
.collect();
self.send(
RpcRequest::GetInflationReward,
json!([
addresses,
RpcEpochConfig {
epoch,
commitment: Some(self.commitment_config),
}
]),
)
}
pub fn get_version(&self) -> ClientResult<RpcVersionInfo> { pub fn get_version(&self) -> ClientResult<RpcVersionInfo> {
self.send(RpcRequest::GetVersion, Value::Null) self.send(RpcRequest::GetVersion, Value::Null)
} }

View File

@ -50,7 +50,7 @@ pub struct RpcLargestAccountsConfig {
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcStakeConfig { pub struct RpcEpochConfig {
pub epoch: Option<Epoch>, pub epoch: Option<Epoch>,
#[serde(flatten)] #[serde(flatten)]
pub commitment: Option<CommitmentConfig>, pub commitment: Option<CommitmentConfig>,
@ -161,6 +161,12 @@ impl RpcConfirmedBlockConfig {
} }
} }
impl From<RpcConfirmedBlockConfig> for RpcEncodingConfigWrapper<RpcConfirmedBlockConfig> {
fn from(config: RpcConfirmedBlockConfig) -> Self {
RpcEncodingConfigWrapper::Current(Some(config))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcConfirmedTransactionConfig { pub struct RpcConfirmedTransactionConfig {

View File

@ -30,6 +30,7 @@ pub enum RpcRequest {
GetIdentity, GetIdentity,
GetInflationGovernor, GetInflationGovernor,
GetInflationRate, GetInflationRate,
GetInflationReward,
GetLargestAccounts, GetLargestAccounts,
GetLeaderSchedule, GetLeaderSchedule,
GetMaxRetransmitSlot, GetMaxRetransmitSlot,
@ -91,6 +92,7 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetIdentity => "getIdentity", RpcRequest::GetIdentity => "getIdentity",
RpcRequest::GetInflationGovernor => "getInflationGovernor", RpcRequest::GetInflationGovernor => "getInflationGovernor",
RpcRequest::GetInflationRate => "getInflationRate", RpcRequest::GetInflationRate => "getInflationRate",
RpcRequest::GetInflationReward => "getInflationReward",
RpcRequest::GetLargestAccounts => "getLargestAccounts", RpcRequest::GetLargestAccounts => "getLargestAccounts",
RpcRequest::GetLeaderSchedule => "getLeaderSchedule", RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
RpcRequest::GetMaxRetransmitSlot => "getMaxRetransmitSlot", RpcRequest::GetMaxRetransmitSlot => "getMaxRetransmitSlot",

View File

@ -362,6 +362,15 @@ pub struct RpcPerfSample {
pub sample_period_secs: u16, pub sample_period_secs: u16,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcInflationReward {
pub epoch: Epoch,
pub effective_slot: Slot,
pub amount: u64, // lamports
pub post_balance: u64, // lamports
}
impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature { impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
fn from(value: ConfirmedTransactionStatusWithSignature) -> Self { fn from(value: ConfirmedTransactionStatusWithSignature) -> Self {
let ConfirmedTransactionStatusWithSignature { let ConfirmedTransactionStatusWithSignature {

View File

@ -70,8 +70,8 @@ use solana_sdk::{
}; };
use solana_stake_program::stake_state::StakeState; use solana_stake_program::stake_state::StakeState;
use solana_transaction_status::{ use solana_transaction_status::{
EncodedConfirmedTransaction, TransactionConfirmationStatus, TransactionStatus, EncodedConfirmedTransaction, Reward, RewardType, TransactionConfirmationStatus,
UiConfirmedBlock, UiTransactionEncoding, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding,
}; };
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
use spl_token_v2_0::{ use spl_token_v2_0::{
@ -382,6 +382,92 @@ impl JsonRpcRequestProcessor {
Ok(result) Ok(result)
} }
pub fn get_inflation_reward(
&self,
addresses: Vec<Pubkey>,
config: Option<RpcEpochConfig>,
) -> Result<Vec<Option<RpcInflationReward>>> {
let config = config.unwrap_or_default();
let epoch_schedule = self.get_epoch_schedule();
let first_available_block = self.get_first_available_block();
let epoch = config.epoch.unwrap_or_else(|| {
epoch_schedule
.get_epoch(self.get_slot(config.commitment))
.saturating_sub(1)
});
// Rewards for this epoch are found in the first confirmed block of the next epoch
let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch.saturating_add(1));
if first_slot_in_epoch < first_available_block {
if self.bigtable_ledger_storage.is_some() {
return Err(RpcCustomError::LongTermStorageSlotSkipped {
slot: first_slot_in_epoch,
}
.into());
} else {
return Err(RpcCustomError::BlockCleanedUp {
slot: first_slot_in_epoch,
first_available_block,
}
.into());
}
}
let first_confirmed_block_in_epoch = *self
.get_confirmed_blocks_with_limit(first_slot_in_epoch, 1, config.commitment)?
.get(0)
.ok_or(RpcCustomError::BlockNotAvailable {
slot: first_slot_in_epoch,
})?;
let first_confirmed_block = if let Ok(Some(first_confirmed_block)) = self
.get_confirmed_block(
first_confirmed_block_in_epoch,
Some(RpcConfirmedBlockConfig::rewards_only().into()),
) {
first_confirmed_block
} else {
return Err(RpcCustomError::BlockNotAvailable {
slot: first_confirmed_block_in_epoch,
}
.into());
};
let addresses: Vec<String> = addresses
.into_iter()
.map(|pubkey| pubkey.to_string())
.collect();
let reward_hash: HashMap<String, Reward> = first_confirmed_block
.rewards
.unwrap_or_default()
.into_iter()
.filter_map(|reward| match reward.reward_type? {
RewardType::Staking | RewardType::Voting => addresses
.contains(&reward.pubkey)
.then(|| (reward.clone().pubkey, reward)),
_ => None,
})
.collect();
let rewards = addresses
.iter()
.map(|address| {
if let Some(reward) = reward_hash.get(address) {
return Some(RpcInflationReward {
epoch,
effective_slot: first_confirmed_block_in_epoch,
amount: reward.lamports.abs() as u64,
post_balance: reward.post_balance,
});
}
None
})
.collect();
Ok(rewards)
}
pub fn get_inflation_governor( pub fn get_inflation_governor(
&self, &self,
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
@ -1258,7 +1344,7 @@ impl JsonRpcRequestProcessor {
pub fn get_stake_activation( pub fn get_stake_activation(
&self, &self,
pubkey: &Pubkey, pubkey: &Pubkey,
config: Option<RpcStakeConfig>, config: Option<RpcEpochConfig>,
) -> Result<RpcStakeActivation> { ) -> Result<RpcStakeActivation> {
let config = config.unwrap_or_default(); let config = config.unwrap_or_default();
let bank = self.bank(config.commitment); let bank = self.bank(config.commitment);
@ -2184,6 +2270,14 @@ pub mod rpc_full {
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
) -> Result<u64>; ) -> Result<u64>;
#[rpc(meta, name = "getInflationReward")]
fn get_inflation_reward(
&self,
meta: Self::Metadata,
address_strs: Vec<String>,
config: Option<RpcEpochConfig>,
) -> Result<Vec<Option<RpcInflationReward>>>;
#[rpc(meta, name = "getInflationGovernor")] #[rpc(meta, name = "getInflationGovernor")]
fn get_inflation_governor( fn get_inflation_governor(
&self, &self,
@ -2388,7 +2482,7 @@ pub mod rpc_full {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
pubkey_str: String, pubkey_str: String,
config: Option<RpcStakeConfig>, config: Option<RpcEpochConfig>,
) -> Result<RpcStakeActivation>; ) -> Result<RpcStakeActivation>;
// SPL Token-specific RPC endpoints // SPL Token-specific RPC endpoints
@ -3127,7 +3221,7 @@ pub mod rpc_full {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
pubkey_str: String, pubkey_str: String,
config: Option<RpcStakeConfig>, config: Option<RpcEpochConfig>,
) -> Result<RpcStakeActivation> { ) -> Result<RpcStakeActivation> {
debug!( debug!(
"get_stake_activation rpc request received: {:?}", "get_stake_activation rpc request received: {:?}",
@ -3137,6 +3231,25 @@ pub mod rpc_full {
meta.get_stake_activation(&pubkey, config) meta.get_stake_activation(&pubkey, config)
} }
fn get_inflation_reward(
&self,
meta: Self::Metadata,
address_strs: Vec<String>,
config: Option<RpcEpochConfig>,
) -> Result<Vec<Option<RpcInflationReward>>> {
debug!(
"get_inflation_reward rpc request received: {:?}",
address_strs.len()
);
let mut addresses: Vec<Pubkey> = vec![];
for address_str in address_strs {
addresses.push(verify_pubkey(address_str)?);
}
meta.get_inflation_reward(addresses, config)
}
fn get_token_account_balance( fn get_token_account_balance(
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,

View File

@ -40,6 +40,7 @@ gives a convenient interface for the RPC methods.
- [getIdentity](jsonrpc-api.md#getidentity) - [getIdentity](jsonrpc-api.md#getidentity)
- [getInflationGovernor](jsonrpc-api.md#getinflationgovernor) - [getInflationGovernor](jsonrpc-api.md#getinflationgovernor)
- [getInflationRate](jsonrpc-api.md#getinflationrate) - [getInflationRate](jsonrpc-api.md#getinflationrate)
- [getInflationReward](jsonrpc-api.md#getinflationreward)
- [getLargestAccounts](jsonrpc-api.md#getlargestaccounts) - [getLargestAccounts](jsonrpc-api.md#getlargestaccounts)
- [getLeaderSchedule](jsonrpc-api.md#getleaderschedule) - [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
- [getMaxRetransmitSlot](jsonrpc-api.md#getmaxretransmitslot) - [getMaxRetransmitSlot](jsonrpc-api.md#getmaxretransmitslot)
@ -1468,6 +1469,58 @@ Result:
{"jsonrpc":"2.0","result":{"epoch":100,"foundation":0.001,"total":0.149,"validator":0.148},"id":1} {"jsonrpc":"2.0","result":{"epoch":100,"foundation":0.001,"total":0.149,"validator":0.148},"id":1}
``` ```
### getInflationReward
Returns the inflation reward for a list of addresses for an epoch
#### Parameters:
- `<array>` - An array of addresses to query, as base-58 encoded strings
* `<object>` - (optional) Configuration object containing the following optional fields:
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* (optional) `epoch: <u64>` - An epoch for which the reward occurs. If omitted, the previous epoch will be used
#### Results
The result field will be a JSON array with the following fields:
- `epoch: <u64>`, epoch
- `effective_slot: <u64>`, the slot in which the rewards are effective
- `amount: <u64>`, reward amount in lamports
- `post_balance: <u64>`, post balance of the account in lamports
#### Example
Request:
```bash
curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getInflationReward",
"params": [
["6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu", "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2"], 2
]
}
'
```
Response:
```json
{
"jsonrpc": "2.0",
"result": [
{
"amount": 2500,
"effectiveSlot": 224,
"epoch": 2,
"postBalance": 499999442500
},
null
],
"id": 1
}
```
### getLargestAccounts ### getLargestAccounts
Returns the 20 largest accounts, by lamport balance (results may be cached up to two hours) Returns the 20 largest accounts, by lamport balance (results may be cached up to two hours)