anza-xyz / anza-xyz/solana-rpc-client-extensions

Past epoch stake activation

Open
#21 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
20
Forks
18
PR merge metrics
No merged PRs in 30d

Description

Hi, I want to share something I built on top of this library and ask for feedback on why it worked at first but then broke.
This library is designed to get stake activation for the current epoch.

I extended it to query stake activation for a specific past epoch by adding a get_stake_activation_by_epoch function.
- I removed rpc_client.get_epoch_info()
- I pass the user-supplied epoch to stake_activating_and_deactivating instead of epoch_info.epoch

```
pub fn get_stake_activation_by_epoch(
rpc_client: &RpcClient,
stake: &Pubkey,
epoch: u64
) -> ClientResult {
let stake_account = rpc_client.get_account(stake)?;
let stake_history_account = rpc_client.get_account(&stake_history::id())?;

let stake_state = bincode::deserialize::(&stake_account.data).map_err(|_| {
Error::from(ErrorKind::Custom(
"Invalid param: stake account not initialized".to_string(),
))
})?;

let delegation = stake_state.delegation();
let rent_exempt_reserve = stake_state
.meta()
.ok_or_else(|| {
Error::from(ErrorKind::Custom(
"Invalid param: stake account not initialized".to_string(),
))
})?
.rent_exempt_reserve;

let delegation = match delegation {
None => {
return Ok(StakeActivation {
state: StakeActivationState::Inactive,
active: 0,
inactive: stake_account.lamports().saturating_sub(rent_exempt_reserve),
})
}
Some(delegation) => delegation,
};

let stake_history =
from_account::(&stake_history_account).ok_or(Error::from(
ErrorKind::Custom("Invalid param: stake history not deserializable".to_string()),
))?;

let StakeActivationStatus {
effective,
activating,
deactivating,
} = delegation.stake_activating_and_deactivating(epoch, &stake_history, Some(0));

let stake_activation_state = if deactivating > 0 {
StakeActivationState::Deactivating
} else if activating > 0 {
StakeActivationState::Activating
} else if effective > 0 {
StakeActivationState::Active
} else {
StakeActivationState::Inactive
};

let inactive_stake = stake_account
.lamports()
.saturating_sub(effective)
.saturating_sub(rent_exempt_reserve);

Ok(StakeActivation {
state: stake_activation_state,
active: effective,
inactive: inactive_stake,
})
}
```
- This worked correctly from approximately January 2025 to June 2025
- Around July 2025 it started returning current active stake value regardless of which past epoch I queried

**My questions**
1. Was my approach fundamentally wrong?
2. If it was wrong, why did it return seemingly correct results for the first 6 months?

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.