From a2857928a433662c71f6da391577bd3fcdb09291 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Apr 2021 02:26:45 +0000 Subject: [PATCH] Rpc: introduce get_inflation_reward rpc call (#16278) (#16410) * 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 (cherry picked from commit e501fa5f0b8cd7a30c0420a34feac3b2510117eb) Co-authored-by: Josh --- client/src/rpc_client.rs | 27 ++++- client/src/rpc_config.rs | 8 +- client/src/rpc_request.rs | 2 + client/src/rpc_response.rs | 9 ++ core/src/rpc.rs | 123 ++++++++++++++++++++- docs/src/developing/clients/jsonrpc-api.md | 53 +++++++++ 6 files changed, 213 insertions(+), 9 deletions(-) diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 6c22d4a22c350b..cc09118d2109e7 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -5,10 +5,10 @@ use { mock_sender::{MockSender, Mocks}, rpc_config::RpcAccountInfoConfig, rpc_config::{ - RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, + RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcEpochConfig, RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig, - RpcStakeConfig, RpcTokenAccountsFilter, + RpcTokenAccountsFilter, }, rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, rpc_response::*, @@ -436,7 +436,7 @@ impl RpcClient { RpcRequest::GetStakeActivation, json!([ stake_account.to_string(), - RpcStakeConfig { + RpcEpochConfig { epoch, commitment: Some(self.commitment_config), } @@ -764,6 +764,27 @@ impl RpcClient { self.send(RpcRequest::GetInflationRate, Value::Null) } + pub fn get_inflation_reward( + &self, + addresses: &[Pubkey], + epoch: Option, + ) -> ClientResult>> { + 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 { self.send(RpcRequest::GetVersion, Value::Null) } diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index a168872c36b80c..2c6a23d7bf48f0 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -50,7 +50,7 @@ pub struct RpcLargestAccountsConfig { #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RpcStakeConfig { +pub struct RpcEpochConfig { pub epoch: Option, #[serde(flatten)] pub commitment: Option, @@ -161,6 +161,12 @@ impl RpcConfirmedBlockConfig { } } +impl From for RpcEncodingConfigWrapper { + fn from(config: RpcConfirmedBlockConfig) -> Self { + RpcEncodingConfigWrapper::Current(Some(config)) + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcConfirmedTransactionConfig { diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index f0859c3a00f218..5a339b833b97ca 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -30,6 +30,7 @@ pub enum RpcRequest { GetIdentity, GetInflationGovernor, GetInflationRate, + GetInflationReward, GetLargestAccounts, GetLeaderSchedule, GetMaxRetransmitSlot, @@ -91,6 +92,7 @@ impl fmt::Display for RpcRequest { RpcRequest::GetIdentity => "getIdentity", RpcRequest::GetInflationGovernor => "getInflationGovernor", RpcRequest::GetInflationRate => "getInflationRate", + RpcRequest::GetInflationReward => "getInflationReward", RpcRequest::GetLargestAccounts => "getLargestAccounts", RpcRequest::GetLeaderSchedule => "getLeaderSchedule", RpcRequest::GetMaxRetransmitSlot => "getMaxRetransmitSlot", diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index 068d18d7b834f8..0219f0567a623b 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -362,6 +362,15 @@ pub struct RpcPerfSample { 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 for RpcConfirmedTransactionStatusWithSignature { fn from(value: ConfirmedTransactionStatusWithSignature) -> Self { let ConfirmedTransactionStatusWithSignature { diff --git a/core/src/rpc.rs b/core/src/rpc.rs index f81097d4258d91..6794a672fadef5 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -70,8 +70,8 @@ use solana_sdk::{ }; use solana_stake_program::stake_state::StakeState; use solana_transaction_status::{ - EncodedConfirmedTransaction, TransactionConfirmationStatus, TransactionStatus, - UiConfirmedBlock, UiTransactionEncoding, + EncodedConfirmedTransaction, Reward, RewardType, TransactionConfirmationStatus, + TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, }; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use spl_token_v2_0::{ @@ -382,6 +382,92 @@ impl JsonRpcRequestProcessor { Ok(result) } + pub fn get_inflation_reward( + &self, + addresses: Vec, + config: Option, + ) -> Result>> { + 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 = addresses + .into_iter() + .map(|pubkey| pubkey.to_string()) + .collect(); + + let reward_hash: HashMap = 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( &self, commitment: Option, @@ -1258,7 +1344,7 @@ impl JsonRpcRequestProcessor { pub fn get_stake_activation( &self, pubkey: &Pubkey, - config: Option, + config: Option, ) -> Result { let config = config.unwrap_or_default(); let bank = self.bank(config.commitment); @@ -2184,6 +2270,14 @@ pub mod rpc_full { commitment: Option, ) -> Result; + #[rpc(meta, name = "getInflationReward")] + fn get_inflation_reward( + &self, + meta: Self::Metadata, + address_strs: Vec, + config: Option, + ) -> Result>>; + #[rpc(meta, name = "getInflationGovernor")] fn get_inflation_governor( &self, @@ -2388,7 +2482,7 @@ pub mod rpc_full { &self, meta: Self::Metadata, pubkey_str: String, - config: Option, + config: Option, ) -> Result; // SPL Token-specific RPC endpoints @@ -3127,7 +3221,7 @@ pub mod rpc_full { &self, meta: Self::Metadata, pubkey_str: String, - config: Option, + config: Option, ) -> Result { debug!( "get_stake_activation rpc request received: {:?}", @@ -3137,6 +3231,25 @@ pub mod rpc_full { meta.get_stake_activation(&pubkey, config) } + fn get_inflation_reward( + &self, + meta: Self::Metadata, + address_strs: Vec, + config: Option, + ) -> Result>> { + debug!( + "get_inflation_reward rpc request received: {:?}", + address_strs.len() + ); + + let mut addresses: Vec = vec![]; + for address_str in address_strs { + addresses.push(verify_pubkey(address_str)?); + } + + meta.get_inflation_reward(addresses, config) + } + fn get_token_account_balance( &self, meta: Self::Metadata, diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index f6e1104bc809a3..9d8984bc556acf 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -40,6 +40,7 @@ gives a convenient interface for the RPC methods. - [getIdentity](jsonrpc-api.md#getidentity) - [getInflationGovernor](jsonrpc-api.md#getinflationgovernor) - [getInflationRate](jsonrpc-api.md#getinflationrate) +- [getInflationReward](jsonrpc-api.md#getinflationreward) - [getLargestAccounts](jsonrpc-api.md#getlargestaccounts) - [getLeaderSchedule](jsonrpc-api.md#getleaderschedule) - [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} ``` +### getInflationReward + +Returns the inflation reward for a list of addresses for an epoch + +#### Parameters: +- `` - An array of addresses to query, as base-58 encoded strings +* `` - (optional) Configuration object containing the following optional fields: + * (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) + * (optional) `epoch: ` - 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: `, epoch +- `effective_slot: `, the slot in which the rewards are effective +- `amount: `, reward amount in lamports +- `post_balance: `, 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 Returns the 20 largest accounts, by lamport balance (results may be cached up to two hours)