1#[derive(Clone, Debug, Eq, RlpEncodable, RlpDecodable, PartialEq, Default)]
3pub struct ChainIdParamsDeprecated {
4 pub chain_id: u32,
6}
7
8impl ChainIdParamsDeprecated {
9 pub fn get_chain_id(&self, _epoch_number: u64) -> u32 { self.chain_id }
11}
12
13#[derive(Clone, Debug, Default, PartialEq)]
14pub struct ChainIdParamsInnerGeneric<ChainID> {
15 heights: Vec<u64>,
16 chain_ids: Vec<ChainID>,
17}
18
19pub type ChainIdParamsInner = ChainIdParamsInnerGeneric<AllChainID>;
20pub type ChainIdParamsOneChainInner = ChainIdParamsInnerGeneric<u32>;
21pub type ChainIdParams = Arc<RwLock<ChainIdParamsInner>>;
22
23impl<T> ChainIdParamsInnerGeneric<T>
24where T: Clone + Debug + Default + PartialEq + Encodable + Decodable + Copy
25{
26 pub fn get_chain_id(&self, epoch_number: u64) -> T {
28 let index = self
29 .heights
30 .binary_search(&epoch_number)
31 .unwrap_or_else(|x| x - 1);
32 self.chain_ids[index]
33 }
34
35 pub fn new_inner(chain_id: T) -> Self {
36 Self {
37 heights: vec![0],
38 chain_ids: vec![chain_id],
39 }
40 }
41
42 pub fn matches(&self, other: &Self, peer_epoch_number: u64) -> bool {
43 let min_len = min(self.heights.len(), other.heights.len());
46 let sub_array_check = other.heights[0..min_len]
47 == self.heights[0..min_len]
48 && other.chain_ids[0..min_len] == self.chain_ids[0..min_len];
49
50 if sub_array_check {
51 let index = self
55 .heights
56 .binary_search(&peer_epoch_number)
57 .unwrap_or_else(|x| x - 1);
58 min_len > index
59 } else {
60 return false;
61 }
62 }
63}
64
65impl ChainIdParamsOneChainInner {
66 pub fn parse_config_str(config: &str) -> std::result::Result<Self, String> {
67 let mut parsed = Self::default();
68 let value = config
69 .parse::<toml::Value>()
70 .map_err(|e| format!("{}", e))?;
71 if let toml::Value::Table(table) = &value {
72 if let Some(height_to_chain_ids) = table.get("height_to_chain_ids")
73 {
74 if let toml::Value::Array(array) = height_to_chain_ids {
75 if array.len() == 0 {
76 return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids is empty"));
77 }
78 let mut used_chain_ids = BTreeSet::new();
79 for element in array {
80 if let toml::Value::Array(pair) = element {
81 if pair.len() != 2 {
82 return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids elements is not [height, chain_id]"));
83 }
84 if let [toml::Value::Integer(height), toml::Value::Integer(chain_id)] =
85 &pair[0..2]
86 {
87 if *height < 0 {
88 return Err(String::from("Invalid ChainIdParams config format: height must be positive"));
89 }
90 if used_chain_ids.contains(chain_id) {
91 return Err(String::from("Invalid ChainIdParams config format: chain_id must be pairwise different"));
92 }
93 if *chain_id < 0
94 || *chain_id > std::u32::MAX as i64
95 {
96 return Err(String::from("Invalid ChainIdParams config format: chain_id out of range for u32"));
97 }
98 parsed.heights.push(*height as u64);
99 parsed.chain_ids.push(*chain_id as u32);
100 used_chain_ids.insert(*chain_id);
101 }
102 } else {
103 return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids elements is not [height, chain_id]"));
104 }
105 }
106 } else {
107 return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids is not an array"));
108 }
109 } else {
110 return Err(String::from("Invalid ChainIdParams config format: height_to_chain_ids not found"));
111 }
112 if parsed.heights[0] != 0 {
113 return Err(String::from("Invalid ChainIdParams config format: height must start from 0"));
114 }
115 Ok(parsed)
116 } else {
117 return Err(String::from(
118 "Invalid ChainIdParams config format: not a table",
119 ));
120 }
121 }
122}
123
124impl ChainIdParamsInner {
125 pub fn new_simple(chain_id: AllChainID) -> ChainIdParams {
126 Arc::new(RwLock::new(Self::new_inner(chain_id)))
127 }
128
129 pub fn new_from_inner(x: &Self) -> ChainIdParams {
130 Arc::new(RwLock::new(x.clone()))
131 }
132
133 pub fn to_native_space_params(&self) -> ChainIdParamsOneChainInner {
134 ChainIdParamsOneChainInner {
135 heights: self.heights.clone(),
136 chain_ids: self
137 .chain_ids
138 .iter()
139 .map(|x| x.in_native_space())
140 .collect(),
141 }
142 }
143}
144
145impl From<ChainIdParamsDeprecated> for ChainIdParamsOneChainInner {
146 fn from(x: ChainIdParamsDeprecated) -> Self {
147 Self {
148 heights: vec![0],
149 chain_ids: vec![x.chain_id],
150 }
151 }
152}
153
154#[cfg(test)]
155mod test {
156 use super::*;
157 use ChainIdParamsInnerGeneric as ChainIdParamsInner;
158
159 #[test]
160 fn test_parse_config_str() {
161 let config_str =
162 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]";
163 let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
164 assert_eq!(
165 config,
166 ChainIdParamsInner {
167 heights: vec![0, 10000, 20000, 30000],
168 chain_ids: vec![0, 1, 2, 3],
169 }
170 );
171
172 let config_str = "";
174 let config = ChainIdParamsInner::parse_config_str(config_str);
175 assert!(config.is_err());
176
177 let config_str = "height_to_chain_ids = [[10, 1024]]";
179 let config = ChainIdParamsInner::parse_config_str(config_str);
180 assert!(config.is_err());
181
182 let config_str = "height_to_chain_ids = [0, 1024]";
184 let config = ChainIdParamsInner::parse_config_str(config_str);
185 assert!(config.is_err());
186
187 let config_str = "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 1]]";
189 let config = ChainIdParamsInner::parse_config_str(config_str);
190 assert!(config.is_err());
191
192 let config_str = "height_to_chain_ids = [[0, 1024]]";
193 let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
194 assert_eq!(
195 config,
196 ChainIdParamsInner {
197 heights: vec![0],
198 chain_ids: vec![1024],
199 }
200 );
201 }
202
203 #[test]
204 fn test_chain_id_at_height() {
205 let config_str =
206 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]";
207 let config = ChainIdParamsInner::parse_config_str(config_str).unwrap();
208 assert_eq!(config.get_chain_id(0), 0);
209 assert_eq!(config.get_chain_id(1), 0);
210 assert_eq!(config.get_chain_id(9999), 0);
211 assert_eq!(config.get_chain_id(10000), 1);
212 assert_eq!(config.get_chain_id(10001), 1);
213 assert_eq!(config.get_chain_id(19999), 1);
214 assert_eq!(config.get_chain_id(20000), 2);
215 assert_eq!(config.get_chain_id(20001), 2);
216 assert_eq!(config.get_chain_id(29999), 2);
217 assert_eq!(config.get_chain_id(30000), 3);
218 assert_eq!(config.get_chain_id(30001), 3);
219 }
220
221 #[test]
222 fn test_chain_id_peer_compatibility() {
223 let epoch_number = 30000;
224 let config = ChainIdParamsInner::parse_config_str(
225 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3]]",
226 )
227 .unwrap();
228 let compatible_config_1 = ChainIdParamsInner::parse_config_str(
229 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2]]",
230 )
231 .unwrap();
232 let compatible_config_2 = ChainIdParamsInner::parse_config_str(
233 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 3], [40000, 4], [50000, 5]]",
234 )
235 .unwrap();
236 let incompatible_config_1 = ChainIdParamsInner::parse_config_str(
237 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [30000, 4]]",
238 )
239 .unwrap();
240 let incompatible_config_2 = ChainIdParamsInner::parse_config_str(
241 "height_to_chain_ids = [[0, 0], [10000, 1], [20000, 2], [25000, 3]]",
242 )
243 .unwrap();
244 let incompatible_config_3 = ChainIdParamsInner::parse_config_str(
245 "height_to_chain_ids = [[0, 0], [10000, 1]]",
246 )
247 .unwrap();
248
249 assert!(config.matches(&compatible_config_1, epoch_number - 1));
250 assert!(!config.matches(&compatible_config_1, epoch_number));
251 assert!(config.matches(&compatible_config_2, epoch_number));
252 assert!(!config.matches(&incompatible_config_1, epoch_number));
253 assert!(!config.matches(&incompatible_config_1, epoch_number - 1));
254 assert!(!config.matches(&incompatible_config_2, epoch_number));
255 assert!(!config.matches(&incompatible_config_3, epoch_number));
256 }
257}
258
259use cfx_types::AllChainID;
260use parking_lot::RwLock;
261use rlp::{Decodable, Encodable};
262use std::{cmp::min, collections::BTreeSet, fmt::Debug, sync::Arc};