Skip to content

Commit

Permalink
feat: implement chunk validator assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
mooori committed Oct 20, 2023
1 parent 2e9046d commit b243fb4
Show file tree
Hide file tree
Showing 10 changed files with 466 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions chain/chain/src/test_utils/kv_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ impl EpochManagerAdapter for MockEpochManager {
1,
1,
RngSeed::default(),
Default::default(),
)))
}

Expand Down
5 changes: 5 additions & 0 deletions chain/epoch-manager/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ mod old_validator_selection {
use near_primitives::types::{
AccountId, Balance, NumSeats, ValidatorId, ValidatorKickoutReason,
};
use near_primitives::validator_mandates::ValidatorMandates;
use near_primitives::version::ProtocolVersion;
use rand::{RngCore, SeedableRng};
use rand_hc::Hc128Rng;
Expand Down Expand Up @@ -248,6 +249,9 @@ mod old_validator_selection {
.map(|(index, s)| (s.account_id().clone(), index as ValidatorId))
.collect::<HashMap<_, _>>();

// Old validator selection is not aware of chunk validator mandates.
let validator_mandates: ValidatorMandates = Default::default();

Ok(EpochInfo::new(
prev_epoch_info.epoch_height() + 1,
final_proposals,
Expand All @@ -264,6 +268,7 @@ mod old_validator_selection {
threshold,
next_version,
rng_seed,
validator_mandates,
))
}

Expand Down
12 changes: 11 additions & 1 deletion chain/epoch-manager/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use near_primitives::types::{
ValidatorId, ValidatorKickoutReason,
};
use near_primitives::utils::get_num_seats_per_shard;
use near_primitives::validator_mandates::{ValidatorMandates, ValidatorMandatesConfig};
use near_primitives::version::PROTOCOL_VERSION;
use near_store::test_utils::create_test_store;

Expand Down Expand Up @@ -104,9 +105,17 @@ pub fn epoch_info_with_num_seats(
})
.collect()
};
let all_validators = account_to_validators(accounts);
// TODO determine required stake per mandate instead of reusing seat price.
// TODO determine `min_mandates_per_shard`
let num_shards = chunk_producers_settlement.len();
let min_mandates_per_shard = 0;
let validator_mandates_config =
ValidatorMandatesConfig::new(seat_price, min_mandates_per_shard, num_shards);
let validator_mandates = ValidatorMandates::new(validator_mandates_config, &all_validators);
EpochInfo::new(
epoch_height,
account_to_validators(accounts),
all_validators,
validator_to_index,
block_producers_settlement,
chunk_producers_settlement,
Expand All @@ -120,6 +129,7 @@ pub fn epoch_info_with_num_seats(
seat_price,
PROTOCOL_VERSION,
TEST_SEED,
validator_mandates,
)
}

Expand Down
59 changes: 59 additions & 0 deletions chain/epoch-manager/src/validator_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use near_primitives::types::validator_stake::ValidatorStake;
use near_primitives::types::{
AccountId, Balance, ProtocolVersion, ValidatorId, ValidatorKickoutReason,
};
use near_primitives::validator_mandates::{ValidatorMandates, ValidatorMandatesConfig};
use num_rational::Ratio;
use std::cmp::{self, Ordering};
use std::collections::hash_map;
Expand Down Expand Up @@ -96,6 +97,7 @@ 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<ValidatorStake> = Vec::with_capacity(num_chunk_producers);
let mut validator_to_index = HashMap::new();
let mut block_producers_settlement = Vec::with_capacity(block_producers.len());
Expand Down Expand Up @@ -170,6 +172,16 @@ pub fn proposals_to_epoch_info(
.collect()
};

// We can use `all_validators` to construct mandates Since a validator's position in
// `all_validators` corresponds to its `ValidatorId`
// TODO determine required stake per mandate instead of reusing seat price.
// TODO determine `min_mandates_per_shard`
// TODO pre chunk-validation, just pass empty vec instead of `all_validators` to avoid costs?
let min_mandates_per_shard = 0;
let validator_mandates_config =
ValidatorMandatesConfig::new(threshold, min_mandates_per_shard, num_shards as usize);
let validator_mandates = ValidatorMandates::new(validator_mandates_config, &all_validators);

let fishermen_to_index = fishermen
.iter()
.enumerate()
Expand All @@ -192,6 +204,7 @@ pub fn proposals_to_epoch_info(
threshold,
next_version,
rng_seed,
validator_mandates,
))
}

Expand Down Expand Up @@ -619,6 +632,52 @@ mod tests {
}
}

/// This test only verifies that chunk validator mandates are correctly wired up with
/// `EpochInfo`. The internals of mandate assignment are tested in the module containing
/// [`ValidatorMandates`].
#[test]
fn test_chunk_validators_sampling() {
// When there is 1 CP per shard, they are chosen 100% of the time.
let num_shards = 4;
let epoch_config = create_epoch_config(
num_shards,
2 * num_shards,
0,
ValidatorSelectionConfig {
num_chunk_only_producer_seats: 0,
minimum_validators_per_shard: 1,
minimum_stake_ratio: Ratio::new(160, 1_000_000),
},
);
let prev_epoch_height = 7;
let prev_epoch_info = create_prev_epoch_info(prev_epoch_height, &["test1", "test2"], &[]);
let proposals =
create_proposals(&[("test1", 15), ("test2", 9), ("test3", 5), ("test4", 3)]);

let epoch_info = proposals_to_epoch_info(
&epoch_config,
[0; 32],
&prev_epoch_info,
proposals,
Default::default(),
Default::default(),
0,
PROTOCOL_VERSION,
PROTOCOL_VERSION,
)
.unwrap();

// Given `epoch_info` and `proposals` above, the sample at a given height is deterministic.
let height = 42;
let expected_assignments: Vec<HashMap<ValidatorId, u16>> = vec![
HashMap::from([(0, 1), (1, 5), (2, 1), (3, 1)]),
HashMap::from([(0, 6), (1, 1), (2, 1)]),
HashMap::from([(0, 5), (1, 2), (2, 1)]),
HashMap::from([(0, 3), (1, 1), (2, 2), (3, 2)]),
];
assert_eq!(epoch_info.sample_chunk_validators(height), expected_assignments);
}

#[test]
fn test_validator_assignment_ratio_condition() {
// There are more seats than proposals, however the
Expand Down
1 change: 1 addition & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ num-rational.workspace = true
once_cell.workspace = true
primitive-types.workspace = true
rand.workspace = true
rand_chacha.workspace = true
reed-solomon-erasure.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand Down
Loading

0 comments on commit b243fb4

Please sign in to comment.