// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
use polkadot_erasure_coding::systematic_recovery_threshold;
use polkadot_primitives::{node_features, ChunkIndex, CoreIndex, NodeFeatures, ValidatorIndex};
/// Compute the per-validator availability chunk index.
/// WARNING: THIS FUNCTION IS CRITICAL TO PARACHAIN CONSENSUS.
/// Any modification to the output of the function needs to be coordinated via the runtime.
/// It's best to use minimal/no external dependencies.
pub fn availability_chunk_index(
maybe_node_features: Option<&NodeFeatures>,
n_validators: usize,
core_index: CoreIndex,
validator_index: ValidatorIndex,
) -> Result {
if let Some(features) = maybe_node_features {
if let Some(&true) = features
.get(usize::from(node_features::FeatureIndex::AvailabilityChunkMapping as u8))
.as_deref()
{
let systematic_threshold = systematic_recovery_threshold(n_validators)? as u32;
let core_start_pos = core_index.0 * systematic_threshold;
return Ok(ChunkIndex((core_start_pos + validator_index.0) % n_validators as u32))
}
}
Ok(validator_index.into())
}
/// Compute the per-core availability chunk indices. Returns a Vec which maps ValidatorIndex to
/// ChunkIndex for a given availability core index
/// WARNING: THIS FUNCTION IS CRITICAL TO PARACHAIN CONSENSUS.
/// Any modification to the output of the function needs to be coordinated via the
/// runtime. It's best to use minimal/no external dependencies.
pub fn availability_chunk_indices(
maybe_node_features: Option<&NodeFeatures>,
n_validators: usize,
core_index: CoreIndex,
) -> Result, polkadot_erasure_coding::Error> {
let identity = (0..n_validators).map(|index| ChunkIndex(index as u32));
if let Some(features) = maybe_node_features {
if let Some(&true) = features
.get(usize::from(node_features::FeatureIndex::AvailabilityChunkMapping as u8))
.as_deref()
{
let systematic_threshold = systematic_recovery_threshold(n_validators)? as u32;
let core_start_pos = core_index.0 * systematic_threshold;
return Ok(identity
.into_iter()
.cycle()
.skip(core_start_pos as usize)
.take(n_validators)
.collect())
}
}
Ok(identity.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
pub fn node_features_with_mapping_enabled() -> NodeFeatures {
let mut node_features = NodeFeatures::new();
node_features
.resize(node_features::FeatureIndex::AvailabilityChunkMapping as usize + 1, false);
node_features
.set(node_features::FeatureIndex::AvailabilityChunkMapping as u8 as usize, true);
node_features
}
pub fn node_features_with_other_bits_enabled() -> NodeFeatures {
let mut node_features = NodeFeatures::new();
node_features.resize(node_features::FeatureIndex::FirstUnassigned as usize + 1, true);
node_features
.set(node_features::FeatureIndex::AvailabilityChunkMapping as u8 as usize, false);
node_features
}
#[test]
fn test_availability_chunk_indices() {
let n_validators = 20u32;
let n_cores = 15u32;
// If the mapping feature is not enabled, it should always be the identity vector.
{
for node_features in
[None, Some(NodeFeatures::EMPTY), Some(node_features_with_other_bits_enabled())]
{
for core_index in 0..n_cores {
let indices = availability_chunk_indices(
node_features.as_ref(),
n_validators as usize,
CoreIndex(core_index),
)
.unwrap();
for validator_index in 0..n_validators {
assert_eq!(
indices[validator_index as usize],
availability_chunk_index(
node_features.as_ref(),
n_validators as usize,
CoreIndex(core_index),
ValidatorIndex(validator_index)
)
.unwrap()
)
}
assert_eq!(
indices,
(0..n_validators).map(|i| ChunkIndex(i)).collect::>()
);
}
}
}
// Test when mapping feature is enabled.
{
let node_features = node_features_with_mapping_enabled();
let mut previous_indices = None;
for core_index in 0..n_cores {
let indices = availability_chunk_indices(
Some(&node_features),
n_validators as usize,
CoreIndex(core_index),
)
.unwrap();
for validator_index in 0..n_validators {
assert_eq!(
indices[validator_index as usize],
availability_chunk_index(
Some(&node_features),
n_validators as usize,
CoreIndex(core_index),
ValidatorIndex(validator_index)
)
.unwrap()
)
}
// Check that it's not equal to the previous core's indices.
if let Some(previous_indices) = previous_indices {
assert_ne!(previous_indices, indices);
}
previous_indices = Some(indices.clone());
// Check that it's indeed a permutation.
assert_eq!(
(0..n_validators).map(|i| ChunkIndex(i)).collect::>(),
indices.into_iter().collect::>()
);
}
}
}
#[test]
// This is just a dummy test that checks the mapping against some hardcoded outputs, to prevent
// accidental changes to the algorithms.
fn prevent_changes_to_mapping() {
let n_validators = 7;
let node_features = node_features_with_mapping_enabled();
assert_eq!(
availability_chunk_indices(Some(&node_features), n_validators, CoreIndex(0))
.unwrap()
.into_iter()
.map(|i| i.0)
.collect::>(),
vec![0, 1, 2, 3, 4, 5, 6]
);
assert_eq!(
availability_chunk_indices(Some(&node_features), n_validators, CoreIndex(1))
.unwrap()
.into_iter()
.map(|i| i.0)
.collect::>(),
vec![2, 3, 4, 5, 6, 0, 1]
);
assert_eq!(
availability_chunk_indices(Some(&node_features), n_validators, CoreIndex(2))
.unwrap()
.into_iter()
.map(|i| i.0)
.collect::>(),
vec![4, 5, 6, 0, 1, 2, 3]
);
assert_eq!(
availability_chunk_indices(Some(&node_features), n_validators, CoreIndex(3))
.unwrap()
.into_iter()
.map(|i| i.0)
.collect::>(),
vec![6, 0, 1, 2, 3, 4, 5]
);
assert_eq!(
availability_chunk_indices(Some(&node_features), n_validators, CoreIndex(4))
.unwrap()
.into_iter()
.map(|i| i.0)
.collect::>(),
vec![1, 2, 3, 4, 5, 6, 0]
);
}
}