From fcba34461973999130db77593a7c27d022b1fc20 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Tue, 7 May 2024 23:30:28 +0400 Subject: [PATCH 01/20] debug --- chain/epoch-manager/src/shard_assignment.rs | 27 +++---- .../epoch-manager/src/validator_selection.rs | 74 ++++++++++++------- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/chain/epoch-manager/src/shard_assignment.rs b/chain/epoch-manager/src/shard_assignment.rs index f280ea4d74d..f47bd9f9f69 100644 --- a/chain/epoch-manager/src/shard_assignment.rs +++ b/chain/epoch-manager/src/shard_assignment.rs @@ -14,13 +14,20 @@ use near_primitives::utils::min_heap::{MinHeap, PeekMut}; /// producer will be assigned to a single shard. If there are fewer producers, /// some of them will be assigned to multiple shards. /// -/// Panics if chunk_producers vector is not sorted in descending order by -/// producer’s stake. +/// Panics if `chunk_producers.len() < min_validators_per_shard` or chunk_producers +/// vector is not sorted in descending order by producer’s stake. pub fn assign_shards( chunk_producers: Vec, num_shards: NumShards, min_validators_per_shard: usize, -) -> Result>, NotEnoughValidators> { +) -> Result>, ()> { + // If there's not enough chunk producers to fill up a single shard there’s + // nothing we can do. Return with an error. + let num_chunk_producers = chunk_producers.len(); + if num_chunk_producers < min_validators_per_shard { + return Err(()); + } + for (idx, pair) in chunk_producers.windows(2).enumerate() { assert!( pair[0].get_stake() >= pair[1].get_stake(), @@ -29,13 +36,6 @@ pub fn assign_shards( ); } - // If there’s not enough chunk producers to fill up a single shard there’s - // nothing we can do. Return with an error. - let num_chunk_producers = chunk_producers.len(); - if num_chunk_producers < min_validators_per_shard { - return Err(NotEnoughValidators); - } - let mut result: Vec> = (0..num_shards).map(|_| Vec::new()).collect(); // Initially, sort by number of validators first so we fill shards up. @@ -135,11 +135,6 @@ fn assign_with_possible_repeats } } -/// Marker struct to communicate the error where you try to assign validators to shards -/// and there are not enough to even meet the minimum per shard. -#[derive(Debug)] -pub struct NotEnoughValidators; - pub trait HasStake { fn get_stake(&self) -> Balance; } @@ -226,7 +221,7 @@ mod tests { stakes: &[Balance], num_shards: NumShards, min_validators_per_shard: usize, - ) -> Result, super::NotEnoughValidators> { + ) -> Result, ()> { let chunk_producers = stakes.iter().copied().enumerate().collect(); let assignments = super::assign_shards(chunk_producers, num_shards, min_validators_per_shard)?; diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index cdc7095fad1..3f511edae86 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -39,22 +39,16 @@ pub fn proposals_to_epoch_info( }; let max_bp_selected = epoch_config.num_block_producer_seats as usize; let mut stake_change = BTreeMap::new(); - let proposals = proposals_with_rollover( + let proposals = apply_epoch_update_to_proposals( proposals, prev_epoch_info, &validator_reward, &validator_kickout, &mut stake_change, ); - let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let (block_producers, bp_stake_threshold) = select_block_producers( - &mut block_producer_proposals, - max_bp_selected, - min_stake_ratio, - current_version, - ); - let (chunk_producer_proposals, chunk_producers, cp_stake_threshold) = - if checked_feature!("stable", ChunkOnlyProducers, next_version) { + + let (remaining_proposals, block_producers, chunk_producers, threshold) = + if checked_feature!("stable", StatelessValidationV0, next_version) { let mut chunk_producer_proposals = order_proposals(proposals.into_values()); let max_cp_selected = max_bp_selected + (epoch_config.validator_selection_config.num_chunk_only_producer_seats as usize); @@ -65,17 +59,44 @@ pub fn proposals_to_epoch_info( shard_ids.len() as NumShards, current_version, ); - (chunk_producer_proposals, chunk_producers, cp_stake_threshold) + + (Default::default(), Default::default(), Default::default(), Balance::default()) } else { - (block_producer_proposals, block_producers.clone(), bp_stake_threshold) - }; + let mut block_producer_proposals = order_proposals(proposals.values().cloned()); + let (block_producers, bp_stake_threshold) = select_block_producers( + &mut block_producer_proposals, + max_bp_selected, + min_stake_ratio, + current_version, + ); + let (chunk_producer_proposals, chunk_producers, cp_stake_threshold) = + if checked_feature!("stable", ChunkOnlyProducers, next_version) { + let mut chunk_producer_proposals = order_proposals(proposals.into_values()); + let max_cp_selected = max_bp_selected + + (epoch_config.validator_selection_config.num_chunk_only_producer_seats + as usize); + let (chunk_producers, cp_stake_threshold) = select_chunk_producers( + &mut chunk_producer_proposals, + max_cp_selected, + min_stake_ratio, + shard_ids.len() as NumShards, + current_version, + ); + (chunk_producer_proposals, chunk_producers, cp_stake_threshold) + } else { + (block_producer_proposals, block_producers.clone(), bp_stake_threshold) + }; - // since block producer proposals could become chunk producers, their actual stake threshold - // is the smaller of the two thresholds - let threshold = cmp::min(bp_stake_threshold, cp_stake_threshold); + // since block producer proposals could become chunk producers, their actual stake threshold + // is the smaller of the two thresholds + let threshold = cmp::min(bp_stake_threshold, cp_stake_threshold); + + // (remaining proposals, BP, CP, threshold) + (chunk_producer_proposals, block_producers, chunk_producers, threshold) + }; - // process remaining chunk_producer_proposals that were not selected for either role - for OrderedValidatorStake(p) in chunk_producer_proposals { + // process remaining proposals that were not selected for either role + for OrderedValidatorStake(p) in remaining_proposals { let stake = p.stake(); let account_id = p.account_id(); *stake_change.get_mut(account_id).unwrap() = 0; @@ -102,12 +123,14 @@ pub fn proposals_to_epoch_info( let chunk_producers_settlement = if checked_feature!("stable", ChunkOnlyProducers, next_version) { + // assign_chunk_producers_to_shards(epoch_config, chunk_producers, &prev_epoch_info)? let minimum_validators_per_shard = epoch_config.validator_selection_config.minimum_validators_per_shard as usize; let shard_assignment = assign_shards( chunk_producers, shard_ids.len() as NumShards, minimum_validators_per_shard, + // prev_epoch_info.chunk_producers_settlement(), ) .map_err(|_| EpochError::NotEnoughValidators { num_validators: num_chunk_producers as u64, @@ -190,18 +213,17 @@ pub fn proposals_to_epoch_info( )) } -/// Generates proposals based on new proposals, last epoch validators/fishermen and validator -/// kickouts -/// For each account that was validator or fisherman in last epoch or made stake action last epoch -/// we apply the following in the order of priority -/// 1. If account is in validator_kickout it cannot be validator or fisherman for the next epoch, -/// we will not include it in proposals or fishermen +/// Generates proposals based on proposals generated throughout last epoch, +/// last epoch validators and validator kickouts. +/// For each account that was validator in last epoch or made stake action last epoch +/// we apply the following in the order of priority: +/// 1. If account is in validator_kickout it cannot be validator for the next epoch, +/// we will not include it in proposals /// 2. If account made staking action last epoch, it will be included in proposals with stake /// adjusted by rewards from last epoch, if any /// 3. If account was validator last epoch, it will be included in proposals with the same stake /// as last epoch, adjusted by rewards from last epoch, if any -/// 4. If account was fisherman last epoch, it is included in fishermen -fn proposals_with_rollover( +fn apply_epoch_update_to_proposals( proposals: Vec, prev_epoch_info: &EpochInfo, validator_reward: &HashMap, From c4c2f281b47ba4d640af398ee11560e1e4a96ada Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 00:34:34 +0400 Subject: [PATCH 02/20] correct numbers --- .../epoch-manager/src/validator_selection.rs | 338 ++++++++++++------ 1 file changed, 233 insertions(+), 105 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index 3f511edae86..e70275a774f 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -33,11 +33,6 @@ pub fn proposals_to_epoch_info( ); let shard_ids: Vec<_> = epoch_config.shard_layout.shard_ids().collect(); - let min_stake_ratio = { - let rational = epoch_config.validator_selection_config.minimum_stake_ratio; - Ratio::new(*rational.numer() as u128, *rational.denom() as u128) - }; - let max_bp_selected = epoch_config.num_block_producer_seats as usize; let mut stake_change = BTreeMap::new(); let proposals = apply_epoch_update_to_proposals( proposals, @@ -47,11 +42,18 @@ pub fn proposals_to_epoch_info( &mut stake_change, ); - let (remaining_proposals, block_producers, chunk_producers, threshold) = + // Select validators for the next epoch. + // Returns unselected proposals, validator lists for all roles and stake + // threshold to become a validator. + let (unselected_proposals, block_producers, chunk_producers, chunk_validators, threshold) = if checked_feature!("stable", StatelessValidationV0, next_version) { - let mut chunk_producer_proposals = order_proposals(proposals.into_values()); - let max_cp_selected = max_bp_selected - + (epoch_config.validator_selection_config.num_chunk_only_producer_seats as usize); + let min_stake_ratio = { + let rational = epoch_config.validator_selection_config.minimum_stake_ratio; + Ratio::new(*rational.numer() as u128, *rational.denom() as u128) + }; + + let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); + let max_cp_selected = 100; let (chunk_producers, cp_stake_threshold) = select_chunk_producers( &mut chunk_producer_proposals, max_cp_selected, @@ -60,43 +62,44 @@ pub fn proposals_to_epoch_info( current_version, ); - (Default::default(), Default::default(), Default::default(), Balance::default()) - } else { let mut block_producer_proposals = order_proposals(proposals.values().cloned()); + let max_bp_selected = 100; let (block_producers, bp_stake_threshold) = select_block_producers( &mut block_producer_proposals, max_bp_selected, min_stake_ratio, current_version, ); - let (chunk_producer_proposals, chunk_producers, cp_stake_threshold) = - if checked_feature!("stable", ChunkOnlyProducers, next_version) { - let mut chunk_producer_proposals = order_proposals(proposals.into_values()); - let max_cp_selected = max_bp_selected - + (epoch_config.validator_selection_config.num_chunk_only_producer_seats - as usize); - let (chunk_producers, cp_stake_threshold) = select_chunk_producers( - &mut chunk_producer_proposals, - max_cp_selected, - min_stake_ratio, - shard_ids.len() as NumShards, - current_version, - ); - (chunk_producer_proposals, chunk_producers, cp_stake_threshold) - } else { - (block_producer_proposals, block_producers.clone(), bp_stake_threshold) - }; - // since block producer proposals could become chunk producers, their actual stake threshold - // is the smaller of the two thresholds - let threshold = cmp::min(bp_stake_threshold, cp_stake_threshold); + let mut chunk_validator_proposals = order_proposals(proposals.into_values()); + let max_cv_selected = 300; + let (chunk_validators, cv_stake_threshold) = select_validators( + &mut chunk_validator_proposals, + max_cv_selected, + min_stake_ratio, + current_version, + ); - // (remaining proposals, BP, CP, threshold) - (chunk_producer_proposals, block_producers, chunk_producers, threshold) + let threshold = + cmp::min(bp_stake_threshold, cmp::min(cp_stake_threshold, cv_stake_threshold)); + ( + chunk_validator_proposals, + block_producers, + chunk_producers, + chunk_validators, + threshold, + ) + } else { + old_validator_selection::select_validators_from_proposals( + epoch_config, + proposals, + next_version, + ) }; - // process remaining proposals that were not selected for either role - for OrderedValidatorStake(p) in remaining_proposals { + // Add kickouts for validators which fell out of validator set. + // Used for querying epoch info by RPC. + for OrderedValidatorStake(p) in unselected_proposals { let stake = p.stake(); let account_id = p.account_id(); *stake_change.get_mut(account_id).unwrap() = 0; @@ -108,76 +111,64 @@ pub fn proposals_to_epoch_info( } } - let num_chunk_producers = chunk_producers.len(); - // Constructing `all_validators` such that a validators position corresponds to its `ValidatorId`. - let mut all_validators: Vec = Vec::with_capacity(num_chunk_producers); - let mut validator_to_index = HashMap::new(); - let mut block_producers_settlement = Vec::with_capacity(block_producers.len()); - - for (i, bp) in block_producers.into_iter().enumerate() { - let id = i as ValidatorId; - validator_to_index.insert(bp.account_id().clone(), id); - block_producers_settlement.push(id); - all_validators.push(bp); - } - - let chunk_producers_settlement = if checked_feature!("stable", ChunkOnlyProducers, next_version) - { - // assign_chunk_producers_to_shards(epoch_config, chunk_producers, &prev_epoch_info)? - let minimum_validators_per_shard = - epoch_config.validator_selection_config.minimum_validators_per_shard as usize; - let shard_assignment = assign_shards( - chunk_producers, - shard_ids.len() as NumShards, - minimum_validators_per_shard, - // prev_epoch_info.chunk_producers_settlement(), - ) - .map_err(|_| EpochError::NotEnoughValidators { - num_validators: num_chunk_producers as u64, - num_shards: shard_ids.len() as NumShards, - })?; - - let mut chunk_producers_settlement: Vec> = - shard_assignment.iter().map(|vs| Vec::with_capacity(vs.len())).collect(); - let mut i = all_validators.len(); - // Here we assign validator ids to all chunk only validators - for (shard_validators, shard_validator_ids) in - shard_assignment.into_iter().zip(chunk_producers_settlement.iter_mut()) - { - for validator in shard_validators { - debug_assert_eq!(i, all_validators.len()); - match validator_to_index.entry(validator.account_id().clone()) { - hash_map::Entry::Vacant(entry) => { - let validator_id = i as ValidatorId; - entry.insert(validator_id); - shard_validator_ids.push(validator_id); - all_validators.push(validator); - i += 1; - } - // Validators which have an entry in the validator_to_index map - // have already been inserted into `all_validators`. - hash_map::Entry::Occupied(entry) => { - let validator_id = *entry.get(); - shard_validator_ids.push(validator_id); - } - } + // Constructing `validator_to_index` and `all_validators` mapping validator + // account names to local indices throughout the epoch and vice versa, for + // convenience of epoch manager. + // Assign chunk producers to shards using local validator indices. + let (all_validators, validator_to_index, mut chunk_producers_settlement) = + if checked_feature!("stable", StatelessValidationV0, next_version) { + let mut all_validators: Vec = + Vec::with_capacity(chunk_validators.len()); + let mut validator_to_index = HashMap::new(); + for (i, validator) in chunk_validators.into_iter().enumerate() { + let id = i as ValidatorId; + validator_to_index.insert(validator.account_id().clone(), id); + all_validators.push(validator); } - } - if epoch_config.validator_selection_config.shuffle_shard_assignment_for_chunk_producers { - chunk_producers_settlement - .shuffle(&mut EpochInfo::shard_assignment_shuffling_rng(&rng_seed)); - } + let num_chunk_producers = chunk_producers.len(); + let minimum_validators_per_shard = + epoch_config.validator_selection_config.minimum_validators_per_shard as usize; + let shard_assignment = assign_shards( + chunk_producers, + shard_ids.len() as NumShards, + minimum_validators_per_shard, + ) + .map_err(|_| EpochError::NotEnoughValidators { + num_validators: num_chunk_producers as u64, + num_shards: shard_ids.len() as NumShards, + })?; + + let chunk_producers_settlement = shard_assignment + .into_iter() + .map(|vs| vs.into_iter().map(|v| validator_to_index[v.account_id()]).collect()) + .collect(); + + (all_validators, validator_to_index, chunk_producers_settlement) + } else if checked_feature!("stable", ChunkOnlyProducers, next_version) { + old_validator_selection::assign_chunk_producers_to_shards_chunk_only( + epoch_config, + chunk_producers, + &block_producers, + )? + } else { + old_validator_selection::assign_chunk_producers_to_shards( + epoch_config, + chunk_producers, + &block_producers, + )? + }; + if epoch_config.validator_selection_config.shuffle_shard_assignment_for_chunk_producers { chunk_producers_settlement - } else { - old_validator_selection::assign_chunk_producers_to_shards( - epoch_config, - chunk_producers, - &block_producers_settlement, - )? - }; + .shuffle(&mut EpochInfo::shard_assignment_shuffling_rng(&rng_seed)); + } + + // Get local indices for block producers. + let block_producers_settlement = + block_producers.into_iter().map(|bp| validator_to_index[bp.account_id()]).collect(); + // Assign chunk validators to shards using validator mandates abstraction. let validator_mandates = if checked_feature!("stable", StatelessValidationV0, next_version) { // Value chosen based on calculations for the security of the protocol. // With this number of mandates per shard and 6 shards, the theory calculations predict the @@ -363,11 +354,140 @@ impl Ord for OrderedValidatorStake { mod old_validator_selection { use super::*; - pub fn assign_chunk_producers_to_shards( + pub(crate) fn select_validators_from_proposals( + epoch_config: &EpochConfig, + proposals: HashMap, + next_version: ProtocolVersion, + ) -> ( + BinaryHeap, + Vec, + Vec, + Vec, + Balance, + ) { + let max_bp_selected = epoch_config.num_block_producer_seats as usize; + let min_stake_ratio = { + let rational = epoch_config.validator_selection_config.minimum_stake_ratio; + Ratio::new(*rational.numer() as u128, *rational.denom() as u128) + }; + + let mut block_producer_proposals = order_proposals(proposals.values().cloned()); + let (block_producers, bp_stake_threshold) = select_block_producers( + &mut block_producer_proposals, + max_bp_selected, + min_stake_ratio, + next_version, + ); + let (chunk_producer_proposals, chunk_producers, cp_stake_threshold) = + if checked_feature!("stable", ChunkOnlyProducers, next_version) { + let mut chunk_producer_proposals = order_proposals(proposals.into_values()); + let max_cp_selected = max_bp_selected + + (epoch_config.validator_selection_config.num_chunk_only_producer_seats + as usize); + let num_shards = epoch_config.shard_layout.shard_ids().count() as NumShards; + let (chunk_producers, cp_stake_threshold) = select_chunk_producers( + &mut chunk_producer_proposals, + max_cp_selected, + min_stake_ratio, + num_shards, + next_version, + ); + (chunk_producer_proposals, chunk_producers, cp_stake_threshold) + } else { + (block_producer_proposals, block_producers.clone(), bp_stake_threshold) + }; + + // since block producer proposals could become chunk producers, their actual stake threshold + // is the smaller of the two thresholds + let threshold = cmp::min(bp_stake_threshold, cp_stake_threshold); + + ( + chunk_producer_proposals, + block_producers, + chunk_producers, + vec![], // chunk validators are not used for older protocol versions + threshold, + ) + } + + pub(crate) fn assign_chunk_producers_to_shards_chunk_only( epoch_config: &EpochConfig, chunk_producers: Vec, - block_producers_settlement: &[ValidatorId], - ) -> Result>, EpochError> { + block_producers: &[ValidatorStake], + ) -> Result< + (Vec, HashMap, Vec>), + EpochError, + > { + let num_chunk_producers = chunk_producers.len(); + let mut all_validators: Vec = Vec::with_capacity(num_chunk_producers); + let mut validator_to_index = HashMap::new(); + for (i, bp) in block_producers.iter().enumerate() { + let id = i as ValidatorId; + validator_to_index.insert(bp.account_id().clone(), id); + all_validators.push(bp.clone()); + } + + let shard_ids: Vec<_> = epoch_config.shard_layout.shard_ids().collect(); + let minimum_validators_per_shard = + epoch_config.validator_selection_config.minimum_validators_per_shard as usize; + let shard_assignment = assign_shards( + chunk_producers, + shard_ids.len() as NumShards, + minimum_validators_per_shard, + ) + .map_err(|_| EpochError::NotEnoughValidators { + num_validators: num_chunk_producers as u64, + num_shards: shard_ids.len() as NumShards, + })?; + + let mut chunk_producers_settlement: Vec> = + shard_assignment.iter().map(|vs| Vec::with_capacity(vs.len())).collect(); + let mut i = all_validators.len(); + // Here we assign validator ids to all chunk only validators + for (shard_validators, shard_validator_ids) in + shard_assignment.into_iter().zip(chunk_producers_settlement.iter_mut()) + { + for validator in shard_validators { + debug_assert_eq!(i, all_validators.len()); + match validator_to_index.entry(validator.account_id().clone()) { + hash_map::Entry::Vacant(entry) => { + let validator_id = i as ValidatorId; + entry.insert(validator_id); + shard_validator_ids.push(validator_id); + all_validators.push(validator); + i += 1; + } + // Validators which have an entry in the validator_to_index map + // have already been inserted into `all_validators`. + hash_map::Entry::Occupied(entry) => { + let validator_id = *entry.get(); + shard_validator_ids.push(validator_id); + } + } + } + } + + Ok((all_validators, validator_to_index, chunk_producers_settlement)) + } + + pub(crate) fn assign_chunk_producers_to_shards( + epoch_config: &EpochConfig, + chunk_producers: Vec, + block_producers: &[ValidatorStake], + ) -> Result< + (Vec, HashMap, Vec>), + EpochError, + > { + let mut all_validators: Vec = Vec::with_capacity(chunk_producers.len()); + let mut validator_to_index = HashMap::new(); + let mut block_producers_settlement = Vec::with_capacity(block_producers.len()); + for (i, bp) in block_producers.into_iter().enumerate() { + let id = i as ValidatorId; + validator_to_index.insert(bp.account_id().clone(), id); + block_producers_settlement.push(id); + all_validators.push(bp.clone()); + } + let shard_ids: Vec<_> = epoch_config.shard_layout.shard_ids().collect(); if chunk_producers.is_empty() { // All validators tried to unstake? @@ -376,12 +496,13 @@ mod old_validator_selection { num_shards: shard_ids.len() as NumShards, }); } + let mut id = 0usize; // Here we assign validators to chunks (we try to keep number of shards assigned for // each validator as even as possible). Note that in prod configuration number of seats // per shard is the same as maximal number of block producers, so normally all // validators would be assigned to all chunks - Ok(shard_ids + let chunk_producers_settlement = shard_ids .iter() .map(|&shard_id| shard_id as usize) .map(|shard_id| { @@ -394,7 +515,14 @@ mod old_validator_selection { }) .collect() }) - .collect()) + .collect(); + + Ok(( + all_validators, + validator_to_index, + block_producers_settlement, + chunk_producers_settlement, + )) } } From f1ddf8b7fea7d11ab0ab884ec8e287efcf387936 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 00:41:48 +0400 Subject: [PATCH 03/20] use bps --- chain/epoch-manager/src/proposals.rs | 1 - .../epoch-manager/src/validator_selection.rs | 24 ++++--------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/chain/epoch-manager/src/proposals.rs b/chain/epoch-manager/src/proposals.rs index 0e1d874f564..c6b6984c1ef 100644 --- a/chain/epoch-manager/src/proposals.rs +++ b/chain/epoch-manager/src/proposals.rs @@ -57,7 +57,6 @@ pub fn proposals_to_epoch_info( validator_kickout, validator_reward, minted_amount, - current_version, next_version, ); } else { diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index e70275a774f..c1cbd5fe29b 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -23,7 +23,6 @@ pub fn proposals_to_epoch_info( mut validator_kickout: HashMap, validator_reward: HashMap, minted_amount: Balance, - current_version: ProtocolVersion, next_version: ProtocolVersion, ) -> Result { debug_assert!( @@ -59,16 +58,16 @@ pub fn proposals_to_epoch_info( max_cp_selected, min_stake_ratio, shard_ids.len() as NumShards, - current_version, + next_version, ); let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let max_bp_selected = 100; + let max_bp_selected = epoch_config.num_block_producer_seats as usize; // 100 in mainnet let (block_producers, bp_stake_threshold) = select_block_producers( &mut block_producer_proposals, max_bp_selected, min_stake_ratio, - current_version, + next_version, ); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); @@ -77,7 +76,7 @@ pub fn proposals_to_epoch_info( &mut chunk_validator_proposals, max_cv_selected, min_stake_ratio, - current_version, + next_version, ); let threshold = @@ -556,7 +555,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -629,7 +627,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -715,7 +712,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); let epoch_info_no_shuffling_different_seed = proposals_to_epoch_info( @@ -727,7 +723,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -741,7 +736,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); let epoch_info_with_shuffling_different_seed = proposals_to_epoch_info( @@ -753,7 +747,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -826,7 +819,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -869,7 +861,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -897,7 +888,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -957,7 +947,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -1031,7 +1020,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -1075,7 +1063,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); #[cfg(feature = "protocol_feature_fix_staking_threshold")] @@ -1099,7 +1086,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); assert_eq!(num_validators, epoch_info.validators_iter().len()); @@ -1127,7 +1113,6 @@ mod tests { Default::default(), 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); @@ -1157,7 +1142,6 @@ mod tests { rewards_map, 0, PROTOCOL_VERSION, - PROTOCOL_VERSION, ) .unwrap(); From 1d53a49e87ce63886954a40b1d1615b899e3c987 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 00:48:16 +0400 Subject: [PATCH 04/20] interface fix --- chain/epoch-manager/src/validator_selection.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index c1cbd5fe29b..a4b9f0e09df 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -114,8 +114,11 @@ pub fn proposals_to_epoch_info( // account names to local indices throughout the epoch and vice versa, for // convenience of epoch manager. // Assign chunk producers to shards using local validator indices. + // TODO: this happens together because assigment logic is more subtle for + // older protocol versions, consider decoupling it. let (all_validators, validator_to_index, mut chunk_producers_settlement) = if checked_feature!("stable", StatelessValidationV0, next_version) { + // Construct local validator indices. let mut all_validators: Vec = Vec::with_capacity(chunk_validators.len()); let mut validator_to_index = HashMap::new(); @@ -125,6 +128,7 @@ pub fn proposals_to_epoch_info( all_validators.push(validator); } + // Assign chunk producers to shards. let num_chunk_producers = chunk_producers.len(); let minimum_validators_per_shard = epoch_config.validator_selection_config.minimum_validators_per_shard as usize; @@ -516,12 +520,7 @@ mod old_validator_selection { }) .collect(); - Ok(( - all_validators, - validator_to_index, - block_producers_settlement, - chunk_producers_settlement, - )) + Ok((all_validators, validator_to_index, chunk_producers_settlement)) } } From 74393f24b6d99ff875a1e347114818945e9c83e0 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 02:00:54 +0400 Subject: [PATCH 05/20] fix tests --- .../epoch-manager/src/validator_selection.rs | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index a4b9f0e09df..c0dc8ab3741 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -44,7 +44,7 @@ pub fn proposals_to_epoch_info( // Select validators for the next epoch. // Returns unselected proposals, validator lists for all roles and stake // threshold to become a validator. - let (unselected_proposals, block_producers, chunk_producers, chunk_validators, threshold) = + let (unselected_proposals, chunk_producers, block_producers, chunk_validators, threshold) = if checked_feature!("stable", StatelessValidationV0, next_version) { let min_stake_ratio = { let rational = epoch_config.validator_selection_config.minimum_stake_ratio; @@ -70,6 +70,7 @@ pub fn proposals_to_epoch_info( next_version, ); + println!("PROPOSALS: {:?}", proposals); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); let max_cv_selected = 300; let (chunk_validators, cv_stake_threshold) = select_validators( @@ -79,15 +80,26 @@ pub fn proposals_to_epoch_info( next_version, ); + // Note that if there are too few validators and too many shards, + // assigning chunk producers to shards is more aggressive, so it + // is not enough to iterate over chunk validators. + // So unfortunately we have to look over all roles to get unselected + // proposals. + // TODO: must be simplified. + let max_validators_for_role = cmp::max( + chunk_producers.len(), + cmp::max(block_producers.len(), chunk_validators.len()), + ); + let unselected_proposals = if chunk_producers.len() == max_validators_for_role { + chunk_producer_proposals + } else if block_producers.len() == max_validators_for_role { + block_producer_proposals + } else { + chunk_validator_proposals + }; let threshold = cmp::min(bp_stake_threshold, cmp::min(cp_stake_threshold, cv_stake_threshold)); - ( - chunk_validator_proposals, - block_producers, - chunk_producers, - chunk_validators, - threshold, - ) + (unselected_proposals, chunk_producers, block_producers, chunk_validators, threshold) } else { old_validator_selection::select_validators_from_proposals( epoch_config, @@ -96,6 +108,8 @@ pub fn proposals_to_epoch_info( ) }; + println!("CPS: {:?}", chunk_producers); + println!("CVS: {:?}", chunk_validators); // Add kickouts for validators which fell out of validator set. // Used for querying epoch info by RPC. for OrderedValidatorStake(p) in unselected_proposals { @@ -119,13 +133,30 @@ pub fn proposals_to_epoch_info( let (all_validators, validator_to_index, mut chunk_producers_settlement) = if checked_feature!("stable", StatelessValidationV0, next_version) { // Construct local validator indices. + // Note that if there are too few validators and too many shards, + // assigning chunk producers to shards is more aggressive, so it + // is not enough to iterate over chunk validators. + // We assign local indices in the order of roles priority and then + // in decreasing order of stake. + let max_validators_for_role = cmp::max( + chunk_producers.len(), + cmp::max(block_producers.len(), chunk_validators.len()), + ); let mut all_validators: Vec = - Vec::with_capacity(chunk_validators.len()); + Vec::with_capacity(max_validators_for_role); let mut validator_to_index = HashMap::new(); - for (i, validator) in chunk_validators.into_iter().enumerate() { - let id = i as ValidatorId; - validator_to_index.insert(validator.account_id().clone(), id); - all_validators.push(validator); + for validators_for_role in + [&chunk_producers, &block_producers, &chunk_validators].iter() + { + for validator in validators_for_role.iter() { + let account_id = validator.account_id().clone(); + if validator_to_index.contains_key(&account_id) { + continue; + } + let id = all_validators.len() as ValidatorId; + validator_to_index.insert(account_id, id); + all_validators.push(validator.clone()); + } } // Assign chunk producers to shards. @@ -142,6 +173,7 @@ pub fn proposals_to_epoch_info( num_shards: shard_ids.len() as NumShards, })?; + println!("ASSIGN: {:?}", shard_assignment); let chunk_producers_settlement = shard_assignment .into_iter() .map(|vs| vs.into_iter().map(|v| validator_to_index[v.account_id()]).collect()) @@ -406,8 +438,8 @@ mod old_validator_selection { ( chunk_producer_proposals, - block_producers, chunk_producers, + block_producers, vec![], // chunk validators are not used for older protocol versions threshold, ) From 2d3cd6e79815bb31c81bb03278c2aeeaab972f4f Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 02:02:55 +0400 Subject: [PATCH 06/20] no debug --- chain/chain/src/test_utils/kv_runtime.rs | 3 --- chain/epoch-manager/src/proposals.rs | 3 --- chain/epoch-manager/src/test_utils.rs | 3 --- .../epoch-manager/src/validator_selection.rs | 7 ------- core/primitives/src/epoch_manager.rs | 21 ++++++++----------- 5 files changed, 9 insertions(+), 28 deletions(-) diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index dfbf85d72fe..255aa2f1768 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -530,9 +530,6 @@ impl EpochManagerAdapter for MockEpochManager { validator_to_index, bp_settlement, cp_settlement, - vec![], - vec![], - HashMap::new(), BTreeMap::new(), HashMap::new(), HashMap::new(), diff --git a/chain/epoch-manager/src/proposals.rs b/chain/epoch-manager/src/proposals.rs index c6b6984c1ef..af5353d3ae6 100644 --- a/chain/epoch-manager/src/proposals.rs +++ b/chain/epoch-manager/src/proposals.rs @@ -257,9 +257,6 @@ mod old_validator_selection { validator_to_index, block_producers_settlement, chunk_producers_settlement, - vec![], - fishermen, - fishermen_to_index, stake_change, validator_reward, validator_kickout, diff --git a/chain/epoch-manager/src/test_utils.rs b/chain/epoch-manager/src/test_utils.rs index 441e8d4903d..fdcbb71a399 100644 --- a/chain/epoch-manager/src/test_utils.rs +++ b/chain/epoch-manager/src/test_utils.rs @@ -121,9 +121,6 @@ pub fn epoch_info_with_num_seats( validator_to_index, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement, - account_to_validators(fishermen), - fishermen_to_index, stake_change, validator_reward, validator_kickout.into_iter().collect(), diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index c0dc8ab3741..9946f89d4af 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -70,7 +70,6 @@ pub fn proposals_to_epoch_info( next_version, ); - println!("PROPOSALS: {:?}", proposals); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); let max_cv_selected = 300; let (chunk_validators, cv_stake_threshold) = select_validators( @@ -108,8 +107,6 @@ pub fn proposals_to_epoch_info( ) }; - println!("CPS: {:?}", chunk_producers); - println!("CVS: {:?}", chunk_validators); // Add kickouts for validators which fell out of validator set. // Used for querying epoch info by RPC. for OrderedValidatorStake(p) in unselected_proposals { @@ -173,7 +170,6 @@ pub fn proposals_to_epoch_info( num_shards: shard_ids.len() as NumShards, })?; - println!("ASSIGN: {:?}", shard_assignment); let chunk_producers_settlement = shard_assignment .into_iter() .map(|vs| vs.into_iter().map(|v| validator_to_index[v.account_id()]).collect()) @@ -225,9 +221,6 @@ pub fn proposals_to_epoch_info( validator_to_index, block_producers_settlement, chunk_producers_settlement, - vec![], - vec![], - Default::default(), stake_change, validator_reward, validator_kickout, diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index 8ecc3a80d31..333b46c4822 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -742,9 +742,6 @@ pub mod epoch_info { validator_to_index: HashMap, block_producers_settlement: Vec, chunk_producers_settlement: Vec>, - hidden_validators_settlement: Vec, - fishermen: Vec, - fishermen_to_index: HashMap, stake_change: BTreeMap, validator_reward: HashMap, validator_kickout: HashMap, @@ -770,15 +767,15 @@ pub mod epoch_info { Self::V4(EpochInfoV4 { epoch_height, validators, - fishermen, + fishermen: Default::default(), validator_to_index, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement, + hidden_validators_settlement: Default::default(), stake_change, validator_reward, validator_kickout, - fishermen_to_index, + fishermen_to_index: Default::default(), minted_amount, seat_price, protocol_version, @@ -791,15 +788,15 @@ pub mod epoch_info { Self::V3(EpochInfoV3 { epoch_height, validators, - fishermen, + fishermen: Default::default(), validator_to_index, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement, + hidden_validators_settlement: Default::default(), stake_change, validator_reward, validator_kickout, - fishermen_to_index, + fishermen_to_index: Default::default(), minted_amount, seat_price, protocol_version, @@ -812,15 +809,15 @@ pub mod epoch_info { Self::V2(EpochInfoV2 { epoch_height, validators, - fishermen, + fishermen: Default::default(), validator_to_index, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement, + hidden_validators_settlement: Default::default(), stake_change, validator_reward, validator_kickout, - fishermen_to_index, + fishermen_to_index: Default::default(), minted_amount, seat_price, protocol_version, From 31aad600d071b34ec81124192490340d825d5d8b Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 02:12:51 +0400 Subject: [PATCH 07/20] warnd --- chain/epoch-manager/src/proposals.rs | 6 ------ chain/epoch-manager/src/test_utils.rs | 6 ++---- chain/epoch-manager/src/validator_selection.rs | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/chain/epoch-manager/src/proposals.rs b/chain/epoch-manager/src/proposals.rs index af5353d3ae6..3242ebc59fa 100644 --- a/chain/epoch-manager/src/proposals.rs +++ b/chain/epoch-manager/src/proposals.rs @@ -236,12 +236,6 @@ mod old_validator_selection { chunk_producers_settlement.push(shard_settlement); } - let fishermen_to_index = fishermen - .iter() - .enumerate() - .map(|(index, s)| (s.account_id().clone(), index as ValidatorId)) - .collect::>(); - let validator_to_index = final_proposals .iter() .enumerate() diff --git a/chain/epoch-manager/src/test_utils.rs b/chain/epoch-manager/src/test_utils.rs index fdcbb71a399..5fa5e0e4344 100644 --- a/chain/epoch-manager/src/test_utils.rs +++ b/chain/epoch-manager/src/test_utils.rs @@ -76,8 +76,8 @@ pub fn epoch_info_with_num_seats( mut accounts: Vec<(AccountId, Balance)>, block_producers_settlement: Vec, chunk_producers_settlement: Vec>, - hidden_validators_settlement: Vec, - fishermen: Vec<(AccountId, Balance)>, + _hidden_validators_settlement: Vec, + _fishermen: Vec<(AccountId, Balance)>, stake_change: BTreeMap, validator_kickout: Vec<(AccountId, ValidatorKickoutReason)>, validator_reward: HashMap, @@ -91,8 +91,6 @@ pub fn epoch_info_with_num_seats( acc.insert(x.0.clone(), i as u64); acc }); - let fishermen_to_index = - fishermen.iter().enumerate().map(|(i, (s, _))| (s.clone(), i as ValidatorId)).collect(); let account_to_validators = |accounts: Vec<(AccountId, Balance)>| -> Vec { accounts .into_iter() diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index 9946f89d4af..d1e7d8cd5d3 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -71,7 +71,7 @@ pub fn proposals_to_epoch_info( ); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); - let max_cv_selected = 300; + let max_cv_selected = 300; // set to align with obsolete chunk-only producers number in mainnet let (chunk_validators, cv_stake_threshold) = select_validators( &mut chunk_validator_proposals, max_cv_selected, From 3761136b2c851f27f9019bde1d86c93b01039d53 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 02:22:23 +0400 Subject: [PATCH 08/20] debug --- core/primitives/src/epoch_manager.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index 333b46c4822..7f50922421f 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -717,9 +717,12 @@ pub mod epoch_info { pub validator_to_index: HashMap, pub block_producers_settlement: Vec, pub chunk_producers_settlement: Vec>, - pub hidden_validators_settlement: Vec, - pub fishermen: Vec, - pub fishermen_to_index: HashMap, + /// Deprecated. + pub _hidden_validators_settlement: Vec, + /// Deprecated. + pub _fishermen: Vec, + /// Deprecated. + pub _fishermen_to_index: HashMap, pub stake_change: BTreeMap, pub validator_reward: HashMap, pub validator_kickout: HashMap, @@ -767,15 +770,15 @@ pub mod epoch_info { Self::V4(EpochInfoV4 { epoch_height, validators, - fishermen: Default::default(), + _fishermen: Default::default(), validator_to_index, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement: Default::default(), + _hidden_validators_settlement: Default::default(), stake_change, validator_reward, validator_kickout, - fishermen_to_index: Default::default(), + _fishermen_to_index: Default::default(), minted_amount, seat_price, protocol_version, @@ -975,7 +978,7 @@ pub mod epoch_info { Self::V1(v1) => ValidatorStakeIter::v1(&v1.fishermen), Self::V2(v2) => ValidatorStakeIter::new(&v2.fishermen), Self::V3(v3) => ValidatorStakeIter::new(&v3.fishermen), - Self::V4(v4) => ValidatorStakeIter::new(&v4.fishermen), + Self::V4(v4) => ValidatorStakeIter::new(&v4._fishermen), } } @@ -1054,7 +1057,7 @@ pub mod epoch_info { Self::V1(v1) => v1.fishermen_to_index.contains_key(account_id), Self::V2(v2) => v2.fishermen_to_index.contains_key(account_id), Self::V3(v3) => v3.fishermen_to_index.contains_key(account_id), - Self::V4(v4) => v4.fishermen_to_index.contains_key(account_id), + Self::V4(v4) => v4._fishermen_to_index.contains_key(account_id), } } @@ -1072,9 +1075,9 @@ pub mod epoch_info { .get(account_id) .map(|validator_id| v3.fishermen[*validator_id as usize].clone()), Self::V4(v4) => v4 - .fishermen_to_index + ._fishermen_to_index .get(account_id) - .map(|validator_id| v4.fishermen[*validator_id as usize].clone()), + .map(|validator_id| v4._fishermen[*validator_id as usize].clone()), } } @@ -1084,7 +1087,7 @@ pub mod epoch_info { Self::V1(v1) => ValidatorStake::V1(v1.fishermen[fisherman_id as usize].clone()), Self::V2(v2) => v2.fishermen[fisherman_id as usize].clone(), Self::V3(v3) => v3.fishermen[fisherman_id as usize].clone(), - Self::V4(v4) => v4.fishermen[fisherman_id as usize].clone(), + Self::V4(v4) => v4._fishermen[fisherman_id as usize].clone(), } } From 6e73a84d2e8fce29772005f6dfb2146eebef4d68 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 03:40:56 +0400 Subject: [PATCH 09/20] fix cp numbers --- .../epoch-manager/src/validator_selection.rs | 89 ++++++++++--------- core/chain-configs/src/genesis_config.rs | 11 +++ core/primitives/src/epoch_manager.rs | 3 + 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index d1e7d8cd5d3..40c9e909309 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -52,7 +52,8 @@ pub fn proposals_to_epoch_info( }; let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); - let max_cp_selected = 100; + let max_cp_selected = + epoch_config.validator_selection_config.num_chunk_producer_seats as usize; let (chunk_producers, cp_stake_threshold) = select_chunk_producers( &mut chunk_producer_proposals, max_cp_selected, @@ -71,7 +72,7 @@ pub fn proposals_to_epoch_info( ); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); - let max_cv_selected = 300; // set to align with obsolete chunk-only producers number in mainnet + let max_cv_selected = 300; // set to align with obsolete number of chunk-only producers in mainnet let (chunk_validators, cv_stake_threshold) = select_validators( &mut chunk_validator_proposals, max_cv_selected, @@ -611,6 +612,7 @@ mod tests { // purposely set the fishermen threshold high so that none become fishermen 10_000, ValidatorSelectionConfig { + num_chunk_producer_seats: num_bp_seats + num_cp_seats, num_chunk_only_producer_seats: num_cp_seats, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -685,21 +687,32 @@ mod tests { // the old, low-stake proposals were not accepted let kickout = epoch_info.validator_kickout(); - assert_eq!(kickout.len(), 2); - assert_eq!( - kickout.get(AccountIdRef::new_or_panic("test1")).unwrap(), - &ValidatorKickoutReason::NotEnoughStake { stake: test1_stake, threshold: 2011 }, - ); - assert_eq!( - kickout.get(AccountIdRef::new_or_panic("test2")).unwrap(), - &ValidatorKickoutReason::NotEnoughStake { stake: 2002, threshold: 2011 }, - ); + // For stateless validation, everyone is selected as chunk validator. + if checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + assert_eq!(kickout.len(), 0); + } else { + assert_eq!(kickout.len(), 2); + assert_eq!( + kickout.get(AccountIdRef::new_or_panic("test1")).unwrap(), + &ValidatorKickoutReason::NotEnoughStake { stake: test1_stake, threshold: 2011 }, + ); + assert_eq!( + kickout.get(AccountIdRef::new_or_panic("test2")).unwrap(), + &ValidatorKickoutReason::NotEnoughStake { stake: 2002, threshold: 2011 }, + ); + }; } // Test that the chunk validators' shard assignments will be shuffled or not shuffled // depending on the `shuffle_shard_assignment_for_chunk_producers` flag. #[test] fn test_validator_assignment_with_chunk_only_producers_with_shard_shuffling() { + // Don't run test without stateless validation because it has slight + // changes in validator epoch indexing. + if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + return; + } + let num_bp_seats = 10; let num_cp_seats = 30; let mut epoch_config = create_epoch_config( @@ -708,6 +721,7 @@ mod tests { // purposely set the fishermen threshold high so that none become fishermen 10_000, ValidatorSelectionConfig { + num_chunk_producer_seats: num_bp_seats + num_cp_seats, num_chunk_only_producer_seats: num_cp_seats, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -774,45 +788,34 @@ mod tests { ) .unwrap(); - assert_eq!( - epoch_info_no_shuffling.chunk_producers_settlement(), - vec![ - vec![0, 10, 11, 12, 13, 14, 15], - vec![1, 16, 17, 18, 19, 20, 21], - vec![2, 9, 22, 23, 24, 25, 26], - vec![3, 8, 27, 28, 29, 30, 31], - vec![4, 7, 32, 33, 34, 35], - vec![5, 6, 36, 37, 38, 39], - ], - ); + let target_settlement = vec![ + vec![0, 11, 12, 23, 24, 35, 36], + vec![1, 10, 13, 22, 25, 34, 37], + vec![2, 9, 14, 21, 26, 33, 38], + vec![3, 8, 15, 20, 27, 32, 39], + vec![4, 7, 16, 19, 28, 31], + vec![5, 6, 17, 18, 29, 30], + ]; + assert_eq!(epoch_info_no_shuffling.chunk_producers_settlement(), target_settlement,); assert_eq!( epoch_info_no_shuffling.chunk_producers_settlement(), epoch_info_no_shuffling_different_seed.chunk_producers_settlement() ); - assert_eq!( - epoch_info_with_shuffling.chunk_producers_settlement(), - vec![ - vec![4, 7, 32, 33, 34, 35], - vec![2, 9, 22, 23, 24, 25, 26], - vec![1, 16, 17, 18, 19, 20, 21], - vec![0, 10, 11, 12, 13, 14, 15], - vec![5, 6, 36, 37, 38, 39], - vec![3, 8, 27, 28, 29, 30, 31], - ], - ); + let shuffled_settlement = [4, 2, 1, 0, 5, 3] + .into_iter() + .map(|i| target_settlement[i].clone()) + .collect::>(); + assert_eq!(epoch_info_with_shuffling.chunk_producers_settlement(), shuffled_settlement); + let shuffled_settlement = [3, 1, 0, 2, 5, 4] + .into_iter() + .map(|i| target_settlement[i].clone()) + .collect::>(); assert_eq!( epoch_info_with_shuffling_different_seed.chunk_producers_settlement(), - vec![ - vec![3, 8, 27, 28, 29, 30, 31], - vec![1, 16, 17, 18, 19, 20, 21], - vec![0, 10, 11, 12, 13, 14, 15], - vec![2, 9, 22, 23, 24, 25, 26], - vec![5, 6, 36, 37, 38, 39], - vec![4, 7, 32, 33, 34, 35], - ], + shuffled_settlement, ); } @@ -824,6 +827,7 @@ mod tests { 2, 0, ValidatorSelectionConfig { + num_chunk_producer_seats: 2, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -865,6 +869,7 @@ mod tests { 2 * num_shards, 0, ValidatorSelectionConfig { + num_chunk_producer_seats: 2 * num_shards, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -937,6 +942,7 @@ mod tests { 2 * num_shards, 0, ValidatorSelectionConfig { + num_chunk_producer_seats: 2 * num_shards, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, // for example purposes, we choose a higher ratio than in production @@ -1017,6 +1023,7 @@ mod tests { 100, 150, ValidatorSelectionConfig { + num_chunk_producer_seats: 300, num_chunk_only_producer_seats: 300, minimum_validators_per_shard: 1, // for example purposes, we choose a higher ratio than in production diff --git a/core/chain-configs/src/genesis_config.rs b/core/chain-configs/src/genesis_config.rs index 1e160d90abe..997e8d6734a 100644 --- a/core/chain-configs/src/genesis_config.rs +++ b/core/chain-configs/src/genesis_config.rs @@ -68,6 +68,10 @@ fn default_minimum_validators_per_shard() -> u64 { 1 } +fn default_num_chunk_producer_seats() -> u64 { + 300 +} + fn default_num_chunk_only_producer_seats() -> u64 { 300 } @@ -162,6 +166,7 @@ pub struct GenesisConfig { pub shard_layout: ShardLayout, #[serde(default = "default_num_chunk_only_producer_seats")] #[default(300)] + /// Deprecated. pub num_chunk_only_producer_seats: NumSeats, /// The minimum number of validators each shard must have #[serde(default = "default_minimum_validators_per_shard")] @@ -189,6 +194,11 @@ pub struct GenesisConfig { /// in AllEpochConfig, and we want to have a way to test that code path. This flag is for that. /// If set to true, the node will use the same config override path as mainnet and testnet. pub use_production_config: bool, + /// Number of chunk producers. + /// Don't mess it up with chunk-only producers feature which is deprecated. + #[serde(default = "default_num_chunk_producer_seats")] + #[default(300)] + pub num_chunk_producer_seats: NumSeats, } impl GenesisConfig { @@ -217,6 +227,7 @@ impl From<&GenesisConfig> for EpochConfig { minimum_stake_divisor: config.minimum_stake_divisor, shard_layout: config.shard_layout.clone(), validator_selection_config: near_primitives::epoch_manager::ValidatorSelectionConfig { + num_chunk_producer_seats: config.num_chunk_producer_seats, num_chunk_only_producer_seats: config.num_chunk_only_producer_seats, minimum_validators_per_shard: config.minimum_validators_per_shard, minimum_stake_ratio: config.minimum_stake_ratio, diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index 7f50922421f..82eb54aae95 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -280,6 +280,9 @@ impl AllEpochConfig { /// algorithm. See for details. #[derive(Debug, Clone, SmartDefault, PartialEq, Eq)] pub struct ValidatorSelectionConfig { + #[default(300)] + pub num_chunk_producer_seats: NumSeats, + // Deprecated, should be set to zero for future protocol versions. #[default(300)] pub num_chunk_only_producer_seats: NumSeats, #[default(1)] From 141e4951bdf6d37238faa886261a4dffe47747a6 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 03:41:46 +0400 Subject: [PATCH 10/20] fix cp numbers --- tools/fork-network/src/cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index d1008a21787..8a250f61350 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -807,6 +807,7 @@ impl ForkNetworkCommand { total_supply: original_config.total_supply, transaction_validity_period: original_config.transaction_validity_period, use_production_config: original_config.use_production_config, + num_chunk_producer_seats: original_config.num_chunk_producer_seats, }; let genesis = Genesis::new_from_state_roots(new_config, new_state_roots); From f0e5fa9e0aa2477479f9eb31da7ec8e5e9fcd0b7 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 16:33:32 +0400 Subject: [PATCH 11/20] put cv in config --- .../epoch-manager/src/validator_selection.rs | 67 +++++++++---------- core/chain-configs/src/genesis_config.rs | 12 +++- core/primitives/src/epoch_manager.rs | 4 +- tools/fork-network/src/cli.rs | 1 + 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index 40c9e909309..01e28c7f3ec 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -52,30 +52,26 @@ pub fn proposals_to_epoch_info( }; let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); - let max_cp_selected = - epoch_config.validator_selection_config.num_chunk_producer_seats as usize; let (chunk_producers, cp_stake_threshold) = select_chunk_producers( &mut chunk_producer_proposals, - max_cp_selected, + epoch_config.validator_selection_config.num_chunk_producer_seats as usize, min_stake_ratio, shard_ids.len() as NumShards, next_version, ); let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let max_bp_selected = epoch_config.num_block_producer_seats as usize; // 100 in mainnet let (block_producers, bp_stake_threshold) = select_block_producers( &mut block_producer_proposals, - max_bp_selected, + epoch_config.num_block_producer_seats as usize, min_stake_ratio, next_version, ); let mut chunk_validator_proposals = order_proposals(proposals.into_values()); - let max_cv_selected = 300; // set to align with obsolete number of chunk-only producers in mainnet let (chunk_validators, cv_stake_threshold) = select_validators( &mut chunk_validator_proposals, - max_cv_selected, + epoch_config.validator_selection_config.num_chunk_validator_seats as usize, min_stake_ratio, next_version, ); @@ -567,7 +563,7 @@ mod tests { // A simple sanity test. Given fewer proposals than the number of seats, // none of which has too little stake, they all get assigned as block and // chunk producers. - let epoch_config = create_epoch_config(2, 100, 0, Default::default()); + let epoch_config = create_epoch_config(2, 100, Default::default()); let prev_epoch_height = 7; let prev_epoch_info = create_prev_epoch_info(prev_epoch_height, &["test1", "test2"], &[]); let proposals = create_proposals(&[("test1", 1000), ("test2", 2000), ("test3", 300)]); @@ -609,10 +605,9 @@ mod tests { let epoch_config = create_epoch_config( 2, num_bp_seats, - // purposely set the fishermen threshold high so that none become fishermen - 10_000, ValidatorSelectionConfig { num_chunk_producer_seats: num_bp_seats + num_cp_seats, + num_chunk_validator_seats: num_bp_seats + num_cp_seats, num_chunk_only_producer_seats: num_cp_seats, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -687,20 +682,15 @@ mod tests { // the old, low-stake proposals were not accepted let kickout = epoch_info.validator_kickout(); - // For stateless validation, everyone is selected as chunk validator. - if checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { - assert_eq!(kickout.len(), 0); - } else { - assert_eq!(kickout.len(), 2); - assert_eq!( - kickout.get(AccountIdRef::new_or_panic("test1")).unwrap(), - &ValidatorKickoutReason::NotEnoughStake { stake: test1_stake, threshold: 2011 }, - ); - assert_eq!( - kickout.get(AccountIdRef::new_or_panic("test2")).unwrap(), - &ValidatorKickoutReason::NotEnoughStake { stake: 2002, threshold: 2011 }, - ); - }; + assert_eq!(kickout.len(), 2); + assert_eq!( + kickout.get(AccountIdRef::new_or_panic("test1")).unwrap(), + &ValidatorKickoutReason::NotEnoughStake { stake: test1_stake, threshold: 2011 }, + ); + assert_eq!( + kickout.get(AccountIdRef::new_or_panic("test2")).unwrap(), + &ValidatorKickoutReason::NotEnoughStake { stake: 2002, threshold: 2011 }, + ); } // Test that the chunk validators' shard assignments will be shuffled or not shuffled @@ -718,10 +708,9 @@ mod tests { let mut epoch_config = create_epoch_config( 6, num_bp_seats, - // purposely set the fishermen threshold high so that none become fishermen - 10_000, ValidatorSelectionConfig { num_chunk_producer_seats: num_bp_seats + num_cp_seats, + num_chunk_validator_seats: num_bp_seats + num_cp_seats, num_chunk_only_producer_seats: num_cp_seats, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -825,9 +814,9 @@ mod tests { let epoch_config = create_epoch_config( num_shards, 2, - 0, ValidatorSelectionConfig { num_chunk_producer_seats: 2, + num_chunk_validator_seats: 2, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -867,9 +856,9 @@ mod tests { let epoch_config = create_epoch_config( num_shards, 2 * num_shards, - 0, ValidatorSelectionConfig { num_chunk_producer_seats: 2 * num_shards, + num_chunk_validator_seats: 2 * num_shards, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, minimum_stake_ratio: Ratio::new(160, 1_000_000), @@ -934,15 +923,14 @@ mod tests { } } - #[cfg(feature = "nightly")] fn get_epoch_info_for_chunk_validators_sampling() -> EpochInfo { let num_shards = 4; let epoch_config = create_epoch_config( num_shards, 2 * num_shards, - 0, ValidatorSelectionConfig { num_chunk_producer_seats: 2 * num_shards, + num_chunk_validator_seats: 2 * num_shards, num_chunk_only_producer_seats: 0, minimum_validators_per_shard: 1, // for example purposes, we choose a higher ratio than in production @@ -987,8 +975,11 @@ mod tests { /// `EpochInfo`. The internals of mandate assignment are tested in the module containing /// [`ValidatorMandates`]. #[test] - #[cfg(feature = "nightly")] fn test_chunk_validators_sampling() { + if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + return; + } + let epoch_info = get_epoch_info_for_chunk_validators_sampling(); // Given `epoch_info` and `proposals` above, the sample at a given height is deterministic. let height = 42; @@ -1002,8 +993,11 @@ mod tests { } #[test] - #[cfg(feature = "nightly")] fn test_deterministic_chunk_validators_sampling() { + if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + return; + } + let epoch_info = get_epoch_info_for_chunk_validators_sampling(); let height = 42; let assignment1 = epoch_info.sample_chunk_validators(height); @@ -1021,9 +1015,9 @@ mod tests { let epoch_config = create_epoch_config( 1, 100, - 150, ValidatorSelectionConfig { num_chunk_producer_seats: 300, + num_chunk_validator_seats: 300, num_chunk_only_producer_seats: 300, minimum_validators_per_shard: 1, // for example purposes, we choose a higher ratio than in production @@ -1125,7 +1119,7 @@ mod tests { #[test] fn test_validator_assignment_with_kickout() { // kicked out validators are not selected - let epoch_config = create_epoch_config(1, 100, 0, Default::default()); + let epoch_config = create_epoch_config(1, 100, Default::default()); let prev_epoch_height = 7; let prev_epoch_info = create_prev_epoch_info( prev_epoch_height, @@ -1156,7 +1150,7 @@ mod tests { // validator balances are updated based on their rewards let validators = [("test1", 3000), ("test2", 2000), ("test3", 1000)]; let rewards: [u128; 3] = [7, 8, 9]; - let epoch_config = create_epoch_config(1, 100, 0, Default::default()); + let epoch_config = create_epoch_config(1, 100, Default::default()); let prev_epoch_height = 7; let prev_epoch_info = create_prev_epoch_info(prev_epoch_height, &validators, &[]); let rewards_map = validators @@ -1194,7 +1188,6 @@ mod tests { fn create_epoch_config( num_shards: u64, num_block_producer_seats: u64, - fishermen_threshold: Balance, validator_selection_config: ValidatorSelectionConfig, ) -> EpochConfig { EpochConfig { @@ -1207,7 +1200,7 @@ mod tests { validator_max_kickout_stake_perc: 100, online_min_threshold: 0.into(), online_max_threshold: 0.into(), - fishermen_threshold, + fishermen_threshold: 0, minimum_stake_divisor: 0, protocol_upgrade_stake_threshold: 0.into(), shard_layout: ShardLayout::v0(num_shards, 0), diff --git a/core/chain-configs/src/genesis_config.rs b/core/chain-configs/src/genesis_config.rs index 997e8d6734a..1c31d117733 100644 --- a/core/chain-configs/src/genesis_config.rs +++ b/core/chain-configs/src/genesis_config.rs @@ -69,6 +69,10 @@ fn default_minimum_validators_per_shard() -> u64 { } fn default_num_chunk_producer_seats() -> u64 { + 100 +} + +fn default_num_chunk_validator_seats() -> u64 { 300 } @@ -194,11 +198,14 @@ pub struct GenesisConfig { /// in AllEpochConfig, and we want to have a way to test that code path. This flag is for that. /// If set to true, the node will use the same config override path as mainnet and testnet. pub use_production_config: bool, + #[serde(default = "default_num_chunk_producer_seats")] + #[default(100)] /// Number of chunk producers. /// Don't mess it up with chunk-only producers feature which is deprecated. - #[serde(default = "default_num_chunk_producer_seats")] - #[default(300)] pub num_chunk_producer_seats: NumSeats, + #[serde(default = "default_num_chunk_validator_seats")] + #[default(300)] + pub num_chunk_validator_seats: NumSeats, } impl GenesisConfig { @@ -228,6 +235,7 @@ impl From<&GenesisConfig> for EpochConfig { shard_layout: config.shard_layout.clone(), validator_selection_config: near_primitives::epoch_manager::ValidatorSelectionConfig { num_chunk_producer_seats: config.num_chunk_producer_seats, + num_chunk_validator_seats: config.num_chunk_validator_seats, num_chunk_only_producer_seats: config.num_chunk_only_producer_seats, minimum_validators_per_shard: config.minimum_validators_per_shard, minimum_stake_ratio: config.minimum_stake_ratio, diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index 82eb54aae95..f85f5d4ddb1 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -280,8 +280,10 @@ impl AllEpochConfig { /// algorithm. See for details. #[derive(Debug, Clone, SmartDefault, PartialEq, Eq)] pub struct ValidatorSelectionConfig { - #[default(300)] + #[default(100)] pub num_chunk_producer_seats: NumSeats, + #[default(300)] + pub num_chunk_validator_seats: NumSeats, // Deprecated, should be set to zero for future protocol versions. #[default(300)] pub num_chunk_only_producer_seats: NumSeats, diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index 8a250f61350..5bb248bb11b 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -808,6 +808,7 @@ impl ForkNetworkCommand { transaction_validity_period: original_config.transaction_validity_period, use_production_config: original_config.use_production_config, num_chunk_producer_seats: original_config.num_chunk_producer_seats, + num_chunk_validator_seats: original_config.num_chunk_validator_seats, }; let genesis = Genesis::new_from_state_roots(new_config, new_state_roots); From 6ee3d97f2768fed40eeb1922ef32313f23158f8e Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 19:47:47 +0400 Subject: [PATCH 12/20] todo --- core/primitives/src/epoch_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index f85f5d4ddb1..d11132cccb9 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -284,7 +284,7 @@ pub struct ValidatorSelectionConfig { pub num_chunk_producer_seats: NumSeats, #[default(300)] pub num_chunk_validator_seats: NumSeats, - // Deprecated, should be set to zero for future protocol versions. + // TODO (#11267): deprecate after StatelessValidationV0 is in place. #[default(300)] pub num_chunk_only_producer_seats: NumSeats, #[default(1)] From bab624cb2a00920f96df265fcbfaf820d0287edb Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 19:48:55 +0400 Subject: [PATCH 13/20] todo --- core/primitives/src/epoch_manager.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index d11132cccb9..0f7fc2f6a43 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -285,6 +285,7 @@ pub struct ValidatorSelectionConfig { #[default(300)] pub num_chunk_validator_seats: NumSeats, // TODO (#11267): deprecate after StatelessValidationV0 is in place. + // Use 300 for older protocol versions. #[default(300)] pub num_chunk_only_producer_seats: NumSeats, #[default(1)] From 8048e32a66927c8303849e1a57675dc43b6cbb6f Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 8 May 2024 23:34:44 +0400 Subject: [PATCH 14/20] todo --- chain/epoch-manager/src/shard_assignment.rs | 4 ++-- chain/epoch-manager/src/validator_selection.rs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/chain/epoch-manager/src/shard_assignment.rs b/chain/epoch-manager/src/shard_assignment.rs index f47bd9f9f69..9311df082ca 100644 --- a/chain/epoch-manager/src/shard_assignment.rs +++ b/chain/epoch-manager/src/shard_assignment.rs @@ -14,8 +14,8 @@ use near_primitives::utils::min_heap::{MinHeap, PeekMut}; /// producer will be assigned to a single shard. If there are fewer producers, /// some of them will be assigned to multiple shards. /// -/// Panics if `chunk_producers.len() < min_validators_per_shard` or chunk_producers -/// vector is not sorted in descending order by producer’s stake. +/// Returns error if `chunk_producers.len() < min_validators_per_shard`. +/// Panics if chunk_producers vector is not sorted in descending order by stake. pub fn assign_shards( chunk_producers: Vec, num_shards: NumShards, diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index 01e28c7f3ec..f53725433f8 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -81,7 +81,9 @@ pub fn proposals_to_epoch_info( // is not enough to iterate over chunk validators. // So unfortunately we have to look over all roles to get unselected // proposals. - // TODO: must be simplified. + // TODO: getting unselected proposals must be simpler. For example, + // we can track all roles assign to each validator in some structure + // and then return all validators which don't have any role. let max_validators_for_role = cmp::max( chunk_producers.len(), cmp::max(block_producers.len(), chunk_validators.len()), From 79f0ea7337e2953108c14f8a8b390b972478e4d2 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Thu, 9 May 2024 00:11:14 +0400 Subject: [PATCH 15/20] config --- chain/jsonrpc/jsonrpc-tests/res/genesis_config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json index f2673819468..850645e6e89 100644 --- a/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json +++ b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json @@ -69,5 +69,7 @@ ], "shuffle_shard_assignment_for_chunk_producers": false, "use_production_config": false, + "num_chunk_producer_seats": 100, + "num_chunk_validator_seats": 300, "records": [] -} +} \ No newline at end of file From e5fa9b0e6ebcce225d00b178b206d3f8841315b1 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Mon, 13 May 2024 15:56:58 +0400 Subject: [PATCH 16/20] comments --- chain/epoch-manager/src/shard_assignment.rs | 12 +- .../epoch-manager/src/validator_selection.rs | 329 ++++++++++-------- 2 files changed, 197 insertions(+), 144 deletions(-) diff --git a/chain/epoch-manager/src/shard_assignment.rs b/chain/epoch-manager/src/shard_assignment.rs index 9311df082ca..b42d2b7e2f0 100644 --- a/chain/epoch-manager/src/shard_assignment.rs +++ b/chain/epoch-manager/src/shard_assignment.rs @@ -2,6 +2,11 @@ use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{Balance, NumShards, ShardId}; use near_primitives::utils::min_heap::{MinHeap, PeekMut}; +/// Marker struct to communicate the error where you try to assign validators to shards +/// and there are not enough to even meet the minimum per shard. +#[derive(Debug)] +pub struct NotEnoughValidators; + /// Assign chunk producers (a.k.a. validators) to shards. The i-th element /// of the output corresponds to the validators assigned to the i-th shard. /// @@ -20,12 +25,12 @@ pub fn assign_shards( chunk_producers: Vec, num_shards: NumShards, min_validators_per_shard: usize, -) -> Result>, ()> { +) -> Result>, NotEnoughValidators> { // If there's not enough chunk producers to fill up a single shard there’s // nothing we can do. Return with an error. let num_chunk_producers = chunk_producers.len(); if num_chunk_producers < min_validators_per_shard { - return Err(()); + return Err(NotEnoughValidators); } for (idx, pair) in chunk_producers.windows(2).enumerate() { @@ -147,6 +152,7 @@ impl HasStake for ValidatorStake { #[cfg(test)] mod tests { + use crate::shard_assignment::NotEnoughValidators; use near_primitives::types::{Balance, NumShards}; use std::collections::HashSet; @@ -221,7 +227,7 @@ mod tests { stakes: &[Balance], num_shards: NumShards, min_validators_per_shard: usize, - ) -> Result, ()> { + ) -> Result, NotEnoughValidators> { let chunk_producers = stakes.iter().copied().enumerate().collect(); let assignments = super::assign_shards(chunk_producers, num_shards, min_validators_per_shard)?; diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index f53725433f8..52d1f9299d8 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -14,6 +14,97 @@ use std::cmp::{self, Ordering}; use std::collections::hash_map; use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet}; +/// Helper struct which is a result of proposals processing. +struct ValidatorRoles { + /// Proposals which were not given any role. + unselected_proposals: BinaryHeap, + /// Validators which are assigned to produce chunks. + chunk_producers: Vec, + /// Validators which are assigned to produce blocks. + block_producers: Vec, + /// Validators which are assigned to validate chunks. + chunk_validators: Vec, + /// Stake threshold to become a validator. + threshold: Balance, +} + +/// Helper struct which is a result of assigning chunk producers to shards. +struct ChunkProducersAssignment { + /// List of all validators in the epoch. + /// Note that it doesn't belong here, but in the legacy code it is computed + /// together with chunk producers assignment. + all_validators: Vec, + /// Maps validator account names to local indices throughout the epoch. + validator_to_index: HashMap, + /// Maps chunk producers to shards, where i-th list contains local indices + /// of validators producing chunks for i-th shard. + chunk_producers_settlement: Vec>, +} + +/// Selects validator roles for the given proposals. +fn select_validators_from_proposals( + epoch_config: &EpochConfig, + proposals: HashMap, + next_version: ProtocolVersion, +) -> ValidatorRoles { + let shard_ids: Vec<_> = epoch_config.shard_layout.shard_ids().collect(); + let min_stake_ratio = { + let rational = epoch_config.validator_selection_config.minimum_stake_ratio; + Ratio::new(*rational.numer() as u128, *rational.denom() as u128) + }; + + let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); + let (chunk_producers, cp_stake_threshold) = select_chunk_producers( + &mut chunk_producer_proposals, + epoch_config.validator_selection_config.num_chunk_producer_seats as usize, + min_stake_ratio, + shard_ids.len() as NumShards, + next_version, + ); + + let mut block_producer_proposals = order_proposals(proposals.values().cloned()); + let (block_producers, bp_stake_threshold) = select_block_producers( + &mut block_producer_proposals, + epoch_config.num_block_producer_seats as usize, + min_stake_ratio, + next_version, + ); + + let mut chunk_validator_proposals = order_proposals(proposals.into_values()); + let (chunk_validators, cv_stake_threshold) = select_validators( + &mut chunk_validator_proposals, + epoch_config.validator_selection_config.num_chunk_validator_seats as usize, + min_stake_ratio, + next_version, + ); + + // Note that if there are too few validators and too many shards, + // assigning chunk producers to shards is more aggressive, so it + // is not enough to iterate over chunk validators. + // So unfortunately we have to look over all roles to get unselected + // proposals. + // TODO: getting unselected proposals must be simpler. For example, + // we can track all roles assign to each validator in some structure + // and then return all validators which don't have any role. + let max_validators_for_role = + chunk_producers.len().max(block_producers.len()).max(chunk_validators.len()); + let unselected_proposals = if chunk_producers.len() == max_validators_for_role { + chunk_producer_proposals + } else if block_producers.len() == max_validators_for_role { + block_producer_proposals + } else { + chunk_validator_proposals + }; + let threshold = bp_stake_threshold.min(cp_stake_threshold).min(cv_stake_threshold); + ValidatorRoles { + unselected_proposals, + chunk_producers, + block_producers, + chunk_validators, + threshold, + } +} + /// Select validators for next epoch and generate epoch info pub fn proposals_to_epoch_info( epoch_config: &EpochConfig, @@ -44,67 +135,21 @@ pub fn proposals_to_epoch_info( // Select validators for the next epoch. // Returns unselected proposals, validator lists for all roles and stake // threshold to become a validator. - let (unselected_proposals, chunk_producers, block_producers, chunk_validators, threshold) = - if checked_feature!("stable", StatelessValidationV0, next_version) { - let min_stake_ratio = { - let rational = epoch_config.validator_selection_config.minimum_stake_ratio; - Ratio::new(*rational.numer() as u128, *rational.denom() as u128) - }; - - let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); - let (chunk_producers, cp_stake_threshold) = select_chunk_producers( - &mut chunk_producer_proposals, - epoch_config.validator_selection_config.num_chunk_producer_seats as usize, - min_stake_ratio, - shard_ids.len() as NumShards, - next_version, - ); - - let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let (block_producers, bp_stake_threshold) = select_block_producers( - &mut block_producer_proposals, - epoch_config.num_block_producer_seats as usize, - min_stake_ratio, - next_version, - ); - - let mut chunk_validator_proposals = order_proposals(proposals.into_values()); - let (chunk_validators, cv_stake_threshold) = select_validators( - &mut chunk_validator_proposals, - epoch_config.validator_selection_config.num_chunk_validator_seats as usize, - min_stake_ratio, - next_version, - ); - - // Note that if there are too few validators and too many shards, - // assigning chunk producers to shards is more aggressive, so it - // is not enough to iterate over chunk validators. - // So unfortunately we have to look over all roles to get unselected - // proposals. - // TODO: getting unselected proposals must be simpler. For example, - // we can track all roles assign to each validator in some structure - // and then return all validators which don't have any role. - let max_validators_for_role = cmp::max( - chunk_producers.len(), - cmp::max(block_producers.len(), chunk_validators.len()), - ); - let unselected_proposals = if chunk_producers.len() == max_validators_for_role { - chunk_producer_proposals - } else if block_producers.len() == max_validators_for_role { - block_producer_proposals - } else { - chunk_validator_proposals - }; - let threshold = - cmp::min(bp_stake_threshold, cmp::min(cp_stake_threshold, cv_stake_threshold)); - (unselected_proposals, chunk_producers, block_producers, chunk_validators, threshold) - } else { - old_validator_selection::select_validators_from_proposals( - epoch_config, - proposals, - next_version, - ) - }; + let ValidatorRoles { + unselected_proposals, + chunk_producers, + block_producers, + chunk_validators, + threshold, + } = if checked_feature!("stable", StatelessValidationV0, next_version) { + select_validators_from_proposals(epoch_config, proposals, next_version) + } else { + old_validator_selection::select_validators_from_proposals( + epoch_config, + proposals, + next_version, + ) + }; // Add kickouts for validators which fell out of validator set. // Used for querying epoch info by RPC. @@ -126,68 +171,68 @@ pub fn proposals_to_epoch_info( // Assign chunk producers to shards using local validator indices. // TODO: this happens together because assigment logic is more subtle for // older protocol versions, consider decoupling it. - let (all_validators, validator_to_index, mut chunk_producers_settlement) = - if checked_feature!("stable", StatelessValidationV0, next_version) { - // Construct local validator indices. - // Note that if there are too few validators and too many shards, - // assigning chunk producers to shards is more aggressive, so it - // is not enough to iterate over chunk validators. - // We assign local indices in the order of roles priority and then - // in decreasing order of stake. - let max_validators_for_role = cmp::max( - chunk_producers.len(), - cmp::max(block_producers.len(), chunk_validators.len()), - ); - let mut all_validators: Vec = - Vec::with_capacity(max_validators_for_role); - let mut validator_to_index = HashMap::new(); - for validators_for_role in - [&chunk_producers, &block_producers, &chunk_validators].iter() - { - for validator in validators_for_role.iter() { - let account_id = validator.account_id().clone(); - if validator_to_index.contains_key(&account_id) { - continue; - } - let id = all_validators.len() as ValidatorId; - validator_to_index.insert(account_id, id); - all_validators.push(validator.clone()); + let ChunkProducersAssignment { + all_validators, + validator_to_index, + mut chunk_producers_settlement, + } = if checked_feature!("stable", StatelessValidationV0, next_version) { + // Construct local validator indices. + // Note that if there are too few validators and too many shards, + // assigning chunk producers to shards is more aggressive, so it + // is not enough to iterate over chunk validators. + // We assign local indices in the order of roles priority and then + // in decreasing order of stake. + let max_validators_for_role = cmp::max( + chunk_producers.len(), + cmp::max(block_producers.len(), chunk_validators.len()), + ); + let mut all_validators: Vec = Vec::with_capacity(max_validators_for_role); + let mut validator_to_index = HashMap::new(); + for validators_for_role in [&chunk_producers, &block_producers, &chunk_validators].iter() { + for validator in validators_for_role.iter() { + let account_id = validator.account_id().clone(); + if validator_to_index.contains_key(&account_id) { + continue; } + let id = all_validators.len() as ValidatorId; + validator_to_index.insert(account_id, id); + all_validators.push(validator.clone()); } + } - // Assign chunk producers to shards. - let num_chunk_producers = chunk_producers.len(); - let minimum_validators_per_shard = - epoch_config.validator_selection_config.minimum_validators_per_shard as usize; - let shard_assignment = assign_shards( - chunk_producers, - shard_ids.len() as NumShards, - minimum_validators_per_shard, - ) - .map_err(|_| EpochError::NotEnoughValidators { - num_validators: num_chunk_producers as u64, - num_shards: shard_ids.len() as NumShards, - })?; - - let chunk_producers_settlement = shard_assignment - .into_iter() - .map(|vs| vs.into_iter().map(|v| validator_to_index[v.account_id()]).collect()) - .collect(); - - (all_validators, validator_to_index, chunk_producers_settlement) - } else if checked_feature!("stable", ChunkOnlyProducers, next_version) { - old_validator_selection::assign_chunk_producers_to_shards_chunk_only( - epoch_config, - chunk_producers, - &block_producers, - )? - } else { - old_validator_selection::assign_chunk_producers_to_shards( - epoch_config, - chunk_producers, - &block_producers, - )? - }; + // Assign chunk producers to shards. + let num_chunk_producers = chunk_producers.len(); + let minimum_validators_per_shard = + epoch_config.validator_selection_config.minimum_validators_per_shard as usize; + let shard_assignment = assign_shards( + chunk_producers, + shard_ids.len() as NumShards, + minimum_validators_per_shard, + ) + .map_err(|_| EpochError::NotEnoughValidators { + num_validators: num_chunk_producers as u64, + num_shards: shard_ids.len() as NumShards, + })?; + + let chunk_producers_settlement = shard_assignment + .into_iter() + .map(|vs| vs.into_iter().map(|v| validator_to_index[v.account_id()]).collect()) + .collect(); + + ChunkProducersAssignment { all_validators, validator_to_index, chunk_producers_settlement } + } else if checked_feature!("stable", ChunkOnlyProducers, next_version) { + old_validator_selection::assign_chunk_producers_to_shards_chunk_only( + epoch_config, + chunk_producers, + &block_producers, + )? + } else { + old_validator_selection::assign_chunk_producers_to_shards( + epoch_config, + chunk_producers, + &block_producers, + )? + }; if epoch_config.validator_selection_config.shuffle_shard_assignment_for_chunk_producers { chunk_producers_settlement @@ -378,20 +423,16 @@ impl Ord for OrderedValidatorStake { } } +/// Helpers to generate new epoch info for older protocol versions. mod old_validator_selection { use super::*; + /// Selects validator roles for the given proposals. pub(crate) fn select_validators_from_proposals( epoch_config: &EpochConfig, proposals: HashMap, next_version: ProtocolVersion, - ) -> ( - BinaryHeap, - Vec, - Vec, - Vec, - Balance, - ) { + ) -> ValidatorRoles { let max_bp_selected = epoch_config.num_block_producer_seats as usize; let min_stake_ratio = { let rational = epoch_config.validator_selection_config.minimum_stake_ratio; @@ -428,23 +469,22 @@ mod old_validator_selection { // is the smaller of the two thresholds let threshold = cmp::min(bp_stake_threshold, cp_stake_threshold); - ( - chunk_producer_proposals, + ValidatorRoles { + unselected_proposals: chunk_producer_proposals, chunk_producers, block_producers, - vec![], // chunk validators are not used for older protocol versions + chunk_validators: vec![], // chunk validators are not used for older protocol versions threshold, - ) + } } + /// Assigns chunk producers to shards for the given proposals when chunk + /// only producers were enabled, but before stateless validation. pub(crate) fn assign_chunk_producers_to_shards_chunk_only( epoch_config: &EpochConfig, chunk_producers: Vec, block_producers: &[ValidatorStake], - ) -> Result< - (Vec, HashMap, Vec>), - EpochError, - > { + ) -> Result { let num_chunk_producers = chunk_producers.len(); let mut all_validators: Vec = Vec::with_capacity(num_chunk_producers); let mut validator_to_index = HashMap::new(); @@ -494,17 +534,20 @@ mod old_validator_selection { } } - Ok((all_validators, validator_to_index, chunk_producers_settlement)) + Ok(ChunkProducersAssignment { + all_validators, + validator_to_index, + chunk_producers_settlement, + }) } + /// Assigns chunk producers to shards given chunk and block producers before + /// chunk only producers were enabled. pub(crate) fn assign_chunk_producers_to_shards( epoch_config: &EpochConfig, chunk_producers: Vec, block_producers: &[ValidatorStake], - ) -> Result< - (Vec, HashMap, Vec>), - EpochError, - > { + ) -> Result { let mut all_validators: Vec = Vec::with_capacity(chunk_producers.len()); let mut validator_to_index = HashMap::new(); let mut block_producers_settlement = Vec::with_capacity(block_producers.len()); @@ -544,7 +587,11 @@ mod old_validator_selection { }) .collect(); - Ok((all_validators, validator_to_index, chunk_producers_settlement)) + Ok(ChunkProducersAssignment { + all_validators, + validator_to_index, + chunk_producers_settlement, + }) } } From ffb08e1076b807de9e9d4f8dd4fd710771d1c735 Mon Sep 17 00:00:00 2001 From: Longarithm Date: Mon, 13 May 2024 16:23:45 +0400 Subject: [PATCH 17/20] fix derived setup --- core/chain-configs/src/test_genesis.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/chain-configs/src/test_genesis.rs b/core/chain-configs/src/test_genesis.rs index b97f2eb8bc5..5ba05bcde91 100644 --- a/core/chain-configs/src/test_genesis.rs +++ b/core/chain-configs/src/test_genesis.rs @@ -57,6 +57,8 @@ enum ValidatorsSpec { validators: Vec, num_block_producer_seats: NumSeats, num_chunk_only_producer_seats: NumSeats, + num_chunk_producer_seats: NumSeats, + num_chunk_validator_seats: NumSeats, }, } @@ -474,6 +476,8 @@ impl TestGenesisBuilder { max_inflation_rate: Rational32::new(1, 1), protocol_upgrade_stake_threshold: Rational32::new(8, 10), use_production_config: false, + num_chunk_producer_seats: derived_validator_setup.num_chunk_producer_seats, + num_chunk_validator_seats: derived_validator_setup.num_chunk_validator_seats, }; Genesis { @@ -487,6 +491,8 @@ struct DerivedValidatorSetup { validators: Vec, num_block_producer_seats: NumSeats, num_chunk_only_producer_seats: NumSeats, + num_chunk_producer_seats: NumSeats, + num_chunk_validator_seats: NumSeats, minimum_stake_ratio: Rational32, } @@ -523,6 +529,8 @@ fn derive_validator_setup(specs: ValidatorsSpec) -> DerivedValidatorSetup { validators, num_block_producer_seats, num_chunk_only_producer_seats, + num_chunk_producer_seats: num_block_producer_seats, + num_chunk_validator_seats: num_block_producer_seats + num_chunk_only_producer_seats, minimum_stake_ratio, } } @@ -530,10 +538,14 @@ fn derive_validator_setup(specs: ValidatorsSpec) -> DerivedValidatorSetup { validators, num_block_producer_seats, num_chunk_only_producer_seats, + num_chunk_producer_seats, + num_chunk_validator_seats, } => DerivedValidatorSetup { validators, num_block_producer_seats, num_chunk_only_producer_seats, + num_chunk_producer_seats, + num_chunk_validator_seats, minimum_stake_ratio: Rational32::new(160, 1000000), }, } From b4f66600964dff187edc6288225ab387da84a09d Mon Sep 17 00:00:00 2001 From: Longarithm Date: Mon, 13 May 2024 16:25:33 +0400 Subject: [PATCH 18/20] fix 2 --- core/chain-configs/src/test_genesis.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/chain-configs/src/test_genesis.rs b/core/chain-configs/src/test_genesis.rs index 5ba05bcde91..40e3741aa46 100644 --- a/core/chain-configs/src/test_genesis.rs +++ b/core/chain-configs/src/test_genesis.rs @@ -190,6 +190,8 @@ impl TestGenesisBuilder { validators, num_block_producer_seats, num_chunk_only_producer_seats, + num_chunk_producer_seats: num_block_producer_seats, + num_chunk_validator_seats: num_block_producer_seats + num_chunk_only_producer_seats, }); self } From ba3d8e21bed7a994322bcb9d7c8e9ef1ef4d698e Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 15 May 2024 17:27:26 +0400 Subject: [PATCH 19/20] feedback --- .../epoch-manager/src/validator_selection.rs | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index 52d1f9299d8..aef75c5b847 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -1,4 +1,5 @@ use crate::shard_assignment::assign_shards; +use itertools::Itertools; use near_primitives::checked_feature; use near_primitives::epoch_manager::epoch_info::EpochInfo; use near_primitives::epoch_manager::{EpochConfig, RngSeed}; @@ -53,48 +54,44 @@ fn select_validators_from_proposals( Ratio::new(*rational.numer() as u128, *rational.denom() as u128) }; - let mut chunk_producer_proposals = order_proposals(proposals.values().cloned()); - let (chunk_producers, cp_stake_threshold) = select_chunk_producers( - &mut chunk_producer_proposals, + let chunk_producer_proposals = order_proposals(proposals.values().cloned()); + let (chunk_producers, _, cp_stake_threshold) = select_chunk_producers( + chunk_producer_proposals, epoch_config.validator_selection_config.num_chunk_producer_seats as usize, min_stake_ratio, shard_ids.len() as NumShards, next_version, ); - let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let (block_producers, bp_stake_threshold) = select_block_producers( - &mut block_producer_proposals, + let block_producer_proposals = order_proposals(proposals.values().cloned()); + let (block_producers, _, bp_stake_threshold) = select_block_producers( + block_producer_proposals, epoch_config.num_block_producer_seats as usize, min_stake_ratio, next_version, ); - let mut chunk_validator_proposals = order_proposals(proposals.into_values()); - let (chunk_validators, cv_stake_threshold) = select_validators( - &mut chunk_validator_proposals, + let chunk_validator_proposals = order_proposals(proposals.values().cloned()); + let (chunk_validators, _, cv_stake_threshold) = select_validators( + chunk_validator_proposals, epoch_config.validator_selection_config.num_chunk_validator_seats as usize, min_stake_ratio, next_version, ); - // Note that if there are too few validators and too many shards, - // assigning chunk producers to shards is more aggressive, so it - // is not enough to iterate over chunk validators. - // So unfortunately we have to look over all roles to get unselected - // proposals. - // TODO: getting unselected proposals must be simpler. For example, - // we can track all roles assign to each validator in some structure - // and then return all validators which don't have any role. - let max_validators_for_role = - chunk_producers.len().max(block_producers.len()).max(chunk_validators.len()); - let unselected_proposals = if chunk_producers.len() == max_validators_for_role { - chunk_producer_proposals - } else if block_producers.len() == max_validators_for_role { - block_producer_proposals - } else { - chunk_validator_proposals - }; + let mut unselected_proposals = BinaryHeap::new(); + for proposal in order_proposals(proposals.into_values()) { + if chunk_producers.contains(&proposal.0) { + continue; + } + if block_producers.contains(&proposal.0) { + continue; + } + if chunk_validators.contains(&proposal.0) { + continue; + } + unselected_proposals.push(proposal); + } let threshold = bp_stake_threshold.min(cp_stake_threshold).min(cv_stake_threshold); ValidatorRoles { unselected_proposals, @@ -328,21 +325,21 @@ fn order_proposals>( } fn select_block_producers( - block_producer_proposals: &mut BinaryHeap, + block_producer_proposals: BinaryHeap, max_num_selected: usize, min_stake_ratio: Ratio, protocol_version: ProtocolVersion, -) -> (Vec, Balance) { +) -> (Vec, BinaryHeap, Balance) { select_validators(block_producer_proposals, max_num_selected, min_stake_ratio, protocol_version) } fn select_chunk_producers( - all_proposals: &mut BinaryHeap, + all_proposals: BinaryHeap, max_num_selected: usize, min_stake_ratio: Ratio, num_shards: u64, protocol_version: ProtocolVersion, -) -> (Vec, Balance) { +) -> (Vec, BinaryHeap, Balance) { select_validators( all_proposals, max_num_selected, @@ -356,11 +353,11 @@ fn select_chunk_producers( // slots are filled, or the stake ratio falls too low, the threshold stake to be included // is also returned. fn select_validators( - proposals: &mut BinaryHeap, + mut proposals: BinaryHeap, max_number_selected: usize, min_stake_ratio: Ratio, protocol_version: ProtocolVersion, -) -> (Vec, Balance) { +) -> (Vec, BinaryHeap, Balance) { let mut total_stake = 0; let n = cmp::min(max_number_selected, proposals.len()); let mut validators = Vec::with_capacity(n); @@ -381,7 +378,7 @@ fn select_validators( // all slots were filled, so the threshold stake is 1 more than the current // smallest stake let threshold = validators.last().unwrap().stake() + 1; - (validators, threshold) + (validators, proposals, threshold) } else { // the stake ratio condition prevented all slots from being filled, // or there were fewer proposals than available slots, @@ -398,7 +395,7 @@ fn select_validators( } else { (min_stake_ratio * Ratio::new(total_stake, 1)).ceil().to_integer() }; - (validators, threshold) + (validators, proposals, threshold) } } @@ -439,30 +436,31 @@ mod old_validator_selection { Ratio::new(*rational.numer() as u128, *rational.denom() as u128) }; - let mut block_producer_proposals = order_proposals(proposals.values().cloned()); - let (block_producers, bp_stake_threshold) = select_block_producers( - &mut block_producer_proposals, + let block_producer_proposals = order_proposals(proposals.values().cloned()); + let (block_producers, not_block_producers, bp_stake_threshold) = select_block_producers( + block_producer_proposals, max_bp_selected, min_stake_ratio, next_version, ); let (chunk_producer_proposals, chunk_producers, cp_stake_threshold) = if checked_feature!("stable", ChunkOnlyProducers, next_version) { - let mut chunk_producer_proposals = order_proposals(proposals.into_values()); + let chunk_producer_proposals = order_proposals(proposals.into_values()); let max_cp_selected = max_bp_selected + (epoch_config.validator_selection_config.num_chunk_only_producer_seats as usize); let num_shards = epoch_config.shard_layout.shard_ids().count() as NumShards; - let (chunk_producers, cp_stake_threshold) = select_chunk_producers( - &mut chunk_producer_proposals, - max_cp_selected, - min_stake_ratio, - num_shards, - next_version, - ); - (chunk_producer_proposals, chunk_producers, cp_stake_threshold) + let (chunk_producers, not_chunk_producers, cp_stake_threshold) = + select_chunk_producers( + chunk_producer_proposals, + max_cp_selected, + min_stake_ratio, + num_shards, + next_version, + ); + (not_chunk_producers, chunk_producers, cp_stake_threshold) } else { - (block_producer_proposals, block_producers.clone(), bp_stake_threshold) + (not_block_producers, block_producers.clone(), bp_stake_threshold) }; // since block producer proposals could become chunk producers, their actual stake threshold From 1cb511a2473acbce161800d725beb06c82a9445a Mon Sep 17 00:00:00 2001 From: Longarithm Date: Wed, 15 May 2024 18:04:44 +0400 Subject: [PATCH 20/20] feedback --- chain/epoch-manager/src/validator_selection.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index aef75c5b847..ae9effcf2d1 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -1,5 +1,4 @@ use crate::shard_assignment::assign_shards; -use itertools::Itertools; use near_primitives::checked_feature; use near_primitives::epoch_manager::epoch_info::EpochInfo; use near_primitives::epoch_manager::{EpochConfig, RngSeed};