EpochManager
Finalizing an epoch
At the last block of epoch T, EpochManager computes EpochInfo for epoch T+2, which
is defined by EpochInfo for T+1 and the information aggregated from blocks of epoch T.
EpochInfo is all the information that EpochManager stores for the epoch, which is:
epoch_height: epoch height (T+2)validators: the list of validators selected for epochT+2validator_to_index: Mapping from account id to index invalidatorsblock_producers_settlement: defines the mapping from height to block producerchunk_producers_settlement: defines the base mapping from height and shard id to chunk producer. From protocol version 87 on, this mapping can be overridden within the epoch, see Early chunk producer reassignmenthidden_validators_settlement: TODOfishermen,fishermen_to_index: TODO. disabled on mainnet through a largefishermen_thresholdin configstake_change: TODOvalidator_reward: validator reward for epochTvalidator_kickout: see Kickout setminted_amount: minted tokens in epochTseat_price: seat price of the epochprotocol_version: TODO
Aggregating blocks of the epoch computes the following sets:
block_stats/chunk_stats: uptime statistics in the form ofproducedandexpectedblocks/chunks for each validator ofTproposals: stake proposals made in epochT. If an account made multiple proposals, the last one is used.slashes: see Slash set
Slash set
NOTE: slashing is currently disabled. The following is the current design, which can change.
Slash sets are maintained on block basis. If a validator gets slashed in epoch T, subsequent blocks of epochs T and
T+1 keep it in their slash sets. At the end of epoch T, the slashed validator is also added to kickout[T+2].
Proposals from blocks in slash sets are ignored.
It's kept in the slash set (and kickout sets) for two or three epochs depending on whether it was going to be a validator in T+1:
- Common case:
vis invalidators[T]and invalidators[T+1]- proposals from
vinT,T+1andT+2are ignored vis added tokickout[T+2],kickout[T+3]andkickout[T+4]as slashedvcan stake again starting with the first block ofT+3.
- proposals from
- If
vis invalidators[T]but not invalidators[T+1](e.g. if it unstaked inT-1)- proposals from
vinTandT+1are ignored vis added tokickout[T+2]andkickout[T+3]as slashedvcan make a proposal inT+2to become a validator inT+4
- proposals from
- If
vis invalidators[T-1]but not invalidators[T](e.g. did slashable behavior right before rotating out)- proposals from
vinTandT+1are ignored vis added tokickout[T+2]andkickout[T+3]as slashedvcan make a proposal inT+2to become a validator inT+4
- proposals from
Computing EpochInfo
Kickout set
kickout[T+2] contains validators of epoch T+1 that stop being validators in T+2, and also accounts that are not
necessarily validators of T+1, but are kept in slashing sets due to the rule described above.
kickout[T+2] is computed the following way:
Slashed: accounts in the slash set of the last block inTUnstaked: accounts that remove their stake in epochT, if their stake is non-zero for epochT+1NotEnoughBlocks/NotEnoughChunks: For each validator compute the ratio of blocks produced to expected blocks produced (same with chunks produced/expected). If the percentage is belowblock_producer_kickout_threshold(chunk_producer_kickout_threshold), the validator is kicked out.- Exception: If all validators of
Tare either inkickout[T+1]or to be kicked out, we don't kick out the validator with the maximum number of blocks produced. If there are multiple, we choose the one with lowest validator id in the epoch.
- Exception: If all validators of
NotEnoughStake: computed after validator selection. Accounts who have stake in epochT+1, but don't meet stake threshold for epochT+2.DidNotGetASeat: computed after validator selection. Accounts who have stake in epochT+1, meet stake threshold for epochT+2, but didn't get any seats.
Processing proposals
The set of proposals is processed by the validator selection algorithm, but before that, the set of proposals is adjusted the following way:
- If an account is in the slash set as of the end of
T, or gets kicked out forNotEnoughBlocks/NotEnoughChunksin epochT, its proposal is ignored. - If a validator is in
validators[T+1], and didn't make a proposal, add an implicit proposal with its stake inT+1. - If a validator is in both
validators[T]andvalidators[T+1], and made a proposal inT(including implicit), then its reward for epochTis automatically added to the proposal.
The adjusted set of proposals is used to compute the seat price, and determine validators,block_producers_settlement,
chunk_producers_settlementsets. This algorithm is described in Economics.
Validator reward
Rewards calculation is described in the Economics section.
Early chunk producer reassignment
The kickout set only takes effect at an epoch boundary, so a chunk producer that stops producing keeps its slots for the rest of the epoch. From protocol version 87 on, the protocol also reassigns chunk producer slots within an epoch.
A chunk producer is excluded on a shard when both hold for its stats in the current epoch:
- it missed at least 100 chunks on that shard, and
- its produced-to-expected ratio on that shard is below 80%.
Exclusion is per shard, and stats never cross an epoch boundary. A 1000-block grace window at the start of each epoch delays the first possible exclusion, so a producer cannot be excluded before roughly block 1000 of an epoch. A safety valve keeps the least-bad producer eligible when the rule would exclude every distinct producer on a shard, so a shard can never run out of chunk producers.
The chunk producer for the chunks anchored at a block is sampled with the excluded producers
removed, and the result is persisted in DBCol::ChunkProducers keyed by
(anchor_block_hash, shard_id). The anchor is the chunk's grandparent block, so a chunk at
height h reads the row written for the block at height h - 2. Chunk validation reads that
row verbatim rather than re-sampling, which is what makes the assignment identical on every
node: the exclusion set is derived from the anchor's last-final block, which is
header-derived and therefore the same everywhere for a canonical anchor.
Within the first two blocks of an epoch the anchor belongs to the previous epoch. The exclusion set is provably empty there, because stats do not cross the boundary, so those chunks fall back to the plain settlement sampler.