host_chain_core/
registry.rs1use crate::chain::{ChainId, ConnectionBackend, GenesisHash};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
16pub struct ChainRegistryEntry {
17 pub id: ChainId,
18 pub genesis_hash: GenesisHash,
19 pub display_name: String,
21 pub endpoint: String,
23 pub backend: ConnectionBackend,
25 pub relay_db_key: String,
27 pub para_db_key: String,
29 pub chain_specs: Option<(String, String)>,
32}
33
34#[derive(Debug, Default)]
40pub struct ChainRegistry {
41 entries: Vec<ChainRegistryEntry>,
42 by_hash: HashMap<GenesisHash, ChainId>,
43}
44
45impl ChainRegistry {
46 pub fn from_known_chains() -> Self {
53 let mut registry = Self::default();
54 for &id in ChainId::all() {
55 if let Some(hash) = id.genesis_hash() {
56 registry.insert_entry(ChainRegistryEntry {
57 id,
58 genesis_hash: hash,
59 display_name: id.display_name().to_string(),
60 endpoint: id.endpoint().to_string(),
61 backend: id.backend(),
62 relay_db_key: id.relay_db_key().to_string(),
63 para_db_key: id.para_db_key().to_string(),
64 chain_specs: id
65 .chain_specs()
66 .map(|(r, p)| (r.to_string(), p.to_string())),
67 });
68 }
69 }
70 registry
71 }
72
73 pub fn insert_entry(&mut self, entry: ChainRegistryEntry) {
80 let id = entry.id;
81 let genesis_hash = entry.genesis_hash;
82 self.by_hash.retain(|_, v| *v != id);
84 self.entries
86 .retain(|e| e.id != id && e.genesis_hash != genesis_hash);
87 self.entries.push(entry);
88 self.by_hash.insert(genesis_hash, id);
89 }
90
91 pub fn insert(&mut self, id: ChainId, genesis_hash: GenesisHash) {
103 self.insert_entry(ChainRegistryEntry {
104 id,
105 genesis_hash,
106 display_name: id.display_name().to_string(),
107 endpoint: id.endpoint().to_string(),
108 backend: id.backend(),
109 relay_db_key: id.relay_db_key().to_string(),
110 para_db_key: id.para_db_key().to_string(),
111 chain_specs: id
112 .chain_specs()
113 .map(|(r, p)| (r.to_string(), p.to_string())),
114 });
115 }
116
117 pub fn by_genesis_hash(&self, hash: &GenesisHash) -> Option<ChainId> {
121 self.by_hash.get(hash).copied()
122 }
123
124 pub fn genesis_hashes(&self) -> Vec<GenesisHash> {
126 self.entries.iter().map(|e| e.genesis_hash).collect()
127 }
128
129 pub fn entries(&self) -> &[ChainRegistryEntry] {
131 &self.entries
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::chain::GENESIS_POLKADOT_ASSET_HUB;
139
140 #[test]
141 fn test_from_known_chains_includes_polkadot_asset_hub() {
142 let registry = ChainRegistry::from_known_chains();
143 let ids: Vec<ChainId> = registry.entries().iter().map(|e| e.id).collect();
144 assert!(
145 ids.contains(&ChainId::PolkadotAssetHub),
146 "registry must contain PolkadotAssetHub"
147 );
148 }
149
150 #[test]
151 fn test_by_genesis_hash_returns_correct_chain() {
152 let registry = ChainRegistry::from_known_chains();
153 let result = registry.by_genesis_hash(&GENESIS_POLKADOT_ASSET_HUB);
154 assert_eq!(result, Some(ChainId::PolkadotAssetHub));
155 }
156
157 #[test]
158 fn test_by_genesis_hash_returns_none_for_unknown() {
159 let registry = ChainRegistry::from_known_chains();
160 let unknown: GenesisHash = [0xde; 32];
161 assert_eq!(registry.by_genesis_hash(&unknown), None);
162 }
163
164 #[test]
165 fn test_insert_and_lookup() {
166 let mut registry = ChainRegistry::default();
167 let hash: GenesisHash = [0x01; 32];
168 registry.insert(ChainId::PaseoAssetHub, hash);
169
170 assert_eq!(
171 registry.by_genesis_hash(&hash),
172 Some(ChainId::PaseoAssetHub)
173 );
174 assert_eq!(registry.entries().len(), 1);
175
176 registry.insert(ChainId::PaseoPeople, hash);
178 assert_eq!(registry.entries().len(), 1);
179 assert_eq!(registry.by_genesis_hash(&hash), Some(ChainId::PaseoPeople));
180 }
181
182 #[test]
183 fn test_insert_same_chain_id_different_hash_replaces() {
184 let mut registry = ChainRegistry::default();
185 let hash_a: GenesisHash = [0xAA; 32];
186 let hash_b: GenesisHash = [0xBB; 32];
187
188 registry.insert(ChainId::PaseoAssetHub, hash_a);
189 assert_eq!(
190 registry.by_genesis_hash(&hash_a),
191 Some(ChainId::PaseoAssetHub)
192 );
193
194 registry.insert(ChainId::PaseoAssetHub, hash_b);
196 assert_eq!(registry.entries().len(), 1, "old entry must be removed");
197 assert_eq!(
198 registry.by_genesis_hash(&hash_b),
199 Some(ChainId::PaseoAssetHub)
200 );
201 assert_eq!(
202 registry.by_genesis_hash(&hash_a),
203 None,
204 "old hash must be gone"
205 );
206 }
207
208 #[test]
209 fn test_genesis_hashes_returns_all() {
210 let mut registry = ChainRegistry::default();
211 let hash_a: GenesisHash = [0xAA; 32];
212 let hash_b: GenesisHash = [0xBB; 32];
213 registry.insert(ChainId::PaseoAssetHub, hash_a);
214 registry.insert(ChainId::PaseoPeople, hash_b);
215
216 let hashes = registry.genesis_hashes();
217 assert_eq!(hashes.len(), 2);
218 assert!(hashes.contains(&hash_a));
219 assert!(hashes.contains(&hash_b));
220 }
221
222 #[test]
223 fn test_empty_registry() {
224 let registry = ChainRegistry::default();
225 assert!(registry.entries().is_empty());
226 assert!(registry.genesis_hashes().is_empty());
227 assert_eq!(registry.by_genesis_hash(&[0u8; 32]), None);
228 }
229
230 #[test]
231 fn test_entry_has_display_name_and_endpoint() {
232 let registry = ChainRegistry::from_known_chains();
233 let entry = registry
234 .entries()
235 .iter()
236 .find(|e| e.id == ChainId::PolkadotAssetHub)
237 .expect("PolkadotAssetHub must be present in from_known_chains()");
238
239 assert_eq!(entry.display_name, "Polkadot Asset Hub");
240 assert_eq!(entry.endpoint, "wss://polkadot-asset-hub-rpc.polkadot.io");
241 }
242
243 #[test]
244 fn test_entry_has_chain_specs_for_smoldot_chains() {
245 let registry = ChainRegistry::from_known_chains();
246 let entry = registry
247 .entries()
248 .iter()
249 .find(|e| e.id == ChainId::PolkadotAssetHub)
250 .expect("PolkadotAssetHub must be present in from_known_chains()");
251
252 assert!(
254 entry.chain_specs.is_some(),
255 "PolkadotAssetHub entry must carry chain_specs for smoldot backend"
256 );
257 let (relay_spec, para_spec) = entry.chain_specs.as_ref().unwrap();
258 assert!(!relay_spec.is_empty(), "relay chain spec must not be empty");
259 assert!(!para_spec.is_empty(), "para chain spec must not be empty");
260 }
261
262 #[test]
263 fn test_entry_relay_db_key_matches_legacy() {
264 let registry = ChainRegistry::from_known_chains();
265 let entry = registry
266 .entries()
267 .iter()
268 .find(|e| e.id == ChainId::PolkadotAssetHub)
269 .expect("PolkadotAssetHub must be present in from_known_chains()");
270
271 assert_eq!(
273 entry.relay_db_key,
274 ChainId::PolkadotAssetHub.relay_db_key(),
275 "ChainRegistryEntry.relay_db_key must match ChainId::relay_db_key()"
276 );
277 assert_eq!(entry.relay_db_key, "polkadot-relay");
278 }
279}