Skip to content

Commit

Permalink
More stake pool details
Browse files Browse the repository at this point in the history
  • Loading branch information
ebatsell committed Aug 20, 2024
1 parent 4d2a6f8 commit ca5ec2d
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 79 deletions.
2 changes: 1 addition & 1 deletion docs/advanced/managing-validator-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ title: Validator States

# Validator States

[Explanation of different validator states and how Steward manages them goes here]
[Explanation of different validator states and how Steward manages them, in progress.]
186 changes: 108 additions & 78 deletions docs/advanced/spl-stake-pool-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ layout: default
title: SPL Stake Pool Internals
---

# SPL Stake Pool Overview
# SPL Stake Pool: Overview

The SPL Stake Pool program allows users to delegate stake to multiple validators while maintaining liquidity through pool tokens.
Thorough documentation of the SPL Stake Pool program can be found [here](https://spl.solana.com/stake-pool/overview). This is meant to be a quick primer, and the Advanced Concepts section contains nuances relevant to the Steward program.

## Key Accounts

1. **Stake Pool**: Central account holding pool configuration and metadata.
1. **Stake Pool**: Main account holding pool configuration and metadata.
2. **Validator List**: Stores information about all validators in the pool.
3. **Reserve Stake**: Holds undelegated stake for the pool.
4. **Validator Stake Accounts**: Individual stake accounts for each validator.
Expand All @@ -19,8 +20,8 @@ The SPL Stake Pool program allows users to delegate stake to multiple validators
## Authorities

1. **Manager**: Controls pool configuration and fees.
2. **Staker**: Manages validator list and stake distribution.
3. **Withdraw Authority**: Program-derived authority for all pool-controlled stake accounts.
2. **Staker**: Manages validator set and stake distribution.
3. **Withdraw Authority**: Program-derived authority for the pool's stake accounts.
4. **Deposit Authority** (optional): Controls stake deposits into the pool.

## Stake Flow
Expand Down Expand Up @@ -68,10 +69,10 @@ These operations are performed via Cross-Program Invocation (CPI) calls from the

## Minimum Lamport Balances

Stake accounts in the pool must maintain minimum balances for two reasons:
Stake accounts in the pool must maintain minimum balances that cover rent and stake minimums:

1. **Rent-Exempt Reserve**: Every stake account must have enough lamports to be rent-exempt.
2. **Minimum Delegation**: Solana enforces a minimum delegation amount for stake accounts.
1. **Rent-Exempt Reserve**: Every stake account must have enough lamports to be rent-exempt (2282880 lamports).
2. **Minimum Delegation**: Solana enforces a minimum delegation amount for stake accounts, which is currently 1 lamport. There is a deactivated feature that will increase this to 1 SOL (1 SOL = 10^9 lamports), but there are no current plans to activate this. spl-stake-pool's own MINIMUM_ACTIVE_STAKE constant is 1_000_000 lamports.

The pool uses the `minimum_stake_lamports` function to calculate this:

Expand All @@ -80,90 +81,51 @@ pub fn minimum_stake_lamports(meta: &Meta, stake_program_minimum_delegation: u64
meta.rent_exempt_reserve
.saturating_add(minimum_delegation(stake_program_minimum_delegation))
}
```

This ensures that stake accounts always have enough lamports to remain valid and delegated.

## Stake Account Rent Funding

The rent for stake accounts comes from different sources depending on the operation:

1. **Validator Addition**: Funded from the pool's reserve account.
2. **User Deposits**: For stake deposits, the rent is part of the deposited stake account. For SOL deposits, it's taken from the deposited amount.
3. **Transient Stake Accounts**: Funded from the pool's reserve when created.

Example of funding a new validator stake account:

```rust
let required_lamports = minimum_stake_lamports(&meta, stake_minimum_delegation);
Self::stake_split(
stake_pool_info.key,
reserve_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
required_lamports,
stake_info.clone(),
)?;
pub fn minimum_delegation(stake_program_minimum_delegation: u64) -> u64 {
std::cmp::max(stake_program_minimum_delegation, MINIMUM_ACTIVE_STAKE)
}
```

## Account Seeds and Their Usage
This ensures that stake accounts always have enough lamports to remain valid and delegated. You cannot decrease or increase a validator account's stake with fewer lamports than the minimum_delegation, because the transient stake account would not have enough lamports to remain valid.

Account seeds are crucial for deterministic derivation of various accounts in the pool:
As of August 2024, the minimum lamports for a stake account is 3_282_880 lamports.

1. **Withdraw Authority**:
Derived using `find_withdraw_authority_program_address`.

```rust
let (withdraw_authority_key, stake_withdraw_bump_seed) =
find_withdraw_authority_program_address(program_id, stake_pool_info.key);
```
## Stake Account Rent Funding

2. **Validator Stake Account**:
Derived using `find_stake_program_address`.
When keeping track of all lamports in the pool, it's important to note the rent for stake accounts in the Stake Pool comes from the reserve account.

1. **Validator Addition**:
When adding a new validator, the rent for the validator's stake account is funded from the pool's reserve account.
```rust
let (stake_address, _) = find_stake_program_address(
program_id,
vote_account_address,
stake_pool_address,
seed,
);
let required_lamports = minimum_stake_lamports(&meta, stake_minimum_delegation);
Self::stake_split(
stake_pool_info.key,
reserve_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
required_lamports,
stake_info.clone(),
)?;
```
2. **Transient Stake Accounts**:
When creating a transient stake account (used for rebalancing), the rent is funded from the pool's reserve account.

3. **Transient Stake Account**:
Derived using `find_transient_stake_program_address`.
```rust
let (transient_stake_address, _) = find_transient_stake_program_address(
program_id,
vote_account_address,
stake_pool_address,
seed,
);
let required_lamports = stake_rent.saturating_add(lamports);
Self::stake_split(
stake_pool_info.key,
reserve_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
required_lamports,
transient_stake_account_info.clone(),
)?;
```

These seeds serve several purposes:

- **Deterministic Derivation**: Allows the program to consistently locate and verify accounts.
- **Authority Delegation**: Enables the program to sign for operations on behalf of the pool.
- **Security**: Ensures that only the program can create and manage these accounts.

When performing operations like stake delegation or withdrawal, the program uses these derived addresses to authorize actions:

```rust
let authority_signature_seeds = [
stake_pool.as_ref(),
AUTHORITY_WITHDRAW,
&[stake_withdraw_bump_seed],
];
let signers = &[&authority_signature_seeds[..]];

invoke_signed(&stake_instruction, &account_info, signers)?;
```

By using these seeds, the program can maintain control over all associated accounts without needing to store explicit keypairs, enhancing security and enabling atomic operations across multiple accounts.

Certainly. Here's a more concise explanation suitable for developer documentation:
The Stake Pool always ensures that any stake account it creates or manages has sufficient lamports to remain rent-exempt.

## Validator Removal Process in Stake Pool

Expand Down Expand Up @@ -211,3 +173,71 @@ Active -> DeactivatingAll -> DeactivatingTransient -> ReadyForRemoval
- Use of `PodStakeStatus` for efficient status management

Note that this process may span multiple epochs, ensuring all stake is properly deactivated and reclaimed before final removal.

---

## Validator Removal Process

When a validator is removed from the stake pool, the process involves several steps and may take multiple epochs to complete. The exact path depends on the current state of the validator's stake accounts and when the `UpdateValidatorListBalance` instruction is called.

### Initial Removal

1. The `RemoveValidatorFromPool` instruction is called by the stake pool's staker.
2. The validator's `StakeStatus` is changed to `DeactivatingValidator`.
3. The validator's active stake account is deactivated.

### Subsequent Updates

The completion of the removal process depends on when `UpdateValidatorListBalance` is called and the state of the validator's stake accounts. This instruction can be called in the same epoch as the removal or in later epochs.

#### Scenario 1: No Transient Stake

If the validator has no transient stake when removed:

1. First `UpdateValidatorListBalance` call:

- If the active stake is fully deactivated (cooldown complete):
- The stake is merged into the reserve.
- The `StakeStatus` changes to `ReadyForRemoval`.
- If the active stake is still cooling down:
- No change occurs.
- The `StakeStatus` remains `DeactivatingValidator`.

2. Subsequent `UpdateValidatorListBalance` calls:

- If the status is still `DeactivatingValidator`:
- Check if cooldown is complete and merge into reserve if so.
- If the status is `ReadyForRemoval`:
- The validator entry is removed from the list.

#### Scenario 2: With Transient Stake

If the validator has transient stake when removed:

1. First `UpdateValidatorListBalance` call:

- The `StakeStatus` changes to `DeactivatingAll`.
- Both active and transient stakes begin deactivation (if not already deactivating).

2. Subsequent `UpdateValidatorListBalance` calls:

- If transient stake cooldown completes first:
- Transient stake is merged into reserve.
- `StakeStatus` changes to `DeactivatingValidator`.
- If active stake cooldown completes first:
- Active stake is merged into reserve.
- `StakeStatus` changes to `DeactivatingTransient`.

3. Final `UpdateValidatorListBalance` call:
- When both active and transient stakes are fully deactivated and merged:
- `StakeStatus` changes to `ReadyForRemoval`.
- On the next call after `ReadyForRemoval`:
- The validator entry is removed from the list.

### Important Notes

- The entire process can take multiple epochs due to Solana's stake cooldown period.
- `UpdateValidatorListBalance` must be called regularly to progress the removal process.
- Validators in any deactivating state (`DeactivatingValidator`, `DeactivatingAll`, `DeactivatingTransient`) cannot receive new stake.
- Withdrawals can still occur from deactivating validator stake accounts, potentially accelerating the removal process.
- If a validator is removed and re-added before the removal process completes, the process resets and the validator becomes active again.

0 comments on commit ca5ec2d

Please sign in to comment.