Skip to main content

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;
20use cumulus_client_service::ParachainHostFunctions;
21use sc_chain_spec::ChainSpec;
22use sc_executor::WasmExecutor;
23use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
24use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive};
25use std::fmt::Display;
26use subxt_metadata::{Metadata, StorageEntryType};
27
28/// Expected parachain system pallet runtime type name.
29pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem";
30/// Expected frame system pallet runtime type name.
31pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System";
32
33/// The Aura ID used by the Aura consensus
34#[derive(Debug, PartialEq)]
35pub enum AuraConsensusId {
36	/// Ed25519
37	Ed25519,
38	/// Sr25519
39	Sr25519,
40}
41
42/// Determines the appropriate Aura consensus ID based on the chain spec ID.
43///
44/// Most parachains use Sr25519 for Aura consensus, but Asset Hub Polkadot
45/// (formerly Statemint) uses Ed25519.
46///
47/// # Returns
48///
49/// Returns `AuraConsensusId::Ed25519` for chain spec IDs starting with
50/// `asset-hub-polkadot` or `statemint`, and `AuraConsensusId::Sr25519` for all
51/// other chains.
52pub fn aura_id_from_chain_spec_id(id: &str) -> AuraConsensusId {
53	let id_normalized = id.replace('_', "-");
54	if id_normalized.starts_with("asset-hub-polkadot") || id_normalized.starts_with("statemint") {
55		log::warn!(
56			"⚠️  Aura authority id type is assumed to be `ed25519` because the chain spec id \
57			starts with `asset-hub-polkadot` or `statemint`. This is a known special case for \
58			Asset Hub Polkadot (formerly Statemint). If this assumption is wrong for your runtime, \
59			the node may not work correctly."
60		);
61		AuraConsensusId::Ed25519
62	} else {
63		log::warn!(
64			"⚠️  Aura authority id type is assumed to be `sr25519` by default. Runtimes using \
65			`ed25519` for Aura are not yet supported (except for `asset-hub-polkadot` / `statemint`). \
66			If your runtime uses `ed25519` for Aura, it may not work correctly with this node."
67		);
68		AuraConsensusId::Sr25519
69	}
70}
71
72/// The choice of consensus for the parachain omni-node.
73#[derive(PartialEq)]
74pub enum Consensus {
75	/// Aura consensus.
76	Aura(AuraConsensusId),
77}
78
79/// The choice of block number for the parachain omni-node.
80#[derive(PartialEq, Debug)]
81pub enum BlockNumber {
82	/// u32
83	U32,
84	/// u64
85	U64,
86}
87
88impl Display for BlockNumber {
89	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90		match self {
91			BlockNumber::U32 => write!(f, "u32"),
92			BlockNumber::U64 => write!(f, "u64"),
93		}
94	}
95}
96
97impl Into<TypeDefPrimitive> for BlockNumber {
98	fn into(self) -> TypeDefPrimitive {
99		match self {
100			BlockNumber::U32 => TypeDefPrimitive::U32,
101			BlockNumber::U64 => TypeDefPrimitive::U64,
102		}
103	}
104}
105
106impl BlockNumber {
107	fn from_type_def(type_def: &TypeDef<PortableForm>) -> Option<BlockNumber> {
108		match type_def {
109			TypeDef::Primitive(TypeDefPrimitive::U32) => Some(BlockNumber::U32),
110			TypeDef::Primitive(TypeDefPrimitive::U64) => Some(BlockNumber::U64),
111			_ => None,
112		}
113	}
114}
115
116/// Helper enum listing the supported Runtime types
117#[derive(PartialEq)]
118pub enum Runtime {
119	/// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be
120	/// an omni-node, and simply run a node with the given consensus algorithm.
121	Omni(BlockNumber, Consensus),
122}
123
124/// Helper trait used for extracting the Runtime variant from the chain spec ID.
125pub trait RuntimeResolver {
126	/// Extract the Runtime variant from the chain spec ID.
127	fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime>;
128}
129
130/// Default implementation for `RuntimeResolver` that just returns
131/// `Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))`.
132pub struct DefaultRuntimeResolver;
133
134impl RuntimeResolver for DefaultRuntimeResolver {
135	fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> {
136		let aura_id = aura_id_from_chain_spec_id(chain_spec.id());
137
138		let Ok(metadata_inspector) = MetadataInspector::new(chain_spec) else {
139			log::info!("Unable to check metadata. Skipping metadata checks. Metadata checks are supported for metadata versions v14 and higher.");
140			return Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(aura_id)));
141		};
142
143		let block_number = metadata_inspector.block_number().unwrap_or_else(|| {
144			log::warn!(
145					r#"⚠️  There isn't a runtime type named `System`, corresponding to the `frame-system`
146                pallet (https://docs.rs/frame-system/latest/frame_system/). Please check Omni Node docs for runtime conventions:
147                https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions.
148                Note: We'll assume a block number size of `u32`."#
149				);
150			BlockNumber::U32
151		});
152
153		if !metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME) {
154			log::warn!(
155				r#"⚠️  The parachain system pallet (https://docs.rs/crate/cumulus-pallet-parachain-system/latest) is
156			   missing from the runtime's metadata. Please check Omni Node docs for runtime conventions:
157			   https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions."#
158			);
159		}
160
161		Ok(Runtime::Omni(block_number, Consensus::Aura(aura_id)))
162	}
163}
164
165struct MetadataInspector(Metadata);
166
167impl MetadataInspector {
168	fn new(chain_spec: &dyn ChainSpec) -> Result<MetadataInspector, sc_cli::Error> {
169		MetadataInspector::fetch_metadata(chain_spec).map(MetadataInspector)
170	}
171
172	fn pallet_exists(&self, name: &str) -> bool {
173		self.0.pallet_by_name(name).is_some()
174	}
175
176	fn block_number(&self) -> Option<BlockNumber> {
177		let pallet_metadata = self.0.pallet_by_name(DEFAULT_FRAME_SYSTEM_PALLET_NAME);
178		pallet_metadata
179			.and_then(|inner| inner.storage())
180			.and_then(|inner| inner.entry_by_name("Number"))
181			.and_then(|number_ty| match number_ty.entry_type() {
182				StorageEntryType::Plain(ty_id) => Some(ty_id),
183				_ => None,
184			})
185			.and_then(|ty_id| self.0.types().resolve(*ty_id))
186			.and_then(|portable_type| BlockNumber::from_type_def(&portable_type.type_def))
187	}
188
189	fn fetch_metadata(chain_spec: &dyn ChainSpec) -> Result<Metadata, sc_cli::Error> {
190		let mut storage = chain_spec.build_storage()?;
191		let code_bytes = storage
192			.top
193			.remove(sp_storage::well_known_keys::CODE)
194			.ok_or("chain spec genesis does not contain code")?;
195		let opaque_metadata = fetch_latest_metadata_from_code_blob(
196			&WasmExecutor::<ParachainHostFunctions>::builder()
197				.with_allow_missing_host_functions(true)
198				.build(),
199			sp_runtime::Cow::Borrowed(code_bytes.as_slice()),
200		)
201		.map_err(|err| err.to_string())?;
202
203		Metadata::decode(&mut (*opaque_metadata).as_slice()).map_err(Into::into)
204	}
205}
206
207#[cfg(test)]
208mod tests {
209	use crate::runtime::{
210		BlockNumber, MetadataInspector, DEFAULT_FRAME_SYSTEM_PALLET_NAME,
211		DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME,
212	};
213	use codec::Decode;
214	use cumulus_client_service::ParachainHostFunctions;
215	use sc_executor::WasmExecutor;
216	use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
217
218	fn cumulus_test_runtime_metadata() -> subxt_metadata::Metadata {
219		let opaque_metadata = fetch_latest_metadata_from_code_blob(
220			&WasmExecutor::<ParachainHostFunctions>::builder()
221				.with_allow_missing_host_functions(true)
222				.build(),
223			sp_runtime::Cow::Borrowed(cumulus_test_runtime::WASM_BINARY.unwrap()),
224		)
225		.unwrap();
226
227		subxt_metadata::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap()
228	}
229
230	#[test]
231	fn test_pallet_exists() {
232		let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata());
233		assert!(metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME));
234		assert!(metadata_inspector.pallet_exists(DEFAULT_FRAME_SYSTEM_PALLET_NAME));
235	}
236
237	#[test]
238	fn test_runtime_block_number() {
239		let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata());
240		assert_eq!(metadata_inspector.block_number().unwrap(), BlockNumber::U32);
241	}
242
243	#[test]
244	fn test_aura_id_from_chain_spec_id() {
245		use crate::runtime::{aura_id_from_chain_spec_id, AuraConsensusId};
246
247		// Asset Hub Polkadot uses Ed25519
248		assert_eq!(aura_id_from_chain_spec_id("asset-hub-polkadot"), AuraConsensusId::Ed25519);
249		assert_eq!(aura_id_from_chain_spec_id("statemint"), AuraConsensusId::Ed25519);
250
251		// Other chains use Sr25519
252		assert_eq!(aura_id_from_chain_spec_id("asset-hub-kusama"), AuraConsensusId::Sr25519);
253		assert_eq!(aura_id_from_chain_spec_id("penpal-rococo-1000"), AuraConsensusId::Sr25519);
254		assert_eq!(aura_id_from_chain_spec_id("collectives-westend"), AuraConsensusId::Sr25519);
255	}
256}