2018-10-10 05:09:00 +00:00
|
|
|
use super::honey_badger_split::SplitExt;
|
2018-10-19 15:36:55 +00:00
|
|
|
use super::types::{
|
|
|
|
ShardAndCommittee,
|
|
|
|
ValidatorRecord,
|
|
|
|
ValidatorStatus,
|
|
|
|
ChainConfig,
|
|
|
|
};
|
2018-10-03 05:26:41 +00:00
|
|
|
use super::TransitionError;
|
|
|
|
use super::shuffle;
|
2018-10-03 05:41:04 +00:00
|
|
|
use std::cmp::min;
|
2018-10-03 05:26:41 +00:00
|
|
|
|
2018-10-05 04:51:16 +00:00
|
|
|
type DelegatedCycle = Vec<Vec<ShardAndCommittee>>;
|
2018-10-03 05:26:41 +00:00
|
|
|
|
2018-10-19 15:36:55 +00:00
|
|
|
/// Returns the indicies of each active validator in a given vec of validators.
|
|
|
|
fn active_validator_indicies(validators: &[ValidatorRecord])
|
2018-10-03 05:26:41 +00:00
|
|
|
-> Vec<usize>
|
|
|
|
{
|
|
|
|
validators.iter()
|
|
|
|
.enumerate()
|
|
|
|
.filter_map(|(i, validator)| {
|
2018-10-19 15:36:55 +00:00
|
|
|
match validator.status {
|
|
|
|
x if x == ValidatorStatus::Active as u8 => Some(i),
|
|
|
|
_ => None
|
2018-10-03 05:26:41 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2018-10-10 03:04:42 +00:00
|
|
|
|
|
|
|
/// Delegates active validators into slots for a given cycle, given a random seed.
|
|
|
|
/// Returns a vector or ShardAndComitte vectors representing the shards and committiees for
|
|
|
|
/// each slot.
|
|
|
|
/// References get_new_shuffling (ethereum 2.1 specification)
|
2018-10-03 05:26:41 +00:00
|
|
|
pub fn delegate_validators(
|
|
|
|
seed: &[u8],
|
2018-10-05 04:51:16 +00:00
|
|
|
validators: &[ValidatorRecord],
|
|
|
|
crosslinking_shard_start: u16,
|
2018-10-03 05:26:41 +00:00
|
|
|
config: &ChainConfig)
|
|
|
|
-> Result<DelegatedCycle, TransitionError>
|
|
|
|
{
|
|
|
|
let shuffled_validator_indices = {
|
2018-10-19 15:36:55 +00:00
|
|
|
let mut validator_indices = active_validator_indicies(validators);
|
2018-10-03 05:26:41 +00:00
|
|
|
match shuffle(seed, validator_indices) {
|
|
|
|
Ok(shuffled) => shuffled,
|
|
|
|
_ => return Err(TransitionError::InvalidInput(
|
|
|
|
String::from("Shuffle list length exceed.")))
|
|
|
|
}
|
|
|
|
};
|
2018-10-05 04:51:16 +00:00
|
|
|
let shard_indices: Vec<usize> = (0_usize..config.shard_count as usize).into_iter().collect();
|
|
|
|
let crosslinking_shard_start = crosslinking_shard_start as usize;
|
2018-10-03 05:26:41 +00:00
|
|
|
let cycle_length = config.cycle_length as usize;
|
|
|
|
let min_committee_size = config.min_committee_size as usize;
|
|
|
|
generate_cycle(
|
|
|
|
&shuffled_validator_indices,
|
|
|
|
&shard_indices,
|
2018-10-05 04:51:16 +00:00
|
|
|
crosslinking_shard_start,
|
|
|
|
cycle_length,
|
|
|
|
min_committee_size)
|
2018-10-03 05:26:41 +00:00
|
|
|
}
|
|
|
|
|
2018-10-10 03:04:42 +00:00
|
|
|
/// Given the validator list, delegates the validators into slots and comittees for a given cycle.
|
2018-10-03 05:26:41 +00:00
|
|
|
fn generate_cycle(
|
2018-10-05 04:51:16 +00:00
|
|
|
validator_indices: &[usize],
|
|
|
|
shard_indices: &[usize],
|
|
|
|
crosslinking_shard_start: usize,
|
|
|
|
cycle_length: usize,
|
|
|
|
min_committee_size: usize)
|
2018-10-03 05:26:41 +00:00
|
|
|
-> Result<DelegatedCycle, TransitionError>
|
|
|
|
{
|
2018-10-03 08:47:18 +00:00
|
|
|
|
2018-10-03 05:26:41 +00:00
|
|
|
let validator_count = validator_indices.len();
|
|
|
|
let shard_count = shard_indices.len();
|
|
|
|
|
2018-10-03 08:47:18 +00:00
|
|
|
if shard_count / cycle_length == 0 {
|
|
|
|
return Err(TransitionError::InvalidInput(String::from("Number of
|
|
|
|
shards needs to be greater than
|
|
|
|
cycle length")));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-10-03 05:26:41 +00:00
|
|
|
let (committees_per_slot, slots_per_committee) = {
|
|
|
|
if validator_count >= cycle_length * min_committee_size {
|
2018-10-03 05:41:04 +00:00
|
|
|
let committees_per_slot = min(validator_count / cycle_length /
|
|
|
|
(min_committee_size * 2) + 1, shard_count /
|
|
|
|
cycle_length);
|
2018-10-03 05:26:41 +00:00
|
|
|
let slots_per_committee = 1;
|
|
|
|
(committees_per_slot, slots_per_committee)
|
|
|
|
} else {
|
|
|
|
let committees_per_slot = 1;
|
|
|
|
let mut slots_per_committee = 1;
|
|
|
|
while (validator_count * slots_per_committee < cycle_length * min_committee_size) &
|
2018-10-05 04:51:16 +00:00
|
|
|
(slots_per_committee < cycle_length) {
|
|
|
|
slots_per_committee *= 2;
|
2018-10-03 05:26:41 +00:00
|
|
|
}
|
|
|
|
(committees_per_slot, slots_per_committee)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-10-05 04:51:16 +00:00
|
|
|
let cycle = validator_indices.honey_badger_split(cycle_length)
|
2018-10-03 05:26:41 +00:00
|
|
|
.enumerate()
|
|
|
|
.map(|(i, slot_indices)| {
|
|
|
|
let shard_id_start = crosslinking_shard_start + i * committees_per_slot / slots_per_committee;
|
2018-10-05 04:51:16 +00:00
|
|
|
slot_indices.honey_badger_split(committees_per_slot)
|
2018-10-03 05:26:41 +00:00
|
|
|
.enumerate()
|
|
|
|
.map(|(j, shard_indices)| {
|
2018-10-05 04:51:16 +00:00
|
|
|
ShardAndCommittee{
|
2018-10-03 05:26:41 +00:00
|
|
|
shard_id: ((shard_id_start + j) % shard_count) as u16,
|
|
|
|
committee: shard_indices.to_vec(),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Ok(cycle)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
fn generate_cycle_helper(
|
|
|
|
validator_count: &usize,
|
|
|
|
shard_count: &usize,
|
2018-10-05 04:53:21 +00:00
|
|
|
crosslinking_shard_start: usize,
|
|
|
|
cycle_length: usize,
|
|
|
|
min_committee_size: usize)
|
2018-10-03 05:26:41 +00:00
|
|
|
-> (Vec<usize>, Vec<usize>, Result<DelegatedCycle, TransitionError>)
|
|
|
|
{
|
2018-10-05 04:53:21 +00:00
|
|
|
let validator_indices: Vec<usize> = (0_usize..*validator_count).into_iter().collect();
|
|
|
|
let shard_indices: Vec<usize> = (0_usize..*shard_count).into_iter().collect();
|
2018-10-03 05:26:41 +00:00
|
|
|
let result = generate_cycle(
|
|
|
|
&validator_indices,
|
|
|
|
&shard_indices,
|
2018-10-05 04:53:21 +00:00
|
|
|
crosslinking_shard_start,
|
|
|
|
cycle_length,
|
|
|
|
min_committee_size);
|
2018-10-03 05:26:41 +00:00
|
|
|
(validator_indices, shard_indices, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
fn print_cycle(cycle: &DelegatedCycle) {
|
|
|
|
cycle.iter()
|
|
|
|
.enumerate()
|
|
|
|
.for_each(|(i, slot)| {
|
|
|
|
println!("slot {:?}", &i);
|
|
|
|
slot.iter()
|
|
|
|
.enumerate()
|
|
|
|
.for_each(|(i, sac)| {
|
|
|
|
println!("#{:?}\tshard_id={}\tcommittee.len()={}",
|
|
|
|
&i, &sac.shard_id, &sac.committee.len())
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flatten_validators(cycle: &DelegatedCycle)
|
|
|
|
-> Vec<usize>
|
|
|
|
{
|
|
|
|
let mut flattened = vec![];
|
|
|
|
for slot in cycle.iter() {
|
|
|
|
for sac in slot.iter() {
|
|
|
|
for validator in sac.committee.iter() {
|
|
|
|
flattened.push(*validator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
flattened
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flatten_and_dedup_shards(cycle: &DelegatedCycle)
|
|
|
|
-> Vec<usize>
|
|
|
|
{
|
|
|
|
let mut flattened = vec![];
|
|
|
|
for slot in cycle.iter() {
|
|
|
|
for sac in slot.iter() {
|
|
|
|
flattened.push(sac.shard_id as usize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
flattened.dedup();
|
|
|
|
flattened
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flatten_shards_in_slots(cycle: &DelegatedCycle)
|
|
|
|
-> Vec<Vec<usize>>
|
|
|
|
{
|
|
|
|
let mut shards_in_slots: Vec<Vec<usize>> = vec![];
|
|
|
|
for slot in cycle.iter() {
|
|
|
|
let mut shards: Vec<usize> = vec![];
|
|
|
|
for sac in slot.iter() {
|
|
|
|
shards.push(sac.shard_id as usize);
|
|
|
|
}
|
|
|
|
shards_in_slots.push(shards);
|
|
|
|
}
|
|
|
|
shards_in_slots
|
|
|
|
}
|
|
|
|
|
2018-10-04 03:21:16 +00:00
|
|
|
// TODO: Improve these tests to check committee lengths
|
2018-10-03 05:26:41 +00:00
|
|
|
#[test]
|
|
|
|
fn test_generate_cycle() {
|
|
|
|
let validator_count: usize = 100;
|
2018-10-04 03:21:16 +00:00
|
|
|
let shard_count: usize = 20;
|
2018-10-03 05:26:41 +00:00
|
|
|
let crosslinking_shard_start: usize = 0;
|
|
|
|
let cycle_length: usize = 20;
|
|
|
|
let min_committee_size: usize = 10;
|
|
|
|
let (validators, shards, result) = generate_cycle_helper(
|
|
|
|
&validator_count,
|
|
|
|
&shard_count,
|
2018-10-05 04:53:21 +00:00
|
|
|
crosslinking_shard_start,
|
|
|
|
cycle_length,
|
|
|
|
min_committee_size);
|
2018-10-03 05:26:41 +00:00
|
|
|
let cycle = result.unwrap();
|
|
|
|
|
|
|
|
let assigned_validators = flatten_validators(&cycle);
|
|
|
|
let assigned_shards = flatten_and_dedup_shards(&cycle);
|
|
|
|
let shards_in_slots = flatten_shards_in_slots(&cycle);
|
2018-10-04 03:21:16 +00:00
|
|
|
let expected_shards = shards.get(0..10).unwrap();
|
2018-10-03 05:26:41 +00:00
|
|
|
assert_eq!(assigned_validators, validators, "Validator assignment incorrect");
|
2018-10-04 03:21:16 +00:00
|
|
|
assert_eq!(assigned_shards, expected_shards, "Shard assignment incorrect");
|
2018-10-03 05:26:41 +00:00
|
|
|
|
2018-10-03 08:47:18 +00:00
|
|
|
let expected_shards_in_slots: Vec<Vec<usize>> = vec![
|
|
|
|
vec![0], vec![0], // Each line is 2 slots..
|
|
|
|
vec![1], vec![1],
|
|
|
|
vec![2], vec![2],
|
|
|
|
vec![3], vec![3],
|
|
|
|
vec![4], vec![4],
|
|
|
|
vec![5], vec![5],
|
|
|
|
vec![6], vec![6],
|
|
|
|
vec![7], vec![7],
|
|
|
|
vec![8], vec![8],
|
|
|
|
vec![9], vec![9],
|
|
|
|
];
|
|
|
|
// assert!(compare_shards_in_slots(&cycle, &expected_shards_in_slots));
|
|
|
|
assert_eq!(expected_shards_in_slots, shards_in_slots, "Shard assignment incorrect.")
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
// Check that the committees per slot is upper bounded by shard count
|
|
|
|
fn test_generate_cycle_committees_bounded() {
|
2018-10-04 03:21:16 +00:00
|
|
|
let validator_count: usize = 523;
|
|
|
|
let shard_count: usize = 31;
|
2018-10-03 08:47:18 +00:00
|
|
|
let crosslinking_shard_start: usize = 0;
|
2018-10-04 03:21:16 +00:00
|
|
|
let cycle_length: usize = 11;
|
|
|
|
let min_committee_size: usize = 5;
|
2018-10-03 08:47:18 +00:00
|
|
|
let (validators, shards, result) = generate_cycle_helper(
|
|
|
|
&validator_count,
|
|
|
|
&shard_count,
|
2018-10-05 04:53:21 +00:00
|
|
|
crosslinking_shard_start,
|
|
|
|
cycle_length,
|
|
|
|
min_committee_size);
|
2018-10-03 08:47:18 +00:00
|
|
|
let cycle = result.unwrap();
|
|
|
|
let assigned_validators = flatten_validators(&cycle);
|
|
|
|
let assigned_shards = flatten_and_dedup_shards(&cycle);
|
|
|
|
let shards_in_slots = flatten_shards_in_slots(&cycle);
|
2018-10-04 03:21:16 +00:00
|
|
|
let expected_shards = shards.get(0..22).unwrap();
|
|
|
|
let expected_shards_in_slots: Vec<Vec<usize>> =
|
|
|
|
(0_usize..11_usize) .map(|x| vec![2*x,2*x+1]).collect();
|
2018-10-03 08:47:18 +00:00
|
|
|
assert_eq!(assigned_validators, validators, "Validator assignment incorrect");
|
2018-10-04 03:21:16 +00:00
|
|
|
assert_eq!(assigned_shards, expected_shards, "Shard assignment incorrect");
|
2018-10-03 05:26:41 +00:00
|
|
|
// assert!(compare_shards_in_slots(&cycle, &expected_shards_in_slots));
|
|
|
|
assert_eq!(expected_shards_in_slots, shards_in_slots, "Shard assignment incorrect.")
|
|
|
|
}
|
|
|
|
}
|