referrerpolicy=no-referrer-when-downgrade

polkadot_omni_node_lib/common/
runtime.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Runtime parameters.
18
19use 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
29/// Expected parachain system pallet runtime type name.
30pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem";
31/// Expected frame system pallet runtime type name.
32pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System";
33/// Expected Aura pallet runtime type name.
34pub const DEFAULT_AURA_PALLET_NAME: &str = "Aura";
35
36/// The Aura ID used by the Aura consensus
37#[derive(Debug, PartialEq)]
38pub enum AuraConsensusId {
39	/// Ed25519
40	Ed25519,
41	/// Sr25519
42	Sr25519,
43}
44
45/// Determines the appropriate Aura consensus ID based on the chain spec ID.
46///
47/// Most parachains use Sr25519 for Aura consensus, but Asset Hub Polkadot
48/// (formerly Statemint) uses Ed25519.
49///
50/// # Returns
51///
52/// Returns `AuraConsensusId::Ed25519` for chain spec IDs starting with
53/// `asset-hub-polkadot` or `statemint`, and `AuraConsensusId::Sr25519` for all
54/// other chains.
55pub 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/// The choice of consensus for the parachain omni-node.
76#[derive(PartialEq)]
77pub enum Consensus {
78	/// Aura consensus.
79	Aura(AuraConsensusId),
80}
81
82/// The choice of block number for the parachain omni-node.
83#[derive(PartialEq, Debug)]
84pub enum BlockNumber {
85	/// u32
86	U32,
87	/// u64
88	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/// Helper enum listing the supported Runtime types
120#[derive(PartialEq)]
121pub enum Runtime {
122	/// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be
123	/// an omni-node, and simply run a node with the given consensus algorithm.
124	Omni(BlockNumber, Consensus),
125}
126
127/// Helper trait used for extracting the Runtime variant from the chain spec ID.
128pub trait RuntimeResolver {
129	/// Extract the Runtime variant from the chain spec ID.
130	fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime>;
131}
132
133/// Default implementation for `RuntimeResolver` that just returns
134/// `Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))`.
135pub 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		// 1. (Recommended) Try to find AuthorityId in the pallet's associated types.
228		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		// 2. (Robust Fallback) Check the "Authorities" storage item in the Aura pallet.
235		// Some chain specs might not expose all associated types clearly, but storage is usually
236		// present.
237		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	/// Resolves whether a given type ID represents an Sr25519 or Ed25519 Aura ID.
248	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		// Check if the type path contains sr25519 or ed25519.
253		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		// Transform into subxt-metadata.
290		// subxt-metadata doesn't directly implement TryFrom<RuntimeMetadata>, so we decode it again
291		// as subxt-metadata. This is "cleaner" because we use a robust metadata versioning check
292		// first. We encode the full `RuntimeMetadataPrefixed` to include the magic number.
293		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		// Asset Hub Polkadot uses Ed25519
350		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		// Other chains use Sr25519
354		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}