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:
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
123
core/src/rpc.rs
123
core/src/rpc.rs
@ -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,
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user