Skip to content

Commit

Permalink
omni-node: add metadata checks for runtime/parachain compatibility (#…
Browse files Browse the repository at this point in the history
…6450)

Get runtime's metadata, parse it and verify pallets list for a pallet
named `ParachainSystem` (for now), and block number to be the same for
both node and runtime. Ideally we'll add other pallets checks too, at
least a small set of pallets we think right away as mandatory for
parachain compatibility.
Closes: #5565

Runtime devs must be made aware that to be fully compatible with Omni
Node, certain naming conventions should be respected when defining
pallets (e.g we verify parachain-system pallet existence by searching
for a pallet with `name` `ParachainSystem` in runtime's metadata). Not
finding such a pallet will not influence the functionality yet, but by
doing these checks we could provide useful feedback for runtimes that
are clearly not implementing what's required for full parachain
compatibility with Omni Node.

- [x] parachain system check
- [x] check frame_system's metadata to ensure the block number in there
is the same as the one in the node side
- [x] add tests for the previous checking logic
- [x] update omni node polkadot-sdk docs to make these conventions
visible.
- [ ] add more pallets checks?

---------

Signed-off-by: Iulian Barbu <[email protected]>
Co-authored-by: Alexandru Vasile <[email protected]>
Co-authored-by: Michal Kucharczyk <[email protected]>
  • Loading branch information
3 people committed Dec 13, 2024
1 parent 51f578a commit 9c40c29
Show file tree
Hide file tree
Showing 16 changed files with 517 additions and 203 deletions.
39 changes: 34 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ members = [
"substrate/client/rpc-api",
"substrate/client/rpc-servers",
"substrate/client/rpc-spec-v2",
"substrate/client/runtime-utilities",
"substrate/client/service",
"substrate/client/service/test",
"substrate/client/state-db",
Expand Down Expand Up @@ -1184,6 +1185,7 @@ sc-rpc-api = { path = "substrate/client/rpc-api", default-features = false }
sc-rpc-server = { path = "substrate/client/rpc-servers", default-features = false }
sc-rpc-spec-v2 = { path = "substrate/client/rpc-spec-v2", default-features = false }
sc-runtime-test = { path = "substrate/client/executor/runtime-test" }
sc-runtime-utilities = { path = "substrate/client/runtime-utilities", default-features = true }
sc-service = { path = "substrate/client/service", default-features = false }
sc-service-test = { path = "substrate/client/service/test" }
sc-state-db = { path = "substrate/client/state-db", default-features = false }
Expand Down Expand Up @@ -1316,8 +1318,9 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" }
substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" }
substrate-test-utils = { path = "substrate/test-utils" }
substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false }
subxt = { version = "0.37", default-features = false }
subxt-signer = { version = "0.37" }
subxt = { version = "0.38", default-features = false }
subxt-metadata = { version = "0.38.0", default-features = false }
subxt-signer = { version = "0.38" }
syn = { version = "2.0.87" }
sysinfo = { version = "0.30" }
tar = { version = "0.4" }
Expand Down
16 changes: 10 additions & 6 deletions cumulus/polkadot-omni-node/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ docify = { workspace = true }
# Local
jsonrpsee = { features = ["server"], workspace = true }
parachains-common = { workspace = true, default-features = true }
scale-info = { workspace = true }
subxt-metadata = { workspace = true, default-features = true }

# Substrate
frame-benchmarking = { optional = true, workspace = true, default-features = true }
frame-benchmarking-cli = { workspace = true, default-features = true }
sp-crypto-hashing = { workspace = true }
sp-runtime = { workspace = true }
sp-core = { workspace = true, default-features = true }
sp-session = { workspace = true, default-features = true }
Expand All @@ -55,12 +58,16 @@ sc-rpc = { workspace = true, default-features = true }
sp-version = { workspace = true, default-features = true }
sp-weights = { workspace = true, default-features = true }
sc-tracing = { workspace = true, default-features = true }
sc-runtime-utilities = { workspace = true, default-features = true }
sp-storage = { workspace = true, default-features = true }
frame-system-rpc-runtime-api = { workspace = true, default-features = true }
pallet-transaction-payment = { workspace = true, default-features = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true }
sp-inherents = { workspace = true, default-features = true }
sp-api = { workspace = true, default-features = true }
sp-consensus-aura = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-wasm-interface = { workspace = true, default-features = true }
sc-consensus-manual-seal = { workspace = true, default-features = true }
sc-sysinfo = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
Expand Down Expand Up @@ -91,15 +98,12 @@ assert_cmd = { workspace = true }
nix = { features = ["signal"], workspace = true }
tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] }
wait-timeout = { workspace = true }
cumulus-test-runtime = { workspace = true }

[features]
default = []
rococo-native = [
"polkadot-cli/rococo-native",
]
westend-native = [
"polkadot-cli/westend-native",
]
rococo-native = ["polkadot-cli/rococo-native"]
westend-native = ["polkadot-cli/westend-native"]
runtime-benchmarks = [
"cumulus-primitives-core/runtime-benchmarks",
"frame-benchmarking-cli/runtime-benchmarks",
Expand Down
147 changes: 144 additions & 3 deletions cumulus/polkadot-omni-node/lib/src/common/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@

//! Runtime parameters.
use codec::Decode;
use cumulus_client_service::ParachainHostFunctions;
use sc_chain_spec::ChainSpec;
use sc_executor::WasmExecutor;
use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive};
use std::fmt::Display;
use subxt_metadata::{Metadata, StorageEntryType};

/// Expected parachain system pallet runtime type name.
pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem";
/// Expected frame system pallet runtime type name.
pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System";

/// The Aura ID used by the Aura consensus
#[derive(PartialEq)]
Expand All @@ -35,14 +47,42 @@ pub enum Consensus {
}

/// The choice of block number for the parachain omni-node.
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
pub enum BlockNumber {
/// u32
U32,
/// u64
U64,
}

impl Display for BlockNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockNumber::U32 => write!(f, "u32"),
BlockNumber::U64 => write!(f, "u64"),
}
}
}

impl Into<TypeDefPrimitive> for BlockNumber {
fn into(self) -> TypeDefPrimitive {
match self {
BlockNumber::U32 => TypeDefPrimitive::U32,
BlockNumber::U64 => TypeDefPrimitive::U64,
}
}
}

impl BlockNumber {
fn from_type_def(type_def: &TypeDef<PortableForm>) -> Option<BlockNumber> {
match type_def {
TypeDef::Primitive(TypeDefPrimitive::U32) => Some(BlockNumber::U32),
TypeDef::Primitive(TypeDefPrimitive::U64) => Some(BlockNumber::U64),
_ => None,
}
}
}

/// Helper enum listing the supported Runtime types
#[derive(PartialEq)]
pub enum Runtime {
Expand All @@ -62,7 +102,108 @@ pub trait RuntimeResolver {
pub struct DefaultRuntimeResolver;

impl RuntimeResolver for DefaultRuntimeResolver {
fn runtime(&self, _chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> {
Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519)))
fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> {
let metadata_inspector = MetadataInspector::new(chain_spec)?;
let block_number = match metadata_inspector.block_number() {
Some(inner) => inner,
None => {
log::warn!(
r#"⚠️ There isn't a runtime type named `System`, corresponding to the `frame-system`
pallet (https://docs.rs/frame-system/latest/frame_system/). Please check Omni Node docs for runtime conventions:
https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions.
Note: We'll assume a block number size of `u32`."#
);
BlockNumber::U32
},
};

if !metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME) {
log::warn!(
r#"⚠️ The parachain system pallet (https://docs.rs/crate/cumulus-pallet-parachain-system/latest) is
missing from the runtime’s metadata. Please check Omni Node docs for runtime conventions:
https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions."#
);
}

Ok(Runtime::Omni(block_number, Consensus::Aura(AuraConsensusId::Sr25519)))
}
}

struct MetadataInspector(Metadata);

impl MetadataInspector {
fn new(chain_spec: &dyn ChainSpec) -> Result<MetadataInspector, sc_cli::Error> {
MetadataInspector::fetch_metadata(chain_spec).map(MetadataInspector)
}

fn pallet_exists(&self, name: &str) -> bool {
self.0.pallet_by_name(name).is_some()
}

fn block_number(&self) -> Option<BlockNumber> {
let pallet_metadata = self.0.pallet_by_name(DEFAULT_FRAME_SYSTEM_PALLET_NAME);
pallet_metadata
.and_then(|inner| inner.storage())
.and_then(|inner| inner.entry_by_name("Number"))
.and_then(|number_ty| match number_ty.entry_type() {
StorageEntryType::Plain(ty_id) => Some(ty_id),
_ => None,
})
.and_then(|ty_id| self.0.types().resolve(*ty_id))
.and_then(|portable_type| BlockNumber::from_type_def(&portable_type.type_def))
}

fn fetch_metadata(chain_spec: &dyn ChainSpec) -> Result<Metadata, sc_cli::Error> {
let mut storage = chain_spec.build_storage()?;
let code_bytes = storage
.top
.remove(sp_storage::well_known_keys::CODE)
.ok_or("chain spec genesis does not contain code")?;
let opaque_metadata = fetch_latest_metadata_from_code_blob(
&WasmExecutor::<ParachainHostFunctions>::builder()
.with_allow_missing_host_functions(true)
.build(),
sp_runtime::Cow::Borrowed(code_bytes.as_slice()),
)
.map_err(|err| err.to_string())?;

Metadata::decode(&mut (*opaque_metadata).as_slice()).map_err(Into::into)
}
}

#[cfg(test)]
mod tests {
use crate::runtime::{
BlockNumber, MetadataInspector, DEFAULT_FRAME_SYSTEM_PALLET_NAME,
DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME,
};
use codec::Decode;
use cumulus_client_service::ParachainHostFunctions;
use sc_executor::WasmExecutor;
use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;

fn cumulus_test_runtime_metadata() -> subxt_metadata::Metadata {
let opaque_metadata = fetch_latest_metadata_from_code_blob(
&WasmExecutor::<ParachainHostFunctions>::builder()
.with_allow_missing_host_functions(true)
.build(),
sp_runtime::Cow::Borrowed(cumulus_test_runtime::WASM_BINARY.unwrap()),
)
.unwrap();

subxt_metadata::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap()
}

#[test]
fn test_pallet_exists() {
let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata());
assert!(metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME));
assert!(metadata_inspector.pallet_exists(DEFAULT_FRAME_SYSTEM_PALLET_NAME));
}

#[test]
fn test_runtime_block_number() {
let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata());
assert_eq!(metadata_inspector.block_number().unwrap(), BlockNumber::U32);
}
}
Loading

0 comments on commit 9c40c29

Please sign in to comment.