#[derive(Clone, Debug, Eq, RlpEncodable, RlpDecodable, PartialEq, Default)]
pub struct ChainIdParamsDeprecated {
pub chain_id: u32,
}
impl ChainIdParamsDeprecated {
pub fn get_chain_id(&self, _epoch_number: u64) -> u32 { self.chain_id }
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ChainIdParamsInnerGeneric<ChainID> {
heights: Vec<u64>,
chain_ids: Vec<ChainID>,
}
pub type ChainIdParamsInner = ChainIdParamsInnerGeneric<AllChainID>;
pub type ChainIdParamsOneChainInner = ChainIdParamsInnerGeneric<u32>;
pub type ChainIdParams = Arc<RwLock<ChainIdParamsInner>>;
impl<T> ChainIdParamsInnerGeneric<T>
where T: Clone + Debug + Default + PartialEq + Encodable + Decodable + Copy
{
pub fn get_chain_id(&self, epoch_number: u64) -> T {
let index = self
.heights
.binary_search(&epoch_number)
.unwrap_or_else(|x| x - 1);
self.chain_ids[index]
}
pub fn new_inner(chain_id: T) -> Self {
Self {
heights: vec![0],
chain_ids: vec![chain_id],
}
}
pub fn matches(&self, other: &Self, peer_epoch_number: u64) -> bool {
let min_len = min(self.heights.len(), other.heights.len());
let sub_array_check = other.heights[0..min_len]
== self.heights[0..min_len]
&& other.chain_ids[0..min_len] == self.chain_ids[0..min_len];
if sub_array_check {
let index = self
.heights
.binary_search(&peer_epoch_number)
.unwrap_or_else(|x| x - 1);
min_len > index
} else {
return false;
}
}
}
impl ChainIdParamsOneChainInner {
pub fn parse_config_str(config: &str) -> std::result::Result<Self, String> {
let mut parsed = Self::default();
let value = config
.parse::<toml::Value>()
.map_err(|e| format!("{}", e))?;
if let toml::Value::Table(table) = &value {
if let Some(height_to_chain_ids) = table.get("height_to_chain_ids")
{
if let toml::Value::Array(array) = height_to_chain_ids {
if array.len() == 0 {
return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids is empty"));
}
let mut used_chain_ids = BTreeSet::new();
for element in array {
if let toml::Value::Array(pair) = element {
if pair.len() != 2 {
return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids elements is not [height, chain_id]"));
}
if let [toml::Value::Integer(height), toml::Value::Integer(chain_id)] =
&pair[0..2]
{
if *height < 0 {
return Err(String::from("Invalid ChainIdParams config format: height must be positive"));
}
if used_chain_ids.contains(chain_id) {
return Err(String::from("Invalid ChainIdParams config format: chain_id must be pairwise different"));
}
if *chain_id < 0
|| *chain_id > std::u32::MAX as i64
{
return Err(String::from("Invalid ChainIdParams config format: chain_id out of range for u32"));
}
parsed.heights.push(*height as u64);
parsed.chain_ids.push(*chain_id as u32);
used_chain_ids.insert(*chain_id);
}
} else {
return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids elements is not [height, chain_id]"));
}
}
} else {
return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids is not an array"));
}
} else {
return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids not found"));
}
if parsed.heights[0] != 0 {
return Err(String::from("Invalid ChainIdParams config format: height must start from 0"));
}
Ok(parsed)
} else {
return Err(String::from(
"Invalid ChainIdParams config format: not a table",
));
}
}
}
impl ChainIdParamsInner {
pub fn new_simple(chain_id: AllChainID) -> ChainIdParams {
Arc::new(RwLock::new(Self::new_inner(chain_id)))
}
pub fn new_from_inner(x: &Self) -> ChainIdParams {
Arc::new(RwLock::new(x.clone()))
}
pub fn to_native_space_params(&self) -> ChainIdParamsOneChainInner {
ChainIdParamsOneChainInner {
heights: self.heights.clone(),
chain_ids: self
.chain_ids
.iter()
.map(|x| x.in_native_space())
.collect(),
}
}
}
impl From<ChainIdParamsDeprecated> for ChainIdParamsOneChainInner {
fn from(x: ChainIdParamsDeprecated) -> Self {
Self {
heights: vec![0],
chain_ids: vec![x.chain_id],
}
}
}
#[cfg(test)]
mod test {
use super::*;
use ChainIdParamsInnerGeneric as ChainIdParamsInner;
#[test]
fn test_parse_config_str() {
let config_str =
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]";
let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
assert_eq!(
config,
ChainIdParamsInner {
heights: vec![0, 10000, 20000, 30000],
chain_ids: vec![0, 1, 2, 3],
}
);
let config_str = "";
let config = ChainIdParamsInner::parse_config_str(config_str);
assert!(config.is_err());
let config_str = "height_to_chain_ids = [[10, 1024]]";
let config = ChainIdParamsInner::parse_config_str(config_str);
assert!(config.is_err());
let config_str = "height_to_chain_ids = [0, 1024]";
let config = ChainIdParamsInner::parse_config_str(config_str);
assert!(config.is_err());
let config_str = "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 1]]";
let config = ChainIdParamsInner::parse_config_str(config_str);
assert!(config.is_err());
let config_str = "height_to_chain_ids = [[0, 1024]]";
let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
assert_eq!(
config,
ChainIdParamsInner {
heights: vec![0],
chain_ids: vec![1024],
}
);
}
#[test]
fn test_chain_id_at_height() {
let config_str =
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]";
let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
assert_eq!(config.get_chain_id(0), 0);
assert_eq!(config.get_chain_id(1), 0);
assert_eq!(config.get_chain_id(9999), 0);
assert_eq!(config.get_chain_id(10000), 1);
assert_eq!(config.get_chain_id(10001), 1);
assert_eq!(config.get_chain_id(19999), 1);
assert_eq!(config.get_chain_id(20000), 2);
assert_eq!(config.get_chain_id(20001), 2);
assert_eq!(config.get_chain_id(29999), 2);
assert_eq!(config.get_chain_id(30000), 3);
assert_eq!(config.get_chain_id(30001), 3);
}
#[test]
fn test_chain_id_peer_compatibility() {
let epoch_number = 30000;
let config = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]",
)
.unwrap();
let compatible_config_1 = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2]]",
)
.unwrap();
let compatible_config_2 = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3], [40000, 4], [50000, 5]]",
)
.unwrap();
let incompatible_config_1 = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 4]]",
)
.unwrap();
let incompatible_config_2 = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [25000, 3]]",
)
.unwrap();
let incompatible_config_3 = ChainIdParamsInner::parse_config_str(
"height_to_chain_ids = [[0, 0], [10000, 1]]",
)
.unwrap();
assert!(config.matches(&compatible_config_1, epoch_number - 1));
assert!(!config.matches(&compatible_config_1, epoch_number));
assert!(config.matches(&compatible_config_2, epoch_number));
assert!(!config.matches(&incompatible_config_1, epoch_number));
assert!(!config.matches(&incompatible_config_1, epoch_number - 1));
assert!(!config.matches(&incompatible_config_2, epoch_number));
assert!(!config.matches(&incompatible_config_3, epoch_number));
}
}
use cfx_types::AllChainID;
use parking_lot::RwLock;
use rlp::{Decodable, Encodable};
use std::{cmp::min, collections::BTreeSet, fmt::Debug, sync::Arc};