Rpc: Add getStakeActivation endpoint (#10902)
* Add getStakeActivation endpoint * Add docs * Update docs/src/apps/jsonrpc-api.md Co-authored-by: Michael Vines <mvines@gmail.com> * Rework return type * Update docs * Rebase Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
@ -37,7 +37,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 RpcInflationConfig {
|
pub struct RpcStakeConfig {
|
||||||
pub epoch: Option<Epoch>,
|
pub epoch: Option<Epoch>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub commitment: Option<CommitmentConfig>,
|
pub commitment: Option<CommitmentConfig>,
|
||||||
|
@ -202,3 +202,20 @@ pub struct RpcSupply {
|
|||||||
pub non_circulating: u64,
|
pub non_circulating: u64,
|
||||||
pub non_circulating_accounts: Vec<String>,
|
pub non_circulating_accounts: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum StakeActivationState {
|
||||||
|
Activating,
|
||||||
|
Active,
|
||||||
|
Deactivating,
|
||||||
|
Inactive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RpcStakeActivation {
|
||||||
|
pub state: StakeActivationState,
|
||||||
|
pub active: u64,
|
||||||
|
pub inactive: u64,
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ use solana_runtime::{
|
|||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
};
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
account_utils::StateMut,
|
||||||
clock::{Slot, UnixTimestamp},
|
clock::{Slot, UnixTimestamp},
|
||||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||||
epoch_info::EpochInfo,
|
epoch_info::EpochInfo,
|
||||||
@ -37,9 +38,12 @@ use solana_sdk::{
|
|||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
|
stake_history::StakeHistory,
|
||||||
|
sysvar::{stake_history, Sysvar},
|
||||||
timing::slot_duration_from_slots_per_year,
|
timing::slot_duration_from_slots_per_year,
|
||||||
transaction::{self, Transaction},
|
transaction::{self, Transaction},
|
||||||
};
|
};
|
||||||
|
use solana_stake_program::stake_state::StakeState;
|
||||||
use solana_transaction_status::{
|
use solana_transaction_status::{
|
||||||
ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
|
ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
|
||||||
};
|
};
|
||||||
@ -755,6 +759,67 @@ impl JsonRpcRequestProcessor {
|
|||||||
.get_first_available_block()
|
.get_first_available_block()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_stake_activation(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
config: Option<RpcStakeConfig>,
|
||||||
|
) -> Result<RpcStakeActivation> {
|
||||||
|
let config = config.unwrap_or_default();
|
||||||
|
let bank = self.bank(config.commitment);
|
||||||
|
let epoch = config.epoch.unwrap_or_else(|| bank.epoch());
|
||||||
|
if bank.epoch().saturating_sub(epoch) > solana_sdk::stake_history::MAX_ENTRIES as u64 {
|
||||||
|
return Err(Error::invalid_params(format!(
|
||||||
|
"Invalid param: epoch {:?} is too far in the past",
|
||||||
|
epoch
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if epoch > bank.epoch() {
|
||||||
|
return Err(Error::invalid_params(format!(
|
||||||
|
"Invalid param: epoch {:?} has not yet started",
|
||||||
|
epoch
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stake_account = bank
|
||||||
|
.get_account(pubkey)
|
||||||
|
.ok_or_else(|| Error::invalid_params("Invalid param: account not found".to_string()))?;
|
||||||
|
let stake_state: StakeState = stake_account
|
||||||
|
.state()
|
||||||
|
.map_err(|_| Error::invalid_params("Invalid param: not a stake account".to_string()))?;
|
||||||
|
let delegation = stake_state.delegation().ok_or_else(|| {
|
||||||
|
Error::invalid_params("Invalid param: stake account has not been delegated".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let stake_history_account = bank
|
||||||
|
.get_account(&stake_history::id())
|
||||||
|
.ok_or_else(Error::internal_error)?;
|
||||||
|
let stake_history =
|
||||||
|
StakeHistory::from_account(&stake_history_account).ok_or_else(Error::internal_error)?;
|
||||||
|
|
||||||
|
let (active, activating, deactivating) =
|
||||||
|
delegation.stake_activating_and_deactivating(epoch, Some(&stake_history));
|
||||||
|
let stake_activation_state = if deactivating > 0 {
|
||||||
|
StakeActivationState::Deactivating
|
||||||
|
} else if activating > 0 {
|
||||||
|
StakeActivationState::Activating
|
||||||
|
} else if active > 0 {
|
||||||
|
StakeActivationState::Active
|
||||||
|
} else {
|
||||||
|
StakeActivationState::Inactive
|
||||||
|
};
|
||||||
|
let inactive_stake = match stake_activation_state {
|
||||||
|
StakeActivationState::Activating => activating,
|
||||||
|
StakeActivationState::Active => 0,
|
||||||
|
StakeActivationState::Deactivating => delegation.stake.saturating_sub(active),
|
||||||
|
StakeActivationState::Inactive => delegation.stake,
|
||||||
|
};
|
||||||
|
Ok(RpcStakeActivation {
|
||||||
|
state: stake_activation_state,
|
||||||
|
active,
|
||||||
|
inactive: inactive_stake,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_filter(input: &RpcFilterType) -> Result<()> {
|
fn verify_filter(input: &RpcFilterType) -> Result<()> {
|
||||||
@ -1062,6 +1127,14 @@ pub trait RpcSol {
|
|||||||
|
|
||||||
#[rpc(meta, name = "getFirstAvailableBlock")]
|
#[rpc(meta, name = "getFirstAvailableBlock")]
|
||||||
fn get_first_available_block(&self, meta: Self::Metadata) -> Result<Slot>;
|
fn get_first_available_block(&self, meta: Self::Metadata) -> Result<Slot>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getStakeActivation")]
|
||||||
|
fn get_stake_activation(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
pubkey_str: String,
|
||||||
|
config: Option<RpcStakeConfig>,
|
||||||
|
) -> Result<RpcStakeActivation>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcSolImpl;
|
pub struct RpcSolImpl;
|
||||||
@ -1589,6 +1662,20 @@ impl RpcSol for RpcSolImpl {
|
|||||||
fn get_first_available_block(&self, meta: Self::Metadata) -> Result<Slot> {
|
fn get_first_available_block(&self, meta: Self::Metadata) -> Result<Slot> {
|
||||||
Ok(meta.get_first_available_block())
|
Ok(meta.get_first_available_block())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_stake_activation(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
pubkey_str: String,
|
||||||
|
config: Option<RpcStakeConfig>,
|
||||||
|
) -> Result<RpcStakeActivation> {
|
||||||
|
debug!(
|
||||||
|
"get_stake_activation rpc request received: {:?}",
|
||||||
|
pubkey_str
|
||||||
|
);
|
||||||
|
let pubkey = verify_pubkey(pubkey_str)?;
|
||||||
|
meta.get_stake_activation(&pubkey, config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_bs58_transaction(bs58_transaction: String) -> Result<(Vec<u8>, Transaction)> {
|
fn deserialize_bs58_transaction(bs58_transaction: String) -> Result<(Vec<u8>, Transaction)> {
|
||||||
|
@ -41,6 +41,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
|||||||
* [getSignatureStatuses](jsonrpc-api.md#getsignaturestatuses)
|
* [getSignatureStatuses](jsonrpc-api.md#getsignaturestatuses)
|
||||||
* [getSlot](jsonrpc-api.md#getslot)
|
* [getSlot](jsonrpc-api.md#getslot)
|
||||||
* [getSlotLeader](jsonrpc-api.md#getslotleader)
|
* [getSlotLeader](jsonrpc-api.md#getslotleader)
|
||||||
|
* [getStakeActivation](jsonrpc-api.md#getstakeactivation)
|
||||||
* [getSupply](jsonrpc-api.md#getsupply)
|
* [getSupply](jsonrpc-api.md#getsupply)
|
||||||
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
||||||
* [getVersion](jsonrpc-api.md#getversion)
|
* [getVersion](jsonrpc-api.md#getversion)
|
||||||
@ -943,6 +944,41 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
|||||||
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### getStakeActivation
|
||||||
|
|
||||||
|
Returns epoch activation information for a stake account
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
* `<string>` - Pubkey of stake account to query, as base-58 encoded string
|
||||||
|
* `<object>` - (optional) Configuration object containing the following optional fields:
|
||||||
|
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
* (optional) `epoch: <u64>` - epoch for which to calculate activation details. If parameter not provided, defaults to current epoch.
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
The result will be a JSON object with the following fields:
|
||||||
|
|
||||||
|
* `state: <string` - the stake account's activation state, one of: `active`, `inactive`, `activating`, `deactivating`
|
||||||
|
* `active: <u64>` - stake active during the epoch
|
||||||
|
* `inactive: <u64>` - stake inactive during the epoch
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStakeActivation", "params": ["CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT"]}' http://localhost:8899
|
||||||
|
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"active":197717120,"inactive":0,"state":"active"},"id":1}
|
||||||
|
|
||||||
|
// Request with Epoch
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStakeActivation", "params": ["CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT", {"epoch": 4}]}' http://localhost:8899
|
||||||
|
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"active":124429280,"inactive":73287840,"state":"activating"},"id":1}
|
||||||
|
```
|
||||||
|
|
||||||
### getSupply
|
### getSupply
|
||||||
|
|
||||||
Returns information about the current supply.
|
Returns information about the current supply.
|
||||||
|
Reference in New Issue
Block a user