Embed runtime version as a custom section (#8688)

* emit a custom section from impl_runtime_apis!

This change emits a custom section from the impl_runtime_apis! proc macro.

Each implemented API will result to emitting a link section `runtime_apis`.
During linking all sections with this name will be concatenated and
placed into the final wasm binary under the same name.

* Introduce `runtime_version` proc macro

This macro takes an existing `RuntimeVersion` const declaration, parses
it and emits the version information in form of a linking section.
Ultimately such a linking section will result into a custom wasm
section.

* Parse custom wasm section for runtime version

* Apply suggestions from code review

Co-authored-by: David <dvdplm@gmail.com>

* Fix sc-executor integration tests

* Nits

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Refactor apis section deserialization

* Fix version decoding

* Reuse uncompressed value for CallInWasm

* Log on decompression error

* Simplify if

* Reexport proc-macro from sp_version

* Merge ReadRuntimeVersionExt

* Export `read_embedded_version`

* Fix test

* Simplify searching for custom section

Co-authored-by: David <dvdplm@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Sergei Shulepov
2021-05-12 15:39:08 +02:00
committed by GitHub
parent 4f7c1df31e
commit 0849bcce0e
27 changed files with 833 additions and 289 deletions
@@ -633,8 +633,11 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result<TokenStream>
/// runtime apis.
fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> {
let mut result = Vec::with_capacity(impls.len());
let mut sections = Vec::with_capacity(impls.len());
let mut processed_traits = HashSet::new();
let c = generate_crate_access(HIDDEN_INCLUDES_ID);
for impl_ in impls {
let mut path = extend_with_runtime_decl_path(
extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(),
@@ -667,12 +670,21 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> {
#( #attrs )*
(#id, #version)
));
}
let c = generate_crate_access(HIDDEN_INCLUDES_ID);
sections.push(quote!(
#( #attrs )*
const _: () = {
// All sections with the same name are going to be merged by concatenation.
#[link_section = "runtime_apis"]
static SECTION_CONTENTS: [u8; 12] = #c::serialize_runtime_api_info(#id, #version);
};
));
}
Ok(quote!(
const RUNTIME_API_VERSIONS: #c::ApisVec = #c::create_apis_vec!([ #( #result ),* ]);
#( #sections )*
))
}
+43
View File
@@ -613,6 +613,49 @@ pub trait RuntimeApiInfo {
const VERSION: u32;
}
/// The number of bytes required to encode a [`RuntimeApiInfo`].
///
/// 8 bytes for `ID` and 4 bytes for a version.
pub const RUNTIME_API_INFO_SIZE: usize = 12;
/// Crude and simple way to serialize the `RuntimeApiInfo` into a bunch of bytes.
pub const fn serialize_runtime_api_info(id: [u8; 8], version: u32) -> [u8; RUNTIME_API_INFO_SIZE] {
let version = version.to_le_bytes();
let mut r = [0; RUNTIME_API_INFO_SIZE];
r[0] = id[0];
r[1] = id[1];
r[2] = id[2];
r[3] = id[3];
r[4] = id[4];
r[5] = id[5];
r[6] = id[6];
r[7] = id[7];
r[8] = version[0];
r[9] = version[1];
r[10] = version[2];
r[11] = version[3];
r
}
/// Deserialize the runtime API info serialized by [`serialize_runtime_api_info`].
pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8; 8], u32) {
use sp_std::convert::TryInto;
let id: [u8; 8] = bytes[0..8]
.try_into()
.expect("the source slice size is equal to the dest array length; qed");
let version = u32::from_le_bytes(
bytes[8..12]
.try_into()
.expect("the source slice size is equal to the array length; qed"),
);
(id, version)
}
#[derive(codec::Encode, codec::Decode)]
pub struct OldRuntimeVersion {
pub spec_name: RuntimeString,
+25 -36
View File
@@ -26,7 +26,7 @@ use std::{
pub use sp_externalities::{Externalities, ExternalitiesExt};
/// Code execution engine.
pub trait CodeExecutor: Sized + Send + Sync + CallInWasm + Clone + 'static {
pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'static {
/// Externalities error type.
type Error: Display + Debug + Send + Sync + 'static;
@@ -123,53 +123,42 @@ impl std::fmt::Display for CodeNotFound {
}
}
/// `Allow` or `Disallow` missing host functions when instantiating a WASM blob.
#[derive(Clone, Copy, Debug)]
pub enum MissingHostFunctions {
/// Any missing host function will be replaced by a stub that returns an error when
/// being called.
Allow,
/// Any missing host function will result in an error while instantiating the WASM blob,
Disallow,
}
impl MissingHostFunctions {
/// Are missing host functions allowed?
pub fn allowed(self) -> bool {
matches!(self, Self::Allow)
}
}
/// Something that can call a method in a WASM blob.
pub trait CallInWasm: Send + Sync {
/// Call the given `method` in the given `wasm_blob` using `call_data` (SCALE encoded arguments)
/// to decode the arguments for the method.
/// A trait that allows reading version information from the binary.
pub trait ReadRuntimeVersion: Send + Sync {
/// Reads the runtime version information from the given wasm code.
///
/// Returns the SCALE encoded return value of the method.
/// The version information may be embedded into the wasm binary itself. If it is not present,
/// then this function may fallback to the legacy way of reading the version.
///
/// # Note
/// The legacy mechanism involves instantiating the passed wasm runtime and calling `Core_version`
/// on it. This is a very expensive operation.
///
/// If `code_hash` is `Some(_)` the `wasm_code` module and instance will be cached internally,
/// otherwise it is thrown away after the call.
fn call_in_wasm(
/// `ext` is only needed in case the calling into runtime happens. Otherwise it is ignored.
///
/// Compressed wasm blobs are supported and will be decompressed if needed. If uncompression fails,
/// the error is returned.
///
/// # Errors
///
/// If the version information present in binary, but is corrupted - returns an error.
///
/// Otherwise, if there is no version information present, and calling into the runtime takes
/// place, then an error would be returned if `Core_version` is not provided.
fn read_runtime_version(
&self,
wasm_code: &[u8],
code_hash: Option<Vec<u8>>,
method: &str,
call_data: &[u8],
ext: &mut dyn Externalities,
missing_host_functions: MissingHostFunctions,
) -> Result<Vec<u8>, String>;
}
sp_externalities::decl_extension! {
/// The call-in-wasm extension to register/retrieve from the externalities.
pub struct CallInWasmExt(Box<dyn CallInWasm>);
/// An extension that provides functionality to read version information from a given wasm blob.
pub struct ReadRuntimeVersionExt(Box<dyn ReadRuntimeVersion>);
}
impl CallInWasmExt {
/// Creates a new instance of `Self`.
pub fn new<T: CallInWasm + 'static>(inner: T) -> Self {
impl ReadRuntimeVersionExt {
/// Creates a new instance of the extension given a version determinator instance.
pub fn new<T: ReadRuntimeVersion + 'static>(inner: T) -> Self {
Self(Box::new(inner))
}
}
+2
View File
@@ -24,6 +24,7 @@ libsecp256k1 = { version = "0.3.4", optional = true }
sp-state-machine = { version = "0.9.0", optional = true, path = "../state-machine" }
sp-wasm-interface = { version = "3.0.0", path = "../wasm-interface", default-features = false }
sp-runtime-interface = { version = "3.0.0", default-features = false, path = "../runtime-interface" }
sp-maybe-compressed-blob = { version = "3.0.0", optional = true, path = "../maybe-compressed-blob" }
sp-trie = { version = "3.0.0", optional = true, path = "../trie" }
sp-externalities = { version = "0.9.0", optional = true, path = "../externalities" }
sp-tracing = { version = "3.0.0", default-features = false, path = "../tracing" }
@@ -47,6 +48,7 @@ std = [
"sp-runtime-interface/std",
"sp-externalities",
"sp-wasm-interface/std",
"sp-maybe-compressed-blob",
"sp-tracing/std",
"tracing/std",
"tracing-core/std",
+31 -20
View File
@@ -38,7 +38,7 @@ use tracing;
#[cfg(feature = "std")]
use sp_core::{
crypto::Pair,
traits::{CallInWasmExt, TaskExecutorExt, RuntimeSpawnExt},
traits::{TaskExecutorExt, RuntimeSpawnExt},
offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt},
hexdisplay::HexDisplay,
storage::ChildInfo,
@@ -70,6 +70,8 @@ mod batch_verifier;
#[cfg(feature = "std")]
use batch_verifier::BatchVerifier;
const LOG_TARGET: &str = "runtime::io";
/// Error verifying ECDSA signature
#[derive(Encode, Decode)]
pub enum EcdsaVerifyError {
@@ -432,6 +434,9 @@ pub trait Trie {
/// Interface that provides miscellaneous functions for communicating between the runtime and the node.
#[runtime_interface]
pub trait Misc {
// NOTE: We use the target 'runtime' for messages produced by general printing functions, instead
// of LOG_TARGET.
/// Print a number.
fn print_num(val: u64) {
log::debug!(target: "runtime", "{}", val);
@@ -456,28 +461,34 @@ pub trait Misc {
///
/// # Performance
///
/// Calling this function is very expensive and should only be done very occasionally.
/// For getting the runtime version, it requires instantiating the wasm blob and calling a
/// function in this blob.
/// This function may be very expensive to call depending on the wasm binary. It may be
/// relatively cheap if the wasm binary contains version information. In that case, uncompression
/// of the wasm blob is the dominating factor.
///
/// If the wasm binary does not have the version information attached, then a legacy mechanism
/// may be involved. This means that a runtime call will be performed to query the version.
///
/// Calling into the runtime may be incredible expensive and should be approached with care.
fn runtime_version(&mut self, wasm: &[u8]) -> Option<Vec<u8>> {
// Create some dummy externalities, `Core_version` should not write data anyway.
use sp_core::traits::ReadRuntimeVersionExt;
let mut ext = sp_state_machine::BasicExternalities::default();
self.extension::<CallInWasmExt>()
.expect("No `CallInWasmExt` associated for the current context!")
.call_in_wasm(
wasm,
None,
"Core_version",
&[],
&mut ext,
// If a runtime upgrade introduces new host functions that are not provided by
// the node, we should not fail at instantiation. Otherwise nodes that are
// updated could run this successfully and it could lead to a storage root
// mismatch when importing this block.
sp_core::traits::MissingHostFunctions::Allow,
)
.ok()
match self
.extension::<ReadRuntimeVersionExt>()
.expect("No `ReadRuntimeVersionExt` associated for the current context!")
.read_runtime_version(wasm, &mut ext)
{
Ok(v) => Some(v),
Err(err) => {
log::debug!(
target: LOG_TARGET,
"cannot read version from the given runtime: {}",
err,
);
None
}
}
}
}
@@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-runtime-interface = { version = "3.0.0", path = "../" }
sc-executor = { version = "0.9.0", path = "../../../client/executor" }
sc-executor-common = { version = "0.9.0", path = "../../../client/executor/common" }
sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" }
sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0", path = "../test-wasm-deprecated" }
sp-state-machine = { version = "0.9.0", path = "../../state-machine" }
@@ -24,7 +24,7 @@ use sp_runtime_interface_test_wasm::{wasm_binary_unwrap, test_api::HostFunctions
use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap;
use sp_wasm_interface::HostFunctions as HostFunctionsT;
use sc_executor::CallInWasm;
use sc_executor_common::runtime_blob::RuntimeBlob;
use std::{collections::HashSet, sync::{Arc, Mutex}};
@@ -46,14 +46,15 @@ fn call_wasm_method_with_result<HF: HostFunctionsT>(
8,
None,
);
executor.call_in_wasm(
binary,
None,
method,
&[],
&mut ext_ext,
sp_core::traits::MissingHostFunctions::Disallow,
).map_err(|e| format!("Failed to execute `{}`: {}", method, e))?;
executor
.uncached_call(
RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"),
&mut ext_ext,
false,
method,
&[],
)
.map_err(|e| format!("Failed to execute `{}`: {}", method, e))?;
Ok(ext)
}
@@ -178,7 +178,7 @@ mod execution {
use codec::{Decode, Encode, Codec};
use sp_core::{
storage::ChildInfo, NativeOrEncoded, NeverNativeValue, hexdisplay::HexDisplay,
traits::{CodeExecutor, CallInWasmExt, RuntimeCode, SpawnNamed},
traits::{CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed},
};
use sp_externalities::Extensions;
@@ -339,7 +339,7 @@ mod execution {
runtime_code: &'a RuntimeCode,
spawn_handle: impl SpawnNamed + Send + 'static,
) -> Self {
extensions.register(CallInWasmExt::new(exec.clone()));
extensions.register(ReadRuntimeVersionExt::new(exec.clone()));
extensions.register(sp_core::traits::TaskExecutorExt::new(spawn_handle));
Self {
@@ -943,15 +943,11 @@ mod tests {
}
}
impl sp_core::traits::CallInWasm for DummyCodeExecutor {
fn call_in_wasm(
impl sp_core::traits::ReadRuntimeVersion for DummyCodeExecutor {
fn read_runtime_version(
&self,
_: &[u8],
_: Option<Vec<u8>>,
_: &str,
_: &[u8],
_: &mut dyn Externalities,
_: sp_core::traits::MissingHostFunctions,
) -> std::result::Result<Vec<u8>, String> {
unimplemented!("Not required in tests.")
}
+1
View File
@@ -20,6 +20,7 @@ serde = { version = "1.0.101", optional = true, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
sp-std = { version = "3.0.0", default-features = false, path = "../std" }
sp-runtime = { version = "3.0.0", default-features = false, path = "../runtime" }
sp-version-proc-macro = { version = "3.0.0", default-features = false, path = "proc-macro" }
[features]
default = ["std"]
@@ -0,0 +1,26 @@
[package]
name = "sp-version-proc-macro"
version = "3.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Macro for defining a runtime version."
documentation = "https://docs.rs/sp-api-proc-macro"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
proc-macro = true
[dependencies]
quote = "1.0.3"
syn = { version = "1.0.58", features = ["full", "fold", "extra-traits", "visit"] }
proc-macro2 = "1.0.6"
proc-macro-crate = "1.0.0"
codec = { package = "parity-scale-codec", version = "2.0.0", features = [ "derive" ] }
[dev-dependencies]
sp-version = { version = "3.0.0", path = ".." }
@@ -0,0 +1,279 @@
// This file is part of Substrate.
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use codec::Encode;
use syn::{
Expr, ExprLit, FieldValue, ItemConst, Lit,
parse::{Result, Error},
parse_macro_input,
spanned::Spanned as _,
};
use quote::quote;
use proc_macro2::{TokenStream, Span};
/// This macro accepts a `const` item that has a struct initializer expression of `RuntimeVersion`-like type.
/// The macro will pass through this declaration and append an item declaration that will
/// lead to emitting a wasm custom section with the contents of `RuntimeVersion`.
pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item = parse_macro_input!(input as ItemConst);
decl_runtime_version_impl_inner(item)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
fn decl_runtime_version_impl_inner(item: ItemConst) -> Result<TokenStream> {
let runtime_version = ParseRuntimeVersion::parse_expr(&*item.expr)?.build(item.expr.span())?;
let link_section =
generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version");
Ok(quote! {
#item
#link_section
})
}
/// This is a duplicate of `sp_version::RuntimeVersion`. We cannot unfortunately use the original
/// declaration, because if we directly depend on `sp_version` from this proc-macro cargo will
/// enable `std` feature even for `no_std` wasm runtime builds.
///
/// One difference from the original definition is the `apis` field. Since we don't actually parse
/// `apis` from this macro it will always be emitteed as empty. An empty vector can be encoded as
/// a zero-byte, thus `u8` is sufficient here.
#[derive(Encode)]
struct RuntimeVersion {
spec_name: String,
impl_name: String,
authoring_version: u32,
spec_version: u32,
impl_version: u32,
apis: u8,
transaction_version: u32,
}
#[derive(Default, Debug)]
struct ParseRuntimeVersion {
spec_name: Option<String>,
impl_name: Option<String>,
authoring_version: Option<u32>,
spec_version: Option<u32>,
impl_version: Option<u32>,
transaction_version: Option<u32>,
}
impl ParseRuntimeVersion {
fn parse_expr(init_expr: &Expr) -> Result<ParseRuntimeVersion> {
let init_expr = match init_expr {
Expr::Struct(ref e) => e,
_ => {
return Err(Error::new(
init_expr.span(),
"expected a struct initializer expression",
));
}
};
let mut parsed = ParseRuntimeVersion::default();
for field_value in init_expr.fields.iter() {
parsed.parse_field_value(field_value)?;
}
Ok(parsed)
}
fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<()> {
let field_name = match field_value.member {
syn::Member::Named(ref ident) => ident,
syn::Member::Unnamed(_) => {
return Err(Error::new(
field_value.span(),
"only named members must be used",
));
}
};
fn parse_once<T>(
value: &mut Option<T>,
field: &FieldValue,
parser: impl FnOnce(&Expr) -> Result<T>,
) -> Result<()> {
if value.is_some() {
return Err(Error::new(
field.span(),
"field is already initialized before",
));
} else {
*value = Some(parser(&field.expr)?);
Ok(())
}
}
if field_name == "spec_name" {
parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?;
} else if field_name == "impl_name" {
parse_once(&mut self.impl_name, field_value, Self::parse_str_literal)?;
} else if field_name == "authoring_version" {
parse_once(
&mut self.authoring_version,
field_value,
Self::parse_num_literal,
)?;
} else if field_name == "spec_version" {
parse_once(&mut self.spec_version, field_value, Self::parse_num_literal)?;
} else if field_name == "impl_version" {
parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?;
} else if field_name == "transaction_version" {
parse_once(
&mut self.transaction_version,
field_value,
Self::parse_num_literal,
)?;
} else if field_name == "apis" {
// Intentionally ignored
//
// The definition will pass through for the declaration, however, it won't get into
// the "runtime_version" custom section. `impl_runtime_apis` is responsible for generating
// a custom section with the supported runtime apis descriptor.
} else {
return Err(Error::new(field_name.span(), "unknown field"));
}
Ok(())
}
fn parse_num_literal(expr: &Expr) -> Result<u32> {
let lit = match *expr {
Expr::Lit(ExprLit {
lit: Lit::Int(ref lit),
..
}) => lit,
_ => {
return Err(Error::new(
expr.span(),
"only numeric literals (e.g. `10`) are supported here",
));
}
};
lit.base10_parse::<u32>()
}
fn parse_str_literal(expr: &Expr) -> Result<String> {
let mac = match *expr {
Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac,
_ => {
return Err(Error::new(
expr.span(),
"a macro expression is expected here",
));
}
};
let lit: ExprLit = mac.parse_body().map_err(|e| {
Error::new(
e.span(),
format!(
"a single literal argument is expected, but parsing is failed: {}",
e
),
)
})?;
match lit.lit {
Lit::Str(ref lit) => Ok(lit.value()),
_ => Err(Error::new(
lit.span(),
"only string literals are supported here",
)),
}
}
fn build(self, span: Span) -> Result<RuntimeVersion> {
macro_rules! required {
($e:expr) => {
$e.ok_or_else(||
{
Error::new(
span,
format!("required field '{}' is missing", stringify!($e)),
)
}
)?
};
}
let Self {
spec_name,
impl_name,
authoring_version,
spec_version,
impl_version,
transaction_version,
} = self;
Ok(RuntimeVersion {
spec_name: required!(spec_name),
impl_name: required!(impl_name),
authoring_version: required!(authoring_version),
spec_version: required!(spec_version),
impl_version: required!(impl_version),
transaction_version: required!(transaction_version),
apis: 0,
})
}
}
fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
let len = contents.len();
quote! {
const _: () = {
#[link_section = #section_name]
static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use codec::DecodeAll;
use std::borrow::Cow;
#[test]
fn version_can_be_deserialized() {
let version_bytes = RuntimeVersion {
spec_name: "hello".to_string(),
impl_name: "world".to_string(),
authoring_version: 10,
spec_version: 265,
impl_version: 1,
apis: 0,
transaction_version: 2,
}
.encode();
assert_eq!(
sp_version::RuntimeVersion::decode_all(&mut &version_bytes[..]).unwrap(),
sp_version::RuntimeVersion {
spec_name: "hello".into(),
impl_name: "world".into(),
authoring_version: 10,
spec_version: 265,
impl_version: 1,
apis: Cow::Owned(vec![]),
transaction_version: 2,
},
);
}
}
@@ -0,0 +1,32 @@
// This file is part of Substrate.
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A proc-macro that generates a custom wasm section from a given RuntimeVersion declaration
//!
//! This macro is re-exported from the `sp_version::runtime_version` and intended to be used from
//! there. Documentation can also be found there.
#![recursion_limit = "512"]
use proc_macro::TokenStream;
mod decl_runtime_version;
#[proc_macro_attribute]
pub fn runtime_version(_: TokenStream, input: TokenStream) -> TokenStream {
decl_runtime_version::decl_runtime_version_impl(input)
}
+45
View File
@@ -35,6 +35,51 @@ pub use sp_std;
#[cfg(feature = "std")]
use sp_runtime::{traits::Block as BlockT, generic::BlockId};
/// An attribute that accepts a version declaration of a runtime and generates a custom wasm section
/// with the equivalent contents.
///
/// The custom section allows to read the version of the runtime without having to execute any code.
/// Instead, the generated custom section can be relatively easily parsed from the wasm binary. The
/// identifier of the custom section is "runtime_version".
///
/// A shortcoming of this macro is that it is unable to embed information regarding supported APIs.
/// This is supported by the `construct_runtime!` macro.
///
/// This macro accepts a const item like the following:
///
/// ```rust
/// use sp_version::{create_runtime_str, RuntimeVersion};
///
/// #[sp_version::runtime_version]
/// pub const VERSION: RuntimeVersion = RuntimeVersion {
/// spec_name: create_runtime_str!("test"),
/// impl_name: create_runtime_str!("test"),
/// authoring_version: 10,
/// spec_version: 265,
/// impl_version: 1,
/// apis: RUNTIME_API_VERSIONS,
/// transaction_version: 2,
/// };
///
/// # const RUNTIME_API_VERSIONS: sp_version::ApisVec = sp_version::create_apis_vec!([]);
/// ```
///
/// It will pass it through and add code required for emitting a custom section. The information that
/// will go into the custom section is parsed from the item declaration. Due to that, the macro is
/// somewhat rigid in terms of the code it accepts. There are the following considerations:
///
/// - The `spec_name` and `impl_name` must be set by a macro-like expression. The name of the macro
/// doesn't matter though.
///
/// - `authoring_version`, `spec_version`, `impl_version` and `transaction_version` must be set
/// by a literal. Literal must be an integer. No other expressions are allowed there. In particular,
/// you can't supply a constant variable.
///
/// - `apis` doesn't have any specific constraints. This is because this information doesn't get into
/// the custom section and is not parsed.
///
pub use sp_version_proc_macro::runtime_version;
/// The identity of a particular API interface that the runtime might provide.
pub type ApiId = [u8; 8];