2019-03-11 16:35:42 -07:00
|
|
|
# Stake Delegation and Reward
|
|
|
|
|
|
|
|
This design proposal focuses on the software architecture for the on-chain
|
|
|
|
voting and staking programs. Incentives for staking is covered in [staking
|
|
|
|
rewards](staking-rewards.md).
|
|
|
|
|
|
|
|
The current architecture requires a vote for each delegated stake from the
|
2019-03-18 18:25:29 -07:00
|
|
|
validator, and therefore does not scale to allow replicator clients to
|
2019-03-11 16:35:42 -07:00
|
|
|
automatically delegate their rewards.
|
|
|
|
|
|
|
|
The design proposes a new set of programs for voting and stake delegation, The
|
|
|
|
proposed programs allow many stake accounts to passively earn rewards with a
|
|
|
|
single validator vote without permission or active involvement from the
|
|
|
|
validator.
|
|
|
|
|
|
|
|
## Current Design Problems
|
|
|
|
|
|
|
|
In the current design each staker creates their own VoteState, and assigns a
|
|
|
|
**delegate** in the VoteState that can submit votes. Since the validator has to
|
2019-03-18 18:25:29 -07:00
|
|
|
actively vote for each stake delegated to it, validators can censor stakes by
|
|
|
|
not voting for them.
|
2019-03-11 16:35:42 -07:00
|
|
|
|
|
|
|
The number of votes is equal to the number of stakers, and not the number of
|
|
|
|
validators. Replicator clients are expected to delegate their replication
|
|
|
|
rewards as they are earned, and therefore the number of stakes is expected to be
|
|
|
|
large compared to the number of validators in a long running cluster.
|
|
|
|
|
|
|
|
## Proposed changes to the current design.
|
|
|
|
|
|
|
|
The general idea is that instead of the staker, the validator will own the
|
|
|
|
VoteState program. In this proposal the VoteState program is there to track
|
|
|
|
validator votes, count validator generated credits and to provide any
|
|
|
|
additional validator specific state. The VoteState program is not aware of any
|
|
|
|
stakes delegated to it, and has no staking weight.
|
|
|
|
|
|
|
|
The rewards generated are proportional to the amount of lamports staked. In
|
|
|
|
this proposal stake state is stored as part of the StakeState program. This
|
|
|
|
program is owned by the staker only. Lamports stored in this program are the
|
|
|
|
stake. Unlike the current design, this program contains a new field to indicate
|
|
|
|
which VoteState program the stake is delegated to.
|
|
|
|
|
2019-05-28 16:01:27 -07:00
|
|
|
### VoteState
|
|
|
|
|
|
|
|
VoteState is the current state of all the votes the **delegate** has submitted
|
|
|
|
to the bank. VoteState contains the following state information:
|
|
|
|
|
|
|
|
* votes - The submitted votes data structure.
|
|
|
|
|
|
|
|
* credits - The total number of rewards this vote program has generated over its
|
|
|
|
lifetime.
|
|
|
|
|
|
|
|
* root\_slot - The last slot to reach the full lockout commitment necessary for
|
|
|
|
rewards.
|
|
|
|
|
|
|
|
* commission - The commission taken by this VoteState for any rewards claimed by
|
|
|
|
staker's StakeState accounts. This is the percentage ceiling of the reward.
|
|
|
|
|
|
|
|
* Account::lamports - The accumulated lamports from the commission. These do not
|
|
|
|
count as stakes.
|
|
|
|
|
|
|
|
* `authorized_vote_signer` - Only this identity is authorized to submit votes, and
|
|
|
|
this field can only modified by this entity
|
|
|
|
|
|
|
|
### VoteInstruction::Initialize
|
|
|
|
|
|
|
|
* `account[0]` - RW - The VoteState
|
|
|
|
`VoteState::authorized_vote_signer` is initialized to `account[0]`
|
|
|
|
other VoteState members defaulted
|
|
|
|
|
|
|
|
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
|
|
|
|
|
|
|
|
* `account[0]` - RW - The VoteState
|
|
|
|
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
|
|
|
|
signed by Pubkey
|
|
|
|
|
|
|
|
|
|
|
|
### StakeState
|
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
A StakeState takes one of two forms, StakeState::Stake and StakeState::MiningPool.
|
2019-05-28 16:01:27 -07:00
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
### StakeState::Stake
|
2019-05-28 16:01:27 -07:00
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
Stake is the current delegation preference of the **staker**. Stake
|
2019-05-28 16:01:27 -07:00
|
|
|
contains the following state information:
|
|
|
|
|
|
|
|
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
|
|
|
delegated to.
|
|
|
|
|
|
|
|
* `credits_observed` - The total credits claimed over the lifetime of the
|
|
|
|
program.
|
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
* `stake` - The actual activated stake.
|
|
|
|
|
|
|
|
* Account::lamports - Lamports available for staking, including any earned as rewards.
|
|
|
|
|
|
|
|
|
2019-05-28 16:01:27 -07:00
|
|
|
### StakeState::MiningPool
|
|
|
|
|
|
|
|
There are two approaches to the mining pool. The bank could allow the
|
|
|
|
StakeState program to bypass the token balance check, or a program representing
|
|
|
|
the mining pool could run on the network. To avoid a single network wide lock,
|
|
|
|
the pool can be split into several mining pools. This design focuses on using a
|
|
|
|
StakeState::MiningPool as the cluster wide mining pools.
|
|
|
|
|
|
|
|
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
|
|
|
|
tokens stored as `Account::lamports`.
|
|
|
|
|
|
|
|
The stakes and the MiningPool are accounts that are owned by the same `Stake`
|
|
|
|
program.
|
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
### StakeInstruction::DelegateStake(stake)
|
2019-05-28 16:01:27 -07:00
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
* `account[0]` - RW - The StakeState::Stake instance.
|
|
|
|
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`.
|
|
|
|
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`
|
|
|
|
`StakeState::Stake::stake` is initialized to `stake`, as long as it's less than account[0].lamports
|
2019-05-28 16:01:27 -07:00
|
|
|
|
|
|
|
* `account[1]` - R - The VoteState instance.
|
|
|
|
|
|
|
|
### StakeInstruction::RedeemVoteCredits
|
|
|
|
|
|
|
|
The VoteState program and the StakeState programs maintain a lifetime counter
|
|
|
|
of total rewards generated and claimed. Therefore an explicit `Clear`
|
|
|
|
instruction is not necessary. When claiming rewards, the total lamports
|
|
|
|
deposited into the StakeState and as validator commission is proportional to
|
|
|
|
`VoteState::credits - StakeState::credits_observed`.
|
|
|
|
|
|
|
|
|
|
|
|
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
|
|
|
|
reward.
|
2019-06-13 16:24:03 -07:00
|
|
|
* `account[1]` - RW - The StakeState::Stake instance that is redeeming votes
|
2019-05-28 16:01:27 -07:00
|
|
|
credits.
|
|
|
|
* `account[2]` - R - The VoteState instance, must be the same as
|
|
|
|
`StakeState::voter_pubkey`
|
|
|
|
|
|
|
|
Reward is payed out for the difference between `VoteState::credits` to
|
|
|
|
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
|
|
|
|
`VoteState::credits`. The commission is deposited into the `VoteState` token
|
2019-06-13 16:24:03 -07:00
|
|
|
balance, and the reward is deposited to the `StakeState::Stake` token balance. The
|
2019-05-28 16:01:27 -07:00
|
|
|
reward and the commission is weighted by the `StakeState::lamports` divided by total lamports staked.
|
|
|
|
|
|
|
|
The Staker or the owner of the Stake program sends a transaction with this
|
|
|
|
instruction to claim the reward.
|
|
|
|
|
|
|
|
Any random MiningPool can be used to redeem the credits.
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
|
|
|
|
stake_state.credits_observed = vote_state.credits;
|
|
|
|
```
|
|
|
|
|
|
|
|
`credits_to_claim` is used to compute the reward and commission, and
|
2019-06-13 16:24:03 -07:00
|
|
|
`StakeState::Stake::credits_observed` is updated to the latest
|
2019-05-28 16:01:27 -07:00
|
|
|
`VoteState::credits` value.
|
|
|
|
|
|
|
|
### Collecting network fees into the MiningPool
|
|
|
|
|
|
|
|
At the end of the block, before the bank is frozen, but after it processed all
|
|
|
|
the transactions for the block, a virtual instruction is executed to collect
|
|
|
|
the transaction fees.
|
|
|
|
|
|
|
|
* A portion of the fees are deposited into the leader's account.
|
|
|
|
* A portion of the fees are deposited into the smallest StakeState::MiningPool
|
|
|
|
account.
|
2019-03-11 16:35:42 -07:00
|
|
|
|
|
|
|
### Benefits
|
|
|
|
|
|
|
|
* Single vote for all the stakers.
|
|
|
|
|
|
|
|
* Clearing of the credit variable is not necessary for claiming rewards.
|
|
|
|
|
|
|
|
* Each delegated stake can claim its rewards independently.
|
|
|
|
|
|
|
|
* Commission for the work is deposited when a reward is claimed by the delegated
|
|
|
|
stake.
|
|
|
|
|
|
|
|
This proposal would benefit from the `read-only` accounts proposal to allow for
|
|
|
|
many rewards to be claimed concurrently.
|
|
|
|
|
|
|
|
## Passive Delegation
|
|
|
|
|
2019-06-13 16:24:03 -07:00
|
|
|
Any number of instances of StakeState::Stake programs can delegate to a single
|
2019-03-11 16:35:42 -07:00
|
|
|
VoteState program without an interactive action from the identity controlling
|
|
|
|
the VoteState program or submitting votes to the program.
|
|
|
|
|
|
|
|
The total stake allocated to a VoteState program can be calculated by the sum of
|
|
|
|
all the StakeState programs that have the VoteState pubkey as the
|
2019-06-13 16:24:03 -07:00
|
|
|
`StakeState::Stake::voter_pubkey`.
|
2019-03-18 18:25:29 -07:00
|
|
|
|
|
|
|
## Example Callflow
|
|
|
|
|
|
|
|
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
|
|
|
|
|
2019-03-11 16:35:42 -07:00
|
|
|
## Future work
|
|
|
|
|
|
|
|
Validators may want to split the stake delegated to them amongst many validator
|
|
|
|
nodes since stake is used as weight in the network control and data planes. One
|
|
|
|
way to implement this would be for the StakeState to delegate to a pool of
|
|
|
|
validators instead of a single one.
|
|
|
|
|
2019-05-23 23:20:04 -07:00
|
|
|
Instead of a single `vote_pubkey` and `credits_observed` entry in the StakeState
|
2019-03-11 16:35:42 -07:00
|
|
|
program, the program can be initialized with a vector of tuples.
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
Voter {
|
2019-05-23 23:20:04 -07:00
|
|
|
voter_pubkey: Pubkey,
|
2019-03-11 16:35:42 -07:00
|
|
|
credits_observed: u64,
|
|
|
|
weight: u8,
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
* voters: Vec<Voter> - Array of VoteState accounts that are voting rewards with
|
|
|
|
this stake.
|
|
|
|
|
|
|
|
A StakeState program would claim a fraction of the reward from each voter in
|
|
|
|
the `voters` array, and each voter would be delegated a fraction of the stake.
|