cfx_internal_common/
chain_id.rs

1/// The parameters needed to determine the chain_id based on epoch_number.
2#[derive(Clone, Debug, Eq, RlpEncodable, RlpDecodable, PartialEq, Default)]
3pub struct ChainIdParamsDeprecated {
4    /// Preconfigured chain_id.
5    pub chain_id: u32,
6}
7
8impl ChainIdParamsDeprecated {
9    /// The function return the chain_id with given parameters
10    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    /// The function return the chain_id with given parameters
27    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        // Sub-array check. One height to epoch id map must be a sub-array of
44        // another.
45        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            // Check if peer has a high epoch_number but a shorter height to
52            // epoch id map, so that the chain_id of ourselves is
53            // not a match anymore.
54            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        // Config can't be empty.
173        let config_str = "";
174        let config = ChainIdParamsInner::parse_config_str(config_str);
175        assert!(config.is_err());
176
177        // Height must start from 0.
178        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        // Must be array of array.
183        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        // Can not reuse chain_id.
188        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};