polkadot_omni_node_lib/common/
runtime.rs1use codec::{Decode, Encode};
20use cumulus_client_service::ParachainHostFunctions;
21use frame_metadata::RuntimeMetadataPrefixed;
22use sc_chain_spec::ChainSpec;
23use sc_executor::WasmExecutor;
24use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
25use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive};
26use std::fmt::Display;
27use subxt_metadata::{Metadata, StorageEntryType};
28
29pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem";
31pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System";
33pub const DEFAULT_AURA_PALLET_NAME: &str = "Aura";
35
36#[derive(Debug, PartialEq)]
38pub enum AuraConsensusId {
39 Ed25519,
41 Sr25519,
43}
44
45pub fn aura_id_from_chain_spec_id(id: &str) -> AuraConsensusId {
56 let id_normalized = id.replace('_', "-");
57 if id_normalized.starts_with("asset-hub-polkadot") || id_normalized.starts_with("statemint") {
58 log::warn!(
59 "⚠️ Aura authority id type is assumed to be `ed25519` because the chain spec id \
60 starts with `asset-hub-polkadot` or `statemint`. This is a known special case for \
61 Asset Hub Polkadot (formerly Statemint). If this assumption is wrong for your runtime, \
62 the node may not work correctly."
63 );
64 AuraConsensusId::Ed25519
65 } else {
66 log::warn!(
67 "⚠️ Aura authority id type is assumed to be `sr25519` by default. Runtimes using \
68 `ed25519` for Aura are not yet supported (except for `asset-hub-polkadot` / `statemint`). \
69 If your runtime uses `ed25519` for Aura, it may not work correctly with this node."
70 );
71 AuraConsensusId::Sr25519
72 }
73}
74
75#[derive(PartialEq)]
77pub enum Consensus {
78 Aura(AuraConsensusId),
80}
81
82#[derive(PartialEq, Debug)]
84pub enum BlockNumber {
85 U32,
87 U64,
89}
90
91impl Display for BlockNumber {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 BlockNumber::U32 => write!(f, "u32"),
95 BlockNumber::U64 => write!(f, "u64"),
96 }
97 }
98}
99
100impl Into<TypeDefPrimitive> for BlockNumber {
101 fn into(self) -> TypeDefPrimitive {
102 match self {
103 BlockNumber::U32 => TypeDefPrimitive::U32,
104 BlockNumber::U64 => TypeDefPrimitive::U64,
105 }
106 }
107}
108
109impl BlockNumber {
110 fn from_type_def(type_def: &TypeDef<PortableForm>) -> Option<BlockNumber> {
111 match type_def {
112 TypeDef::Primitive(TypeDefPrimitive::U32) => Some(BlockNumber::U32),
113 TypeDef::Primitive(TypeDefPrimitive::U64) => Some(BlockNumber::U64),
114 _ => None,
115 }
116 }
117}
118
119#[derive(PartialEq)]
121pub enum Runtime {
122 Omni(BlockNumber, Consensus),
125}
126
127pub trait RuntimeResolver {
129 fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime>;
131}
132
133pub struct DefaultRuntimeResolver;
136
137impl RuntimeResolver for DefaultRuntimeResolver {
138 fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> {
139 let Ok(metadata_inspector) = MetadataInspector::new(chain_spec) else {
140 log::info!(
141 "Unable to check metadata. Skipping metadata checks. Metadata checks are supported for metadata versions v14 and higher."
142 );
143 let aura_id = aura_id_from_chain_spec_id(chain_spec.id());
144 return Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(aura_id)));
145 };
146
147 let block_number = metadata_inspector.block_number().unwrap_or_else(|| {
148 log::warn!(
149 r#"⚠️ There isn't a runtime type named `System`, corresponding to the `frame-system`
150 pallet (https://docs.rs/frame-system/latest/frame_system/). Please check Omni Node docs for runtime conventions:
151 https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions.
152 Note: We'll assume a block number size of `u32`."#
153 );
154 BlockNumber::U32
155 });
156
157 if !metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME) {
158 log::warn!(
159 r#"⚠️ The parachain system pallet (https://docs.rs/crate/cumulus-pallet-parachain-system/latest) is
160 missing from the runtime's metadata. Please check Omni Node docs for runtime conventions:
161 https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions."#
162 );
163 }
164
165 let aura_id = match metadata_inspector.aura_consensus_id() {
166 Some(id) => id,
167 None => {
168 log::warn!(
169 r#"⚠️ The Aura authority ID type was not found in the runtime metadata.
170 This can be expected if the runtime does not include `pallet-aura`,
171 or if the chain starts without Aura at genesis and enables it later
172 via a runtime upgrade (for example, asset-hub-polkadot).
173
174 Falling back to chain spec ID heuristics."#
175 );
176 aura_id_from_chain_spec_id(chain_spec.id())
177 },
178 };
179 log::info!(
180 "Omni Node strategy: BlockNumber={}, Consensus=Aura({:?})",
181 block_number,
182 aura_id
183 );
184 Ok(Runtime::Omni(block_number, Consensus::Aura(aura_id)))
185 }
186}
187
188struct MetadataInspector {
189 metadata: Metadata,
190}
191
192impl MetadataInspector {
193 fn new(chain_spec: &dyn ChainSpec) -> Result<MetadataInspector, sc_cli::Error> {
194 let (metadata, version) = MetadataInspector::fetch_metadata(chain_spec)?;
195 log::info!("Detected runtime metadata version: V{}", version);
196 Ok(MetadataInspector { metadata })
197 }
198
199 fn storage_entry_type(
200 &self,
201 pallet_name: &str,
202 entry_name: &str,
203 ) -> Option<&Type<PortableForm>> {
204 self.metadata
205 .pallet_by_name(pallet_name)?
206 .storage()?
207 .entry_by_name(entry_name)
208 .and_then(|entry| match entry.entry_type() {
209 StorageEntryType::Plain(ty_id) => Some(*ty_id),
210 _ => None,
211 })
212 .and_then(|ty_id| self.metadata.types().resolve(ty_id))
213 }
214
215 fn pallet_exists(&self, name: &str) -> bool {
216 self.metadata.pallet_by_name(name).is_some()
217 }
218
219 fn block_number(&self) -> Option<BlockNumber> {
220 self.storage_entry_type(DEFAULT_FRAME_SYSTEM_PALLET_NAME, "Number")
221 .and_then(|portable_type| BlockNumber::from_type_def(&portable_type.type_def))
222 }
223
224 fn aura_consensus_id(&self) -> Option<AuraConsensusId> {
225 let pallet = self.metadata.pallet_by_name(DEFAULT_AURA_PALLET_NAME)?;
226
227 if let Some(ty_id) = pallet.associated_type_id("AuthorityId") {
229 if let Some(id) = self.resolve_aura_id_from_type_id(ty_id) {
230 return Some(id);
231 }
232 }
233
234 if let Some(authorities_ty) =
238 self.storage_entry_type(DEFAULT_AURA_PALLET_NAME, "Authorities")
239 {
240 let authority_ty = authorities_ty.type_params.get(0)?;
241 return self.resolve_aura_id_from_type_id(authority_ty.ty?.id);
242 }
243
244 None
245 }
246
247 fn resolve_aura_id_from_type_id(&self, type_id: u32) -> Option<AuraConsensusId> {
249 let portable_type = self.metadata.types().resolve(type_id)?;
250 let segments = &portable_type.path.segments;
251
252 if segments.iter().any(|s| s.to_lowercase().contains("sr25519")) {
254 return Some(AuraConsensusId::Sr25519);
255 }
256 if segments.iter().any(|s| s.to_lowercase().contains("ed25519")) {
257 return Some(AuraConsensusId::Ed25519);
258 }
259
260 None
261 }
262
263 fn fetch_metadata(chain_spec: &dyn ChainSpec) -> Result<(Metadata, u32), sc_cli::Error> {
264 let mut storage = chain_spec.build_storage()?;
265 let code_bytes = storage
266 .top
267 .remove(sp_storage::well_known_keys::CODE)
268 .ok_or("chain spec genesis does not contain code")?;
269 let executor = WasmExecutor::<ParachainHostFunctions>::builder()
270 .with_allow_missing_host_functions(true)
271 .build();
272 let opaque_metadata = fetch_latest_metadata_from_code_blob(
273 &executor,
274 sp_runtime::Cow::Borrowed(code_bytes.as_slice()),
275 )
276 .map_err(|err| err.to_string())?;
277
278 let mut encoded = (*opaque_metadata).as_slice();
279 MetadataInspector::fetch_metadata_from_bytes(&mut encoded)
280 }
281
282 fn fetch_metadata_from_bytes(mut encoded: &[u8]) -> Result<(Metadata, u32), sc_cli::Error> {
283 let prefixed = RuntimeMetadataPrefixed::decode(&mut encoded).map_err(|e| {
284 sc_cli::Error::Input(format!("failed to decode prefixed metadata: {e}").into())
285 })?;
286
287 let version = prefixed.1.version();
288
289 let encoded = prefixed.encode();
294 let metadata = Metadata::decode(&mut &encoded[..]).map_err(|e| {
295 sc_cli::Error::Input(format!("failed to decode subxt metadata: {e}").into())
296 })?;
297
298 Ok((metadata, version))
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use crate::runtime::{
305 AuraConsensusId, BlockNumber, MetadataInspector, DEFAULT_FRAME_SYSTEM_PALLET_NAME,
306 DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME,
307 };
308 use cumulus_client_service::ParachainHostFunctions;
309 use sc_executor::WasmExecutor;
310 use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
311
312 fn cumulus_test_runtime_inspector() -> MetadataInspector {
313 let opaque_metadata = fetch_latest_metadata_from_code_blob(
314 &WasmExecutor::<ParachainHostFunctions>::builder()
315 .with_allow_missing_host_functions(true)
316 .build(),
317 sp_runtime::Cow::Borrowed(cumulus_test_runtime::WASM_BINARY.unwrap()),
318 )
319 .unwrap();
320 let mut encoded = (*opaque_metadata).as_slice();
321 let (metadata, _version) =
322 MetadataInspector::fetch_metadata_from_bytes(&mut encoded).unwrap();
323 MetadataInspector { metadata }
324 }
325
326 #[test]
327 fn test_pallet_exists() {
328 let inspector = cumulus_test_runtime_inspector();
329 assert!(inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME));
330 assert!(inspector.pallet_exists(DEFAULT_FRAME_SYSTEM_PALLET_NAME));
331 }
332
333 #[test]
334 fn test_runtime_block_number() {
335 let inspector = cumulus_test_runtime_inspector();
336 assert_eq!(inspector.block_number().unwrap(), BlockNumber::U32);
337 }
338
339 #[test]
340 fn test_runtime_aura_consensus_id() {
341 let inspector = cumulus_test_runtime_inspector();
342 assert_eq!(inspector.aura_consensus_id().unwrap(), AuraConsensusId::Sr25519);
343 }
344
345 #[test]
346 fn test_aura_id_from_chain_spec_id() {
347 use crate::runtime::{aura_id_from_chain_spec_id, AuraConsensusId};
348
349 assert_eq!(aura_id_from_chain_spec_id("asset-hub-polkadot"), AuraConsensusId::Ed25519);
351 assert_eq!(aura_id_from_chain_spec_id("statemint"), AuraConsensusId::Ed25519);
352
353 assert_eq!(aura_id_from_chain_spec_id("asset-hub-kusama"), AuraConsensusId::Sr25519);
355 assert_eq!(aura_id_from_chain_spec_id("penpal-rococo-1000"), AuraConsensusId::Sr25519);
356 assert_eq!(aura_id_from_chain_spec_id("collectives-westend"), AuraConsensusId::Sr25519);
357 }
358}