mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 13:27:57 +00:00
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:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user