use crate::rand::StakeWeightedIndex;
use crate::shard_layout::ShardLayout;
use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter};
use crate::types::{AccountId, ValidatorKickoutReason, ValidatorStakeV1};
use crate::validator_mandates::ValidatorMandates;
use crate::version::PROTOCOL_VERSION;
use borsh::{BorshDeserialize, BorshSerialize};
use near_primitives_core::types::{Balance, EpochHeight, ProtocolVersion, ValidatorId};
use near_primitives_core::version::ProtocolFeature;
use near_primitives_core::{
hash::hash,
types::{BlockHeight, ShardId},
};
use near_schema_checker_lib::ProtocolSchema;
use smart_default::SmartDefault;
use std::collections::{BTreeMap, HashMap};
#[derive(
BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, serde::Serialize, ProtocolSchema,
)]
pub enum EpochInfo {
V1(EpochInfoV1),
V2(EpochInfoV2),
V3(EpochInfoV3),
V4(EpochInfoV4),
V5(EpochInfoV5),
}
pub type RngSeed = [u8; 32];
#[derive(
Default,
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
ProtocolSchema,
)]
pub struct ValidatorWeight(ValidatorId, u64);
#[derive(
BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, serde::Serialize, ProtocolSchema,
)]
pub struct EpochInfoV5 {
pub epoch_height: EpochHeight,
pub validators: Vec<ValidatorStake>,
pub validator_to_index: HashMap<AccountId, ValidatorId>,
pub block_producers_settlement: Vec<ValidatorId>,
pub chunk_producers_settlement: Vec<Vec<ValidatorId>>,
pub stake_change: BTreeMap<AccountId, Balance>,
pub validator_reward: HashMap<AccountId, Balance>,
pub validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
pub minted_amount: Balance,
pub seat_price: Balance,
pub protocol_version: ProtocolVersion,
pub shard_layout: ShardLayout,
pub last_resharding: Option<EpochHeight>,
rng_seed: RngSeed,
block_producers_sampler: StakeWeightedIndex,
chunk_producers_sampler: Vec<StakeWeightedIndex>,
validator_mandates: ValidatorMandates,
}
#[derive(
SmartDefault,
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
ProtocolSchema,
)]
pub struct EpochInfoV4 {
pub epoch_height: EpochHeight,
pub validators: Vec<ValidatorStake>,
pub validator_to_index: HashMap<AccountId, ValidatorId>,
pub block_producers_settlement: Vec<ValidatorId>,
pub chunk_producers_settlement: Vec<Vec<ValidatorId>>,
pub _hidden_validators_settlement: Vec<ValidatorWeight>,
pub _fishermen: Vec<crate::types::validator_stake::ValidatorStake>,
pub _fishermen_to_index: HashMap<AccountId, ValidatorId>,
pub stake_change: BTreeMap<AccountId, Balance>,
pub validator_reward: HashMap<AccountId, Balance>,
pub validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
pub minted_amount: Balance,
pub seat_price: Balance,
#[default(PROTOCOL_VERSION)]
pub protocol_version: ProtocolVersion,
rng_seed: RngSeed,
block_producers_sampler: crate::rand::StakeWeightedIndex,
chunk_producers_sampler: Vec<crate::rand::StakeWeightedIndex>,
validator_mandates: crate::validator_mandates::ValidatorMandates,
}
impl Default for EpochInfo {
fn default() -> Self {
Self::V2(EpochInfoV2::default())
}
}
#[derive(
SmartDefault,
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
ProtocolSchema,
)]
pub struct EpochInfoV2 {
pub epoch_height: EpochHeight,
pub validators: Vec<ValidatorStake>,
pub validator_to_index: HashMap<AccountId, ValidatorId>,
pub block_producers_settlement: Vec<ValidatorId>,
pub chunk_producers_settlement: Vec<Vec<ValidatorId>>,
pub hidden_validators_settlement: Vec<ValidatorWeight>,
pub fishermen: Vec<ValidatorStake>,
pub fishermen_to_index: HashMap<AccountId, ValidatorId>,
pub stake_change: BTreeMap<AccountId, Balance>,
pub validator_reward: HashMap<AccountId, Balance>,
pub validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
pub minted_amount: Balance,
pub seat_price: Balance,
#[default(PROTOCOL_VERSION)]
pub protocol_version: ProtocolVersion,
}
#[derive(
SmartDefault,
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
ProtocolSchema,
)]
pub struct EpochInfoV3 {
pub epoch_height: EpochHeight,
pub validators: Vec<ValidatorStake>,
pub validator_to_index: HashMap<AccountId, ValidatorId>,
pub block_producers_settlement: Vec<ValidatorId>,
pub chunk_producers_settlement: Vec<Vec<ValidatorId>>,
pub hidden_validators_settlement: Vec<ValidatorWeight>,
pub fishermen: Vec<ValidatorStake>,
pub fishermen_to_index: HashMap<AccountId, ValidatorId>,
pub stake_change: BTreeMap<AccountId, Balance>,
pub validator_reward: HashMap<AccountId, Balance>,
pub validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
pub minted_amount: Balance,
pub seat_price: Balance,
#[default(PROTOCOL_VERSION)]
pub protocol_version: ProtocolVersion,
rng_seed: RngSeed,
block_producers_sampler: StakeWeightedIndex,
chunk_producers_sampler: Vec<StakeWeightedIndex>,
}
impl EpochInfo {
pub fn new(
epoch_height: EpochHeight,
validators: Vec<ValidatorStake>,
validator_to_index: HashMap<AccountId, ValidatorId>,
block_producers_settlement: Vec<ValidatorId>,
chunk_producers_settlement: Vec<Vec<ValidatorId>>,
stake_change: BTreeMap<AccountId, Balance>,
validator_reward: HashMap<AccountId, Balance>,
validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
minted_amount: Balance,
seat_price: Balance,
protocol_version: ProtocolVersion,
rng_seed: RngSeed,
validator_mandates: ValidatorMandates,
shard_layout: ShardLayout,
last_resharding: Option<EpochHeight>,
) -> Self {
let stake_weights = |ids: &[ValidatorId]| -> StakeWeightedIndex {
StakeWeightedIndex::new(
ids.iter()
.copied()
.map(|validator_id| validators[validator_id as usize].stake())
.collect(),
)
};
let block_producers_sampler = stake_weights(&block_producers_settlement);
let chunk_producers_sampler =
chunk_producers_settlement.iter().map(|vs| stake_weights(vs)).collect();
if ProtocolFeature::DynamicResharding.enabled(protocol_version) {
return Self::V5(EpochInfoV5 {
epoch_height,
validators,
validator_to_index,
block_producers_settlement,
chunk_producers_settlement,
stake_change,
validator_reward,
validator_kickout,
minted_amount,
seat_price,
protocol_version,
shard_layout,
last_resharding,
rng_seed,
block_producers_sampler,
chunk_producers_sampler,
validator_mandates,
});
}
Self::V4(EpochInfoV4 {
epoch_height,
validators,
_fishermen: Default::default(),
validator_to_index,
block_producers_settlement,
chunk_producers_settlement,
_hidden_validators_settlement: Default::default(),
stake_change,
validator_reward,
validator_kickout,
_fishermen_to_index: Default::default(),
minted_amount,
seat_price,
protocol_version,
rng_seed,
block_producers_sampler,
chunk_producers_sampler,
validator_mandates,
})
}
#[inline]
pub fn epoch_height_mut(&mut self) -> &mut EpochHeight {
match self {
Self::V1(v1) => &mut v1.epoch_height,
Self::V2(v2) => &mut v2.epoch_height,
Self::V3(v3) => &mut v3.epoch_height,
Self::V4(v4) => &mut v4.epoch_height,
Self::V5(v5) => &mut v5.epoch_height,
}
}
#[inline]
pub fn epoch_height(&self) -> EpochHeight {
match self {
Self::V1(v1) => v1.epoch_height,
Self::V2(v2) => v2.epoch_height,
Self::V3(v3) => v3.epoch_height,
Self::V4(v4) => v4.epoch_height,
Self::V5(v5) => v5.epoch_height,
}
}
#[inline]
pub fn seat_price(&self) -> Balance {
match self {
Self::V1(v1) => v1.seat_price,
Self::V2(v2) => v2.seat_price,
Self::V3(v3) => v3.seat_price,
Self::V4(v4) => v4.seat_price,
Self::V5(v5) => v5.seat_price,
}
}
#[inline]
pub fn minted_amount(&self) -> Balance {
match self {
Self::V1(v1) => v1.minted_amount,
Self::V2(v2) => v2.minted_amount,
Self::V3(v3) => v3.minted_amount,
Self::V4(v4) => v4.minted_amount,
Self::V5(v5) => v5.minted_amount,
}
}
#[inline]
pub fn block_producers_settlement(&self) -> &[ValidatorId] {
match self {
Self::V1(v1) => &v1.block_producers_settlement,
Self::V2(v2) => &v2.block_producers_settlement,
Self::V3(v3) => &v3.block_producers_settlement,
Self::V4(v4) => &v4.block_producers_settlement,
Self::V5(v5) => &v5.block_producers_settlement,
}
}
#[inline]
pub fn chunk_producers_settlement(&self) -> &[Vec<ValidatorId>] {
match self {
Self::V1(v1) => &v1.chunk_producers_settlement,
Self::V2(v2) => &v2.chunk_producers_settlement,
Self::V3(v3) => &v3.chunk_producers_settlement,
Self::V4(v4) => &v4.chunk_producers_settlement,
Self::V5(v5) => &v5.chunk_producers_settlement,
}
}
#[inline]
pub fn chunk_producers_settlement_mut(&mut self) -> &mut Vec<Vec<ValidatorId>> {
match self {
Self::V1(v1) => &mut v1.chunk_producers_settlement,
Self::V2(v2) => &mut v2.chunk_producers_settlement,
Self::V3(v3) => &mut v3.chunk_producers_settlement,
Self::V4(v4) => &mut v4.chunk_producers_settlement,
Self::V5(v5) => &mut v5.chunk_producers_settlement,
}
}
#[inline]
pub fn validator_kickout(&self) -> &HashMap<AccountId, ValidatorKickoutReason> {
match self {
Self::V1(v1) => &v1.validator_kickout,
Self::V2(v2) => &v2.validator_kickout,
Self::V3(v3) => &v3.validator_kickout,
Self::V4(v4) => &v4.validator_kickout,
Self::V5(v5) => &v5.validator_kickout,
}
}
#[inline]
pub fn protocol_version(&self) -> ProtocolVersion {
match self {
Self::V1(v1) => v1.protocol_version,
Self::V2(v2) => v2.protocol_version,
Self::V3(v3) => v3.protocol_version,
Self::V4(v4) => v4.protocol_version,
Self::V5(v5) => v5.protocol_version,
}
}
#[inline]
pub fn stake_change(&self) -> &BTreeMap<AccountId, Balance> {
match self {
Self::V1(v1) => &v1.stake_change,
Self::V2(v2) => &v2.stake_change,
Self::V3(v3) => &v3.stake_change,
Self::V4(v4) => &v4.stake_change,
Self::V5(v5) => &v5.stake_change,
}
}
#[inline]
pub fn validator_reward(&self) -> &HashMap<AccountId, Balance> {
match self {
Self::V1(v1) => &v1.validator_reward,
Self::V2(v2) => &v2.validator_reward,
Self::V3(v3) => &v3.validator_reward,
Self::V4(v4) => &v4.validator_reward,
Self::V5(v5) => &v5.validator_reward,
}
}
#[inline]
pub fn validators_iter(&self) -> ValidatorStakeIter<'_> {
match self {
Self::V1(v1) => ValidatorStakeIter::v1(&v1.validators),
Self::V2(v2) => ValidatorStakeIter::new(&v2.validators),
Self::V3(v3) => ValidatorStakeIter::new(&v3.validators),
Self::V4(v4) => ValidatorStakeIter::new(&v4.validators),
Self::V5(v5) => ValidatorStakeIter::new(&v5.validators),
}
}
#[inline]
pub fn validator_stake(&self, validator_id: u64) -> Balance {
match self {
Self::V1(v1) => v1.validators[validator_id as usize].stake,
Self::V2(v2) => v2.validators[validator_id as usize].stake(),
Self::V3(v3) => v3.validators[validator_id as usize].stake(),
Self::V4(v4) => v4.validators[validator_id as usize].stake(),
Self::V5(v5) => v5.validators[validator_id as usize].stake(),
}
}
#[inline]
pub fn validator_account_id(&self, validator_id: u64) -> &AccountId {
match self {
Self::V1(v1) => &v1.validators[validator_id as usize].account_id,
Self::V2(v2) => v2.validators[validator_id as usize].account_id(),
Self::V3(v3) => v3.validators[validator_id as usize].account_id(),
Self::V4(v4) => v4.validators[validator_id as usize].account_id(),
Self::V5(v5) => v5.validators[validator_id as usize].account_id(),
}
}
#[inline]
pub fn account_is_validator(&self, account_id: &AccountId) -> bool {
match self {
Self::V1(v1) => v1.validator_to_index.contains_key(account_id),
Self::V2(v2) => v2.validator_to_index.contains_key(account_id),
Self::V3(v3) => v3.validator_to_index.contains_key(account_id),
Self::V4(v4) => v4.validator_to_index.contains_key(account_id),
Self::V5(v5) => v5.validator_to_index.contains_key(account_id),
}
}
pub fn get_validator_id(&self, account_id: &AccountId) -> Option<&ValidatorId> {
match self {
Self::V1(v1) => v1.validator_to_index.get(account_id),
Self::V2(v2) => v2.validator_to_index.get(account_id),
Self::V3(v3) => v3.validator_to_index.get(account_id),
Self::V4(v4) => v4.validator_to_index.get(account_id),
Self::V5(v5) => v5.validator_to_index.get(account_id),
}
}
pub fn get_validator_by_account(&self, account_id: &AccountId) -> Option<ValidatorStake> {
match self {
Self::V1(v1) => v1.validator_to_index.get(account_id).map(|validator_id| {
ValidatorStake::V1(v1.validators[*validator_id as usize].clone())
}),
Self::V2(v2) => v2
.validator_to_index
.get(account_id)
.map(|validator_id| v2.validators[*validator_id as usize].clone()),
Self::V3(v3) => v3
.validator_to_index
.get(account_id)
.map(|validator_id| v3.validators[*validator_id as usize].clone()),
Self::V4(v4) => v4
.validator_to_index
.get(account_id)
.map(|validator_id| v4.validators[*validator_id as usize].clone()),
Self::V5(v5) => v5
.validator_to_index
.get(account_id)
.map(|validator_id| v5.validators[*validator_id as usize].clone()),
}
}
pub fn get_validator_stake(&self, account_id: &AccountId) -> Option<Balance> {
match self {
Self::V1(v1) => v1
.validator_to_index
.get(account_id)
.map(|validator_id| v1.validators[*validator_id as usize].stake),
Self::V2(v2) => v2
.validator_to_index
.get(account_id)
.map(|validator_id| v2.validators[*validator_id as usize].stake()),
Self::V3(v3) => v3
.validator_to_index
.get(account_id)
.map(|validator_id| v3.validators[*validator_id as usize].stake()),
Self::V4(v4) => v4
.validator_to_index
.get(account_id)
.map(|validator_id| v4.validators[*validator_id as usize].stake()),
Self::V5(v5) => v5
.validator_to_index
.get(account_id)
.map(|validator_id| v5.validators[*validator_id as usize].stake()),
}
}
#[inline]
pub fn get_validator(&self, validator_id: u64) -> ValidatorStake {
match self {
Self::V1(v1) => ValidatorStake::V1(v1.validators[validator_id as usize].clone()),
Self::V2(v2) => v2.validators[validator_id as usize].clone(),
Self::V3(v3) => v3.validators[validator_id as usize].clone(),
Self::V4(v4) => v4.validators[validator_id as usize].clone(),
Self::V5(v5) => v5.validators[validator_id as usize].clone(),
}
}
#[inline]
pub fn validators_len(&self) -> usize {
match self {
Self::V1(v1) => v1.validators.len(),
Self::V2(v2) => v2.validators.len(),
Self::V3(v3) => v3.validators.len(),
Self::V4(v4) => v4.validators.len(),
Self::V5(v5) => v5.validators.len(),
}
}
#[inline]
pub fn rng_seed(&self) -> RngSeed {
match self {
Self::V1(_) | Self::V2(_) => Default::default(),
Self::V3(v3) => v3.rng_seed,
Self::V4(v4) => v4.rng_seed,
Self::V5(v5) => v5.rng_seed,
}
}
#[inline]
pub fn validator_mandates(&self) -> ValidatorMandates {
match self {
Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(),
Self::V4(v4) => v4.validator_mandates.clone(),
Self::V5(v5) => v5.validator_mandates.clone(),
}
}
pub fn sample_block_producer(&self, height: BlockHeight) -> ValidatorId {
match &self {
Self::V1(v1) => {
let bp_settlement = &v1.block_producers_settlement;
bp_settlement[(height % (bp_settlement.len() as u64)) as usize]
}
Self::V2(v2) => {
let bp_settlement = &v2.block_producers_settlement;
bp_settlement[(height % (bp_settlement.len() as u64)) as usize]
}
Self::V3(v3) => {
let seed = Self::block_produce_seed(height, &v3.rng_seed);
v3.block_producers_settlement[v3.block_producers_sampler.sample(seed)]
}
Self::V4(v4) => {
let seed = Self::block_produce_seed(height, &v4.rng_seed);
v4.block_producers_settlement[v4.block_producers_sampler.sample(seed)]
}
Self::V5(v5) => {
let seed = Self::block_produce_seed(height, &v5.rng_seed);
v5.block_producers_settlement[v5.block_producers_sampler.sample(seed)]
}
}
}
pub fn sample_chunk_producer(
&self,
shard_layout: &ShardLayout,
shard_id: ShardId,
height: BlockHeight,
) -> Option<ValidatorId> {
let shard_index = shard_layout.get_shard_index(shard_id).ok()?;
match &self {
Self::V1(v1) => {
let cp_settlement = &v1.chunk_producers_settlement;
let shard_cps = cp_settlement.get(shard_index)?;
shard_cps.get((height as u64 % (shard_cps.len() as u64)) as usize).copied()
}
Self::V2(v2) => {
let cp_settlement = &v2.chunk_producers_settlement;
let shard_cps = cp_settlement.get(shard_index)?;
shard_cps.get((height as u64 % (shard_cps.len() as u64)) as usize).copied()
}
Self::V3(v3) => {
let seed = Self::chunk_produce_seed(&v3.rng_seed, height, shard_id);
let sample = v3.chunk_producers_sampler.get(shard_index)?.sample(seed);
v3.chunk_producers_settlement.get(shard_index)?.get(sample).copied()
}
Self::V4(v4) => {
let seed = Self::chunk_produce_seed(&v4.rng_seed, height, shard_id);
let sample = v4.chunk_producers_sampler.get(shard_index)?.sample(seed);
v4.chunk_producers_settlement.get(shard_index)?.get(sample).copied()
}
Self::V5(v5) => {
let seed = Self::chunk_produce_seed(&v5.rng_seed, height, shard_id);
let sample = v5.chunk_producers_sampler.get(shard_index)?.sample(seed);
v5.chunk_producers_settlement.get(shard_index)?.get(sample).copied()
}
}
}
#[cfg(feature = "rand")]
pub fn sample_chunk_validators(
&self,
height: BlockHeight,
) -> crate::validator_mandates::ChunkValidatorStakeAssignment {
match &self {
Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(),
Self::V4(v4) => {
let mut rng = Self::chunk_validate_rng(&v4.rng_seed, height);
v4.validator_mandates.sample(&mut rng)
}
Self::V5(v5) => {
let mut rng = Self::chunk_validate_rng(&v5.rng_seed, height);
v5.validator_mandates.sample(&mut rng)
}
}
}
pub fn shard_layout(&self) -> Option<&ShardLayout> {
match self {
Self::V5(v5) => Some(&v5.shard_layout),
_ => None,
}
}
pub fn last_resharding(&self) -> Option<EpochHeight> {
match self {
Self::V1(_) | Self::V2(_) | Self::V3(_) | Self::V4(_) => None,
Self::V5(v5) => v5.last_resharding,
}
}
fn block_produce_seed(height: BlockHeight, seed: &RngSeed) -> [u8; 32] {
let mut buffer = [0u8; 40];
buffer[0..32].copy_from_slice(seed);
buffer[32..40].copy_from_slice(&height.to_le_bytes());
hash(&buffer).0
}
fn chunk_produce_seed(seed: &RngSeed, height: BlockHeight, shard_id: ShardId) -> [u8; 32] {
let mut buffer = [0u8; 48];
buffer[0..32].copy_from_slice(seed);
buffer[32..40].copy_from_slice(&height.to_le_bytes());
buffer[40..48].copy_from_slice(&shard_id.to_le_bytes());
hash(&buffer).0
}
}
#[cfg(feature = "rand")]
impl EpochInfo {
fn chunk_validate_rng(seed: &RngSeed, height: BlockHeight) -> rand_chacha::ChaCha20Rng {
let mut buffer = [0u8; 40];
buffer[0..32].copy_from_slice(seed);
buffer[32..40].copy_from_slice(&height.to_le_bytes());
let seed = hash(&buffer);
rand::SeedableRng::from_seed(seed.0)
}
pub fn shard_assignment_rng(seed: &RngSeed) -> rand_chacha::ChaCha20Rng {
let mut buffer = [0u8; 62];
buffer[0..32].copy_from_slice(seed);
buffer[32..62].copy_from_slice(b"shard_assignment_shuffling_rng");
let seed = hash(&buffer);
rand::SeedableRng::from_seed(seed.0)
}
}
#[derive(
SmartDefault,
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
ProtocolSchema,
)]
pub struct EpochInfoV1 {
pub epoch_height: EpochHeight,
pub validators: Vec<ValidatorStakeV1>,
pub validator_to_index: HashMap<AccountId, ValidatorId>,
pub block_producers_settlement: Vec<ValidatorId>,
pub chunk_producers_settlement: Vec<Vec<ValidatorId>>,
pub hidden_validators_settlement: Vec<ValidatorWeight>,
pub fishermen: Vec<ValidatorStakeV1>,
pub fishermen_to_index: HashMap<AccountId, ValidatorId>,
pub stake_change: BTreeMap<AccountId, Balance>,
pub validator_reward: HashMap<AccountId, Balance>,
pub validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
pub minted_amount: Balance,
pub seat_price: Balance,
#[default(PROTOCOL_VERSION)]
pub protocol_version: ProtocolVersion,
}