Make API backwards compatible with CC (#1697)

* Rework how a runtime api calls into the runtime

Now we generate a default implementation for each api call that calls
a generated method `method_runtime_api_impl`. This newly generated
method is the one that will be implemented by the `impl_runtime_apis`
macro in the runtime for the client side.

* Support `changed_in` to change runtime api function signatures

* Update documentation

* Fixes tests

* Implement checking the api version with a predicate

* Make the implementation backwards compatible with CC

* Update wasm files after merge

* Check for wasm runtime differences by building master and current branch

* Update spec_version and wasm files

* Fixes

* Revert my changes

* Remove `patch.crates-io` from test-runtime
This commit is contained in:
Bastian Köcher
2019-02-06 11:47:47 +01:00
committed by Gav Wood
parent 1ba7e35c18
commit 190393d476
25 changed files with 731 additions and 224 deletions
@@ -378,3 +378,35 @@ mod impl_two_traits_with_same_name {
```
*/
}
mod changed_at_unknown_version {
/*!
```compile_fail
#[macro_use]
extern crate substrate_client;
extern crate substrate_test_client as test_client;
extern crate sr_primitives as runtime_primitives;
extern crate substrate_primitives as primitives;
use runtime_primitives::traits::GetNodeBlockType;
use test_client::runtime::Block;
/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType`
/// trait are done by the `construct_runtime!` macro in a real runtime.
struct Runtime {}
impl GetNodeBlockType for Runtime {
type NodeBlock = Block;
}
decl_runtime_apis! {
pub trait Api {
#[changed_in(2)]
fn test(data: u64);
fn test(data: u64);
}
}
fn main() {}
```
*/
}
@@ -18,6 +18,7 @@ use utils::{
generate_crate_access, generate_hidden_includes, generate_runtime_mod_name_for_trait,
fold_fn_decl_for_client_side, unwrap_or_error, extract_parameter_names_types_and_borrows,
generate_native_call_generator_fn_name, return_type_extract_type,
generate_method_runtime_api_impl_name
};
use proc_macro;
@@ -27,8 +28,9 @@ use quote::quote;
use syn::{
spanned::Spanned, parse_macro_input, parse::{Parse, ParseStream, Result, Error}, ReturnType,
fold::{self, Fold}, FnDecl, parse_quote, ItemTrait, Generics, GenericParam, Attribute,
visit::{Visit, self}, FnArg, Pat, TraitBound, Meta, NestedMeta, Lit, TraitItem, Ident, Type
fold::{self, Fold}, parse_quote, ItemTrait, Generics, GenericParam, Attribute, FnArg,
visit::{Visit, self}, Pat, TraitBound, Meta, NestedMeta, Lit, TraitItem, Ident, Type,
TraitItemMethod
};
use std::collections::HashMap;
@@ -44,9 +46,16 @@ const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS";
/// The `core_trait` attribute.
const CORE_TRAIT_ATTRIBUTE: &str = "core_trait";
/// The `api_version` attribute.
/// Is used to set the current version of the trait.
const API_VERSION_ATTRIBUTE: &str = "api_version";
/// The `changed_in` attribute.
/// Is used when the function signature changed between different versions of a trait.
/// This attribute should be placed on the old signature of the function.
const CHANGED_IN_ATTRIBUTE: &str = "changed_in";
/// All attributes that we support in the declaratio of a runtime api trait.
const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = &[CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE];
const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = &[
CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE, CHANGED_IN_ATTRIBUTE
];
/// The structure used for parsing the runtime api declarations.
struct RuntimeApiDecls {
@@ -279,6 +288,20 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> TokenStream {
generate_runtime_api_version(v as u32)
}));
let id = generate_runtime_api_id(&decl.ident.to_string());
// Remove methods that have the `changed_in` attribute as they are not required for the
// runtime anymore.
decl.items = decl.items.iter_mut().filter_map(|i| match i {
TraitItem::Method(ref mut method) => {
if remove_supported_attributes(&mut method.attrs).contains_key(CHANGED_IN_ATTRIBUTE) {
None
} else {
Some(TraitItem::Method(method.clone()))
}
}
r => Some(r.clone()),
}).collect();
let native_call_generators = unwrap_or_error(generate_native_call_generators(&decl));
result.push(quote!(
@@ -306,19 +329,164 @@ struct ToClientSideDecl<'a> {
block_id: &'a TokenStream,
crate_: &'a TokenStream,
found_attributes: &'a mut HashMap<&'static str, Attribute>,
/// Any error that we found while converting this declaration.
errors: &'a mut Vec<TokenStream>,
}
impl<'a> Fold for ToClientSideDecl<'a> {
fn fold_fn_decl(&mut self, input: FnDecl) -> FnDecl {
let input = fold_fn_decl_for_client_side(
input,
impl<'a> ToClientSideDecl<'a> {
fn fold_item_trait_items(&mut self, items: Vec<TraitItem>) -> Vec<TraitItem> {
let mut result = Vec::new();
items.into_iter().for_each(|i| match i {
TraitItem::Method(method) => {
let (fn_decl, fn_impl) = self.fold_trait_item_method(method);
result.push(fn_decl.into());
if let Some(fn_impl) = fn_impl {
result.push(fn_impl.into());
}
},
r => result.push(r),
});
result
}
fn fold_trait_item_method(&mut self, method: TraitItemMethod) -> (TraitItemMethod, Option<TraitItemMethod>) {
let fn_impl = self.create_method_runtime_api_impl(method.clone());
let fn_decl = self.create_method_decl(method);
(fn_decl, fn_impl)
}
/// Takes the given method and creates a `method_runtime_api_impl` method that will be
/// implemented in the runtime for the client side.
fn create_method_runtime_api_impl(&mut self, mut method: TraitItemMethod) -> Option<TraitItemMethod> {
if remove_supported_attributes(&mut method.attrs).contains_key(CHANGED_IN_ATTRIBUTE) {
return None;
}
let fn_decl = &method.sig.decl;
let ret_type = return_type_extract_type(&fn_decl.output);
// Get types and if the value is borrowed from all parameters.
// If there is an error, we push it as the block to the user.
let param_types = match extract_parameter_names_types_and_borrows(fn_decl) {
Ok(res) => res.into_iter().map(|v| {
let ty = v.1;
let borrow = v.2;
quote!( #borrow #ty )
}).collect::<Vec<_>>(),
Err(e) => {
self.errors.push(e.to_compile_error());
Vec::new()
}
};
let name = generate_method_runtime_api_impl_name(&method.sig.ident);
let block_id = self.block_id;
let crate_ = self.crate_;
Some(
parse_quote!{
#[doc(hidden)]
fn #name(
&self,
at: &#block_id,
params: Option<( #( #param_types ),* )>,
params_encoded: Vec<u8>
) -> #crate_::error::Result<#crate_::runtime_api::NativeOrEncoded<#ret_type>>;
}
)
}
/// Takes the method declared by the user and creates the declaration we require for the runtime
/// api client side. This method will call by default the `method_runtime_api_impl` for doing
/// the actual call into the runtime.
fn create_method_decl(&mut self, mut method: TraitItemMethod) -> TraitItemMethod {
let params = match extract_parameter_names_types_and_borrows(&method.sig.decl) {
Ok(res) => res.into_iter().map(|v| v.0).collect::<Vec<_>>(),
Err(e) => {
self.errors.push(e.to_compile_error());
Vec::new()
}
};
let params2 = params.clone();
let ret_type = return_type_extract_type(&method.sig.decl.output);
method.sig.decl = fold_fn_decl_for_client_side(
method.sig.decl.clone(),
&self.block_id,
&self.crate_
);
let name_impl = generate_method_runtime_api_impl_name(&method.sig.ident);
let crate_ = self.crate_;
fold::fold_fn_decl(self, input)
let found_attributes = remove_supported_attributes(&mut method.attrs);
// If the method has a `changed_in` attribute, we need to alter the method name to
// `method_before_version_VERSION`.
let (native_handling, param_tuple) = match get_changed_in(&found_attributes) {
Ok(Some(version)) => {
// Make sure that the `changed_in` version is at least the current `api_version`.
if get_api_version(&self.found_attributes).ok() < Some(version) {
self.errors.push(
Error::new(
method.span(),
"`changed_in` version can not be greater than the `api_version`",
).to_compile_error()
);
}
let ident = Ident::new(
&format!("{}_before_version_{}", method.sig.ident, version),
method.sig.ident.span()
);
method.sig.ident = ident;
method.attrs.push(parse_quote!( #[deprecated] ));
let panic = format!("Calling `{}` should not return a native value!", method.sig.ident);
(quote!( panic!(#panic) ), quote!( None ))
},
Ok(None) => (quote!( Ok(n) ), quote!( Some(( #( #params2 ),* )) )),
Err(e) => {
self.errors.push(e.to_compile_error());
(quote!( unimplemented!() ), quote!( None ))
}
};
let function_name = method.sig.ident.to_string();
// Generate the default implementation that calls the `method_runtime_api_impl` method.
method.default = Some(
parse_quote! {
{
let runtime_api_impl_params_encoded =
#crate_::runtime_api::Encode::encode(&( #( &#params ),* ));
self.#name_impl(at, #param_tuple, runtime_api_impl_params_encoded)
.and_then(|r|
match r {
#crate_::runtime_api::NativeOrEncoded::Native(n) => {
#native_handling
},
#crate_::runtime_api::NativeOrEncoded::Encoded(r) => {
<#ret_type as #crate_::runtime_api::Decode>::decode(&mut &r[..])
.ok_or_else(||
#crate_::error::ErrorKind::CallResultDecode(
#function_name
).into()
)
}
}
)
}
}
);
method
}
}
impl<'a> Fold for ToClientSideDecl<'a> {
fn fold_item_trait(&mut self, mut input: ItemTrait) -> ItemTrait {
extend_generics_with_block(&mut input.generics);
@@ -344,6 +512,7 @@ impl<'a> Fold for ToClientSideDecl<'a> {
// The client side trait is only required when compiling with the feature `std` or `test`.
input.attrs.push(parse_quote!( #[cfg(any(feature = "std", test))] ));
input.items = self.fold_item_trait_items(input.items);
fold::fold_item_trait(self, input)
}
@@ -412,12 +581,16 @@ fn generate_runtime_info_impl(trait_: &ItemTrait, version: u64) -> TokenStream {
)
}
/// Get changed in version from the user given attribute or `Ok(None)`, if no attribute was given.
fn get_changed_in(found_attributes: &HashMap<&'static str, Attribute>) -> Result<Option<u64>> {
found_attributes.get(&CHANGED_IN_ATTRIBUTE)
.map(|v| parse_runtime_api_version(v).map(Some))
.unwrap_or(Ok(None))
}
/// Get the api version from the user given attribute or `Ok(1)`, if no attribute was given.
fn get_api_version(found_attributes: &HashMap<&'static str, Attribute>) -> Result<u64> {
match found_attributes.get(&API_VERSION_ATTRIBUTE) {
Some(attr) => parse_runtime_api_version(attr),
None => Ok(1),
}
found_attributes.get(&API_VERSION_ATTRIBUTE).map(parse_runtime_api_version).unwrap_or(Ok(1))
}
/// Generate the decleration of the trait for the client side.
@@ -430,12 +603,14 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> TokenStream {
let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID);
let block_id = quote!( #crate_::runtime_api::BlockId<Block> );
let mut found_attributes = HashMap::new();
let mut errors = Vec::new();
let decl = {
let mut to_client_side = ToClientSideDecl {
crate_: &crate_,
block_id: &block_id,
found_attributes: &mut found_attributes
found_attributes: &mut found_attributes,
errors: &mut errors,
};
to_client_side.fold_item_trait(decl)
};
@@ -446,7 +621,7 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> TokenStream {
api_version.map(|v| generate_runtime_info_impl(&decl, v))
);
result.push(quote!( #decl #runtime_info ));
result.push(quote!( #decl #runtime_info #( #errors )* ));
}
quote!( #( #result )* )
@@ -16,8 +16,8 @@
use utils::{
unwrap_or_error, generate_crate_access, generate_hidden_includes,
generate_runtime_mod_name_for_trait, fold_fn_decl_for_client_side, generate_unique_pattern,
extract_parameter_names_types_and_borrows, generate_native_call_generator_fn_name
generate_runtime_mod_name_for_trait, generate_method_runtime_api_impl_name,
extract_parameter_names_types_and_borrows, generate_native_call_generator_fn_name, return_type_extract_type
};
use proc_macro;
@@ -28,7 +28,7 @@ use quote::quote;
use syn::{
spanned::Spanned, parse_macro_input, Ident, Type, ItemImpl, MethodSig, Path,
ImplItem, parse::{Parse, ParseStream, Result, Error}, PathArguments, GenericArgument, TypePath,
fold::{self, Fold}, FnDecl, parse_quote, FnArg
fold::{self, Fold}, parse_quote
};
use std::{collections::HashSet, iter};
@@ -300,11 +300,11 @@ fn generate_runtime_api_base_structures(impls: &[ItemImpl]) -> Result<TokenStrea
res
}
fn has_api<A: #crate_::runtime_api::RuntimeApiInfo + ?Sized>(
fn runtime_version_at(
&self,
at: &#block_id
) -> #crate_::error::Result<bool> where Self: Sized {
self.call.runtime_version_at(at).map(|r| r.has_api::<A>())
) -> #crate_::error::Result<#crate_::runtime_api::RuntimeVersion> {
self.call.runtime_version_at(at)
}
}
@@ -336,8 +336,8 @@ fn generate_runtime_api_base_structures(impls: &[ItemImpl]) -> Result<TokenStrea
at: &#block_id,
function: &'static str,
args: Vec<u8>,
native_call: NC,
) -> #crate_::error::Result<R> {
native_call: Option<NC>,
) -> #crate_::error::Result<#crate_::runtime_api::NativeOrEncoded<R>> {
let res = unsafe {
self.call.call_api_at(
at,
@@ -345,21 +345,7 @@ fn generate_runtime_api_base_structures(impls: &[ItemImpl]) -> Result<TokenStrea
args,
&mut *self.changes.borrow_mut(),
&mut *self.initialised_block.borrow_mut(),
Some(native_call),
).and_then(|r|
match r {
#crate_::runtime_api::NativeOrEncoded::Native(n) => {
Ok(n)
},
#crate_::runtime_api::NativeOrEncoded::Encoded(r) => {
R::decode(&mut &r[..])
.ok_or_else(||
#crate_::error::ErrorKind::CallResultDecode(
function
).into()
)
}
}
native_call,
)
};
@@ -446,50 +432,69 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> {
fold::fold_type_path(self, new_ty_path)
}
fn fold_fn_decl(&mut self, input: FnDecl) -> FnDecl {
let input = fold_fn_decl_for_client_side(
input,
&self.node_block_id,
&generate_crate_access(HIDDEN_INCLUDES_ID)
);
fold::fold_fn_decl(self, input)
}
fn fold_impl_item_method(&mut self, mut input: syn::ImplItemMethod) -> syn::ImplItemMethod {
let block = {
let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID);
let mut generated_name_counter = 0;
// Replace `_` with unique patterns and collect all patterns.
let arg_names = input.sig.decl.inputs.iter_mut().filter_map(|i| match i {
FnArg::Captured(ref mut arg) => Some(&mut arg.pat),
_ => None,
}).map(|p| {
*p = generate_unique_pattern(p.clone(), &mut generated_name_counter);
p.clone()
}).collect::<Vec<_>>();
let runtime_mod_path = self.runtime_mod_path;
let runtime = self.runtime_type;
let arg_names2 = arg_names.clone();
let fn_name = prefix_function_with_trait(self.impl_trait_ident, &input.sig.ident);
let native_call_generator_ident =
generate_native_call_generator_fn_name(&input.sig.ident);
let trait_generic_arguments = self.trait_generic_arguments;
let node_block = self.node_block;
let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID);
let block_id = self.node_block_id;
// Generate the access to the native parameters
let param_tuple_access = if input.sig.decl.inputs.len() == 1 {
vec![ quote!( p ) ]
} else {
input.sig.decl.inputs.iter().enumerate().map(|(i, _)| {
let i = syn::Index::from(i);
quote!( p.#i )
}).collect::<Vec<_>>()
};
let (param_types, error) = match extract_parameter_names_types_and_borrows(&input.sig.decl) {
Ok(res) => (
res.into_iter().map(|v| {
let ty = v.1;
let borrow = v.2;
quote!( #borrow #ty )
}).collect::<Vec<_>>(),
None
),
Err(e) => (Vec::new(), Some(e.to_compile_error())),
};
// Rewrite the input parameters.
input.sig.decl.inputs = parse_quote! {
&self, at: &#block_id, params: Option<( #( #param_types ),* )>, params_encoded: Vec<u8>
};
input.sig.ident = generate_method_runtime_api_impl_name(&input.sig.ident);
let ret_type = return_type_extract_type(&input.sig.decl.output);
// Generate the correct return type.
input.sig.decl.output = parse_quote!(
-> #crate_::error::Result<#crate_::runtime_api::NativeOrEncoded<#ret_type>>
);
// Generate the new method implementation that calls into the runime.
parse_quote!(
{
let args = #crate_::runtime_api::Encode::encode(&( #( &#arg_names ),* ));
// Get the error to the user (if we have one).
#( #error )*
self.call_api_at(
at,
#fn_name,
args,
#runtime_mod_path #native_call_generator_ident ::
<#runtime, #node_block #(, #trait_generic_arguments )*> (
#( #arg_names2 ),*
)
params_encoded,
params.map(|p| {
#runtime_mod_path #native_call_generator_ident ::
<#runtime, #node_block #(, #trait_generic_arguments )*> (
#( #param_tuple_access ),*
)
})
)
}
)
+11 -2
View File
@@ -159,7 +159,11 @@ pub fn impl_runtime_apis(input: TokenStream) -> TokenStream {
///
/// To support versioning of the traits, the macro supports the attribute `#[api_version(1)]`.
/// The attribute supports any `u32` as version. By default, each trait is at version `1`, if no
/// version is provided.
/// version is provided. We also support chaning the signature of a method. This signature
/// change is highlighted with the `#[changed_in(2)]` attribute above a method. A method that is
/// tagged with this attribute is callable by the name `METHOD_before_version_VERSION`. This
/// method will only support calling into wasm, trying to call into native will fail (change the
/// spec version!). Such a method also does not need to be implemented in the runtime.
///
/// ```rust
/// #[macro_use]
@@ -171,8 +175,13 @@ pub fn impl_runtime_apis(input: TokenStream) -> TokenStream {
/// pub trait Balance {
/// /// Get the balance.
/// fn get_balance() -> u64;
/// /// Set the balance.
/// /// Set balance.
/// fn set_balance(val: u64);
/// /// Set balance, old version.
/// ///
/// /// Is callable by `set_balance_before_version_2`.
/// #[changed_in(2)]
/// fn set_balance(val: u8);
/// /// In version 2, we added this new function.
/// fn increase_balance(val: u64);
/// }
@@ -58,6 +58,11 @@ pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident {
Ident::new(&format!("runtime_decl_for_{}", trait_.to_string()), Span::call_site())
}
/// Generates a name for a method that needs to be implemented in the runtime for the client side.
pub fn generate_method_runtime_api_impl_name(method: &Ident) -> Ident {
Ident::new(&format!("{}_runtime_api_impl", method.to_string()), Span::call_site())
}
/// Get the type of a `syn::ReturnType`.
pub fn return_type_extract_type(rt: &syn::ReturnType) -> Type {
match rt {