From 0f165e0e432105854a12c766174a1352a07098e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 7 Sep 2018 13:35:36 +0200 Subject: [PATCH 01/13] Adds `impl_json_metadata!` for generating all metadata of a runtime --- substrate/runtime-support/src/lib.rs | 3 + substrate/runtime-support/src/metadata.rs | 186 +++++++++++++++++++ substrate/runtime-support/src/storage/mod.rs | 1 + 3 files changed, 190 insertions(+) create mode 100644 substrate/runtime-support/src/metadata.rs diff --git a/substrate/runtime-support/src/lib.rs b/substrate/runtime-support/src/lib.rs index 21823b383fa32..5a35995a39667 100644 --- a/substrate/runtime-support/src/lib.rs +++ b/substrate/runtime-support/src/lib.rs @@ -43,10 +43,13 @@ pub use self::storage::generator::Storage as GenericStorage; #[macro_use] pub mod dispatch; +#[macro_use] pub mod storage; mod hashable; #[macro_use] mod event; +#[macro_use] +mod metadata; pub use self::storage::{StorageVec, StorageList, StorageValue, StorageMap}; pub use self::hashable::Hashable; diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs new file mode 100644 index 0000000000000..442598e4547e8 --- /dev/null +++ b/substrate/runtime-support/src/metadata.rs @@ -0,0 +1,186 @@ +/// Implements the json metadata support for the given runtime and all its modules. +/// +/// Example: +/// ```compile_fail +/// impl_json_metadata!(for RUNTIME_NAME with modules MODULE0, MODULE2, MODULE3 with Storage); +/// ``` +/// +/// In this example, just `MODULE3` implements the `Storage` trait. +#[macro_export] +macro_rules! impl_json_metadata { + ( + for $runtime:ident with modules + $( $mod:ident::$module:ident $(with Storage)* ),* + ) => { + impl $runtime { + pub fn json_metadata() -> String { + format!(r#"{{ "events": {events}, "modules": {modules} }}"#, + events = Self::outer_event_json_metadata(), + modules = __runtime_impl_json_metadata!($runtime; $( $mod::$module; )*) + ) + } + } + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __runtime_impl_json_metadata { + ( + $runtime: ident; + $mod:ident::$module:ident; + $( $rest:tt )* + ) => { + __runtime_impl_json_metadata!( + $runtime; + r#"{{ "prefix": "{}", "module": {} }}"#; + stringify!($mod), $mod::$module::<$runtime>::json_metadata(); + $( $rest )*) + }; + ( + $runtime: ident; + $format_str:expr; + $( $format_params:expr ),*; + $mod:ident::$module:ident; + $( $rest:tt )* + ) => { + __runtime_impl_json_metadata!( + $runtime; + concat!($format_str, r#", {{ "prefix": "{}", "module": {} }}"#); + $( $format_params, )* stringify!($mod), $mod::$module::<$runtime>::json_metadata(); + $( $rest )*) + }; + ( + $runtime: ident; + $mod:ident::$module:ident with Storage; + $( $rest:tt )* + ) => { + __runtime_impl_json_metadata!( + $runtime; + r#"{{ "prefix": "{}", "module": {}, "storage": {} }}"#; + stringify!($mod), $mod::$module::<$runtime>::json_metadata(), + $mod::$module::<$runtime>::store_json_metadata(); $( $rest )*) + }; + ( + $runtime: ident; + $format_str:expr; + $( $format_params:expr ),*; + $mod:ident::$module:ident with Storage; + $( $rest:tt )* + ) => { + __runtime_impl_json_metadata!( + $runtime; + concat!($format_str, r#", {{ "prefix": "{}", "module": {}, "storage": {} }}"#); + $( $format_params, )* stringify!($mod), $mod::$module::<$runtime>::json_metadata(), + $mod::$module::<$runtime>::store_json_metadata(); $( $rest )*) + }; + ( + $runtime:ident; + $format_str:expr; + $( $format_params:expr ),*; + ) => { + format!(concat!("[ ", $format_str, " ]"), $( $format_params, )*) + }; + // No modules + () => { "null" } +} + +#[cfg(test)] +// Do not complain about unused `dispatch` and `dispatch_aux`. +#[allow(dead_code)] +mod tests { + use serde; + use serde_json; + use dispatch::Result; + + mod system { + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Deserialize, Serialize)] + pub struct Event; + } + + mod event_module { + use super::*; + + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Deserialize, Serialize)] + pub struct Event { + t: T, + } + + decl_module! { + pub struct Module; + + #[derive(Serialize, Deserialize)] + pub enum Call where aux: T::PublicAux { + fn aux_0(aux) -> Result; + } + } + + impl Module { + fn aux_0(_: &T::PublicAux) -> Result { + unreachable!() + } + } + } + + mod event_module2 { + use super::*; + + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Deserialize, Serialize)] + pub struct Event { + t: T, + } + + decl_module! { + pub struct ModuleWithStorage; + } + + decl_storage! { + trait Store for ModuleWithStorage as TestStorage { + StorageMethod : u32; + } + } + } + + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Deserialize, Serialize)] + pub struct TestRuntime; + + impl_outer_event! { + pub enum TestEvent for TestRuntime { + event_module, event_module2 + } + } + + pub trait Trait { + type PublicAux; + } + + impl Trait for TestRuntime { + type PublicAux = u32; + } + + impl_json_metadata!(for TestRuntime with modules + event_module::Module, + event_module2::ModuleWithStorage with Storage); + + const EXPECTED_METADATA: &str = concat!( + r#"{ "events": { "name": "TestEvent", "items": "#, + r#"{ "system": "system::Event", "event_module": "event_module::Event", "#, + r#""event_module2": "event_module2::Event" } }, "#, + r#""modules": [ "#, + r#"{ "prefix": "event_module", "#, + r#""module": { "name": "Module", "calls": [ "#, + r#"{ "name": "Call", "functions": "#, + r#"{ "0": { "name": "aux_0", "params": [ "#, + r#"{ "name": "aux", "type": "T::PublicAux" } ], "#, + r#""description": [ ] } } } ] } }, "#, + r#"{ "prefix": "event_module2", "module": "#, + r#"{ "name": "ModuleWithStorage", "calls": [ ] } } ] }"#); + + #[test] + fn runtime_json_metadata() { + let metadata = TestRuntime::json_metadata(); + assert_eq!(EXPECTED_METADATA, metadata); + let _: serde::de::IgnoredAny = + serde_json::from_str(&metadata).expect("Is valid json syntax"); + } +} diff --git a/substrate/runtime-support/src/storage/mod.rs b/substrate/runtime-support/src/storage/mod.rs index 0d2460297fa82..3c797e23fd540 100644 --- a/substrate/runtime-support/src/storage/mod.rs +++ b/substrate/runtime-support/src/storage/mod.rs @@ -21,6 +21,7 @@ use rstd::borrow::Borrow; use runtime_io::{self, twox_128}; use codec::{Codec, Decode, KeyedVec, Input}; +#[macro_use] pub mod generator; // TODO: consider using blake256 to avoid possible preimage attack. From 8f35c770805640e28d6640271af950eb18d61dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 7 Sep 2018 17:49:27 +0200 Subject: [PATCH 02/13] Adds `json_metadata` RPC call --- Cargo.lock | 1 + substrate/client/src/client.rs | 7 +++++++ substrate/rpc/Cargo.toml | 1 + substrate/rpc/src/lib.rs | 1 + substrate/rpc/src/state/error.rs | 6 ++++++ substrate/rpc/src/state/mod.rs | 11 +++++++++++ 6 files changed, 27 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index eafcb9aa92e99..c76767d1899eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2615,6 +2615,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-client 0.1.0", "substrate-codec 0.1.0", "substrate-executor 0.1.0", diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index d74c8ef304da5..63b03f9b8f078 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -251,6 +251,13 @@ impl Client where &self.executor } + /// Returns the runtime metadata as JSON. + pub fn json_metadata(&self, id: &BlockId) -> error::Result { + self.executor.call(id, "json_metadata",&[]) + .and_then(|r| String::decode(&mut &r.return_data[..]) + .ok_or("Metadata decoding failed".into())) + } + /// Reads storage value at a given block + key, returning read proof. pub fn read_proof(&self, id: &BlockId, key: &[u8]) -> error::Result>> { self.state_at(id) diff --git a/substrate/rpc/Cargo.toml b/substrate/rpc/Cargo.toml index b209a3cd4e91a..423a0523b8b69 100644 --- a/substrate/rpc/Cargo.toml +++ b/substrate/rpc/Cargo.toml @@ -19,6 +19,7 @@ substrate-runtime-primitives = { path = "../runtime/primitives" } substrate-runtime-version = { path = "../runtime/version" } substrate-state-machine = { path = "../state-machine" } tokio = "0.1.7" +serde_json = "1.0" [dev-dependencies] assert_matches = "1.1" diff --git a/substrate/rpc/src/lib.rs b/substrate/rpc/src/lib.rs index 760b1ca567263..2d62e7e3ee070 100644 --- a/substrate/rpc/src/lib.rs +++ b/substrate/rpc/src/lib.rs @@ -29,6 +29,7 @@ extern crate substrate_runtime_primitives as runtime_primitives; extern crate substrate_state_machine as state_machine; extern crate substrate_runtime_version as runtime_version; extern crate tokio; +extern crate serde_json; #[macro_use] extern crate error_chain; diff --git a/substrate/rpc/src/state/error.rs b/substrate/rpc/src/state/error.rs index 587937ea607ab..de65e466c08b9 100644 --- a/substrate/rpc/src/state/error.rs +++ b/substrate/rpc/src/state/error.rs @@ -19,11 +19,17 @@ use rpc; use errors; +use serde_json; + error_chain! { links { Client(client::error::Error, client::error::ErrorKind) #[doc = "Client error"]; } + foreign_links { + Json(serde_json::Error); + } + errors { /// Provided block range couldn't be resolved to a list of blocks. InvalidBlockRange(from: String, to: String, details: String) { diff --git a/substrate/rpc/src/state/mod.rs b/substrate/rpc/src/state/mod.rs index 80369326de047..36a8d216b2e52 100644 --- a/substrate/rpc/src/state/mod.rs +++ b/substrate/rpc/src/state/mod.rs @@ -33,6 +33,7 @@ use rpc::futures::{stream, Future, Sink, Stream}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header}; use tokio::runtime::TaskExecutor; +use serde_json; use subscriptions::Subscriptions; @@ -63,6 +64,10 @@ build_rpc_trait! { #[rpc(name = "state_getStorageSize", alias = ["state_getStorageSizeAt", ])] fn storage_size(&self, StorageKey, Trailing) -> Result>; + /// Returns the runtime metadata as JSON. + #[rpc(name = "state_metadata", alias = ["state_metadataAt", ])] + fn json_metadata(&self, Trailing) -> Result; + /// Query historical storage entries (by key) starting from a block given as the second parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. @@ -138,6 +143,12 @@ impl StateApi for State where Ok(self.storage(key, block)?.map(|x| x.0.len() as u64)) } + fn json_metadata(&self, block: Trailing) -> Result { + let block = self.unwrap_or_best(block)?; + let metadata = self.client.json_metadata(&BlockId::Hash(block))?; + serde_json::from_str(&metadata).map_err(Into::into) + } + fn query_storage(&self, keys: Vec, from: Block::Hash, to: Trailing) -> Result>> { let to = self.unwrap_or_best(to)?; From 2c12c835fd163875151a8337aed93c900fbe4f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 7 Sep 2018 21:22:25 +0200 Subject: [PATCH 03/13] Adds simple test for the `json_metadata` RPC call --- substrate/client/src/client.rs | 25 +++++++++++++++++++++++++ substrate/test-runtime/src/lib.rs | 1 + 2 files changed, 26 insertions(+) diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index 63b03f9b8f078..7b3a841314c5e 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -718,4 +718,29 @@ mod tests { assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap()); assert_eq!(client.body(&BlockId::Number(1)).unwrap().unwrap().len(), 1) } + + #[test] + fn json_metadata() { + let client = test_client::new(); + + let mut builder = client.new_block().unwrap(); + + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 42, + nonce: 0, + }).unwrap(); + + assert!(builder.push_transfer(Transfer { + from: Keyring::Eve.to_raw_public().into(), + to: Keyring::Alice.to_raw_public().into(), + amount: 42, + nonce: 0, + }).is_err()); + + client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap(); + + assert_eq!(client.json_metadata(&BlockId::Number(1)).unwrap(), "metadata"); + } } diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index 0bf753a9443a4..c7faacf34db55 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -134,6 +134,7 @@ pub mod api { use system; impl_stubs!( version => |()| super::version(), + json_metadata => |()| String::from("metadata"), authorities => |()| system::authorities(), initialise_block => |header| system::initialise_block(header), execute_block => |block| system::execute_block(block), From 4dee84bf51e551ab2bcfaf5e4341e13af31cc30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 7 Sep 2018 21:51:58 +0200 Subject: [PATCH 04/13] Implements json metadata in the demo runtime --- demo/runtime/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 9429c1a04e6cb..f426851ddbf32 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -229,9 +229,20 @@ pub type CheckedExtrinsic = generic::CheckedExtrinsic; pub type Executive = executive::Executive; +impl_json_metadata!(for Runtime with modules + system::Module with Storage, + balances::Module with Storage, + consensus::Module with Storage, + timestamp::Module with Storage, + session::Module with Storage, + staking::Module with Storage, + democracy::Module with Storage, + council::Module with Storage); + pub mod api { impl_stubs!( version => |()| super::VERSION, + metadata => |()| super::Runtime::json_metadata(), authorities => |()| super::Consensus::authorities(), events => |()| super::System::events(), initialise_block => |header| super::Executive::initialise_block(&header), From 79d265732c85ca8d17845cf76da5e4d492d9bb90 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 8 Sep 2018 10:06:00 +0200 Subject: [PATCH 05/13] Fix indent --- demo/runtime/src/lib.rs | 20 +++++++++++--------- substrate/runtime-support/src/metadata.rs | 8 +++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index f426851ddbf32..7ac624a58d72e 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -229,15 +229,17 @@ pub type CheckedExtrinsic = generic::CheckedExtrinsic; pub type Executive = executive::Executive; -impl_json_metadata!(for Runtime with modules - system::Module with Storage, - balances::Module with Storage, - consensus::Module with Storage, - timestamp::Module with Storage, - session::Module with Storage, - staking::Module with Storage, - democracy::Module with Storage, - council::Module with Storage); +impl_json_metadata!( + for Runtime with modules + system::Module with Storage, + balances::Module with Storage, + consensus::Module with Storage, + timestamp::Module with Storage, + session::Module with Storage, + staking::Module with Storage, + democracy::Module with Storage, + council::Module with Storage +); pub mod api { impl_stubs!( diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 442598e4547e8..ada67facea717 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -158,9 +158,11 @@ mod tests { type PublicAux = u32; } - impl_json_metadata!(for TestRuntime with modules - event_module::Module, - event_module2::ModuleWithStorage with Storage); + impl_json_metadata!( + for TestRuntime with modules + event_module::Module, + event_module2::ModuleWithStorage with Storage + ); const EXPECTED_METADATA: &str = concat!( r#"{ "events": { "name": "TestEvent", "items": "#, From b879a52a09d9161d3f3d7158f3802a5b958886fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 8 Sep 2018 18:53:57 +0200 Subject: [PATCH 06/13] Adds missing copyright headers --- substrate/runtime-support/src/event.rs | 16 ++++++++++++++++ substrate/runtime-support/src/metadata.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/substrate/runtime-support/src/event.rs b/substrate/runtime-support/src/event.rs index 9d73ef966e3f8..ccbcec51c2dc6 100644 --- a/substrate/runtime-support/src/event.rs +++ b/substrate/runtime-support/src/event.rs @@ -1,3 +1,19 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + #[macro_export] macro_rules! impl_outer_event { ($(#[$attr:meta])* pub enum $name:ident for $runtime:ident { $( $module:ident ),* }) => { diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index ada67facea717..760a21c73b8ea 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -1,3 +1,19 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + /// Implements the json metadata support for the given runtime and all its modules. /// /// Example: From d41807f9d34d0f998817a7c24a41831bad4472d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 10:27:49 +0200 Subject: [PATCH 07/13] Dispatch json metadata renamings and improvements --- substrate/runtime-support/src/dispatch.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/substrate/runtime-support/src/dispatch.rs b/substrate/runtime-support/src/dispatch.rs index 203e3a1347ffe..90f6fd7a6d4ff 100644 --- a/substrate/runtime-support/src/dispatch.rs +++ b/substrate/runtime-support/src/dispatch.rs @@ -213,7 +213,7 @@ macro_rules! decl_module { } } - __impl_json_metadata! { + __dispatch_impl_json_metadata! { $mod_type $trait_instance $trait_name $call_type $origin_type {$( $(#[doc = $doc_attr])* fn $fn_name(origin $(, $param_name : $param )*) -> $result; )*} } @@ -240,7 +240,7 @@ macro_rules! __impl_decode { )* return Some($call_type:: $fn_name( $( $param_name ),* )); } - + __impl_decode!($input; $input_id; $fn_id + 1; $call_type; $($rest)*) } }; @@ -278,7 +278,7 @@ macro_rules! __impl_encode { $param_name.encode_to($dest); )* } - + __impl_encode!($dest; $self; $fn_id + 1; $call_type; $($rest)*) } }; @@ -368,15 +368,15 @@ macro_rules! __impl_outer_dispatch_common { /// Implement the `json_metadata` function. #[macro_export] #[doc(hidden)] -macro_rules! __impl_json_metadata { +macro_rules! __dispatch_impl_json_metadata { ( $mod_type:ident $trait_instance:ident $trait_name:ident $($rest:tt)* ) => { impl<$trait_instance: $trait_name> $mod_type<$trait_instance> { pub fn json_metadata() -> &'static str { - concat!(r#"{ "name": ""#, stringify!($mod_type), r#"", "call": [ "#, - __call_to_json!($($rest)*), " ] }") + concat!(r#"{ "name": ""#, stringify!($mod_type), r#"", "call": "#, + __call_to_json!($($rest)*), " }") } } } @@ -536,7 +536,7 @@ mod tests { } const EXPECTED_METADATA: &str = concat!( - r#"{ "name": "Module", "call": [ "#, + r#"{ "name": "Module", "call": "#, r#"{ "name": "Call", "functions": { "#, r#""0": { "name": "aux_0", "params": [ "#, r#"{ "name": "origin", "type": "T::Origin" }"#, @@ -551,7 +551,7 @@ mod tests { r#"{ "name": "data2", "type": "String" }"#, r#" ], "description": [ ] }"#, r#" } }"#, - r#" ] }"#, + r#" }"#, ); impl Module { @@ -581,4 +581,4 @@ mod tests { let _: serde::de::IgnoredAny = serde_json::from_str(metadata).expect("Is valid json syntax"); } -} \ No newline at end of file +} From f58727c2f8a370c4810a3a60a6f4ce7904ed119f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 11:06:11 +0200 Subject: [PATCH 08/13] Replaces `format!` & `String` with `Vec { impl $runtime { - pub fn json_metadata() -> String { - format!(r#"{{ "events": {events}, "modules": {modules} }}"#, - events = Self::outer_event_json_metadata(), - modules = __runtime_impl_json_metadata!($runtime; $( $mod::$module; )*) - ) + pub fn json_metadata() -> Vec<$crate::metadata::JSONMetadata> { + __impl_json_metadata!($runtime; + $crate::metadata::JSONMetadata::Events { + events: Self::outer_event_json_metadata() + }; + $( $rest )* + ) } } } } +#[derive(Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[allow(dead_code)] +pub enum JSONMetadata { + Events { events: &'static str }, + Module { module: &'static str, prefix: &'static str }, + ModuleWithStorage { module: &'static str, prefix: &'static str, storage: &'static str } +} + #[macro_export] #[doc(hidden)] -macro_rules! __runtime_impl_json_metadata { +macro_rules! __impl_json_metadata { ( $runtime: ident; - $mod:ident::$module:ident; + $( $metadata:expr ),*; + $mod:ident::$module:ident, $( $rest:tt )* ) => { - __runtime_impl_json_metadata!( + __impl_json_metadata!( $runtime; - r#"{{ "prefix": "{}", "module": {} }}"#; - stringify!($mod), $mod::$module::<$runtime>::json_metadata(); - $( $rest )*) + $( $metadata, )* $crate::metadata::JSONMetadata::Module { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod) + }; + $( $rest )* + ) }; ( $runtime: ident; - $format_str:expr; - $( $format_params:expr ),*; - $mod:ident::$module:ident; - $( $rest:tt )* + $( $metadata:expr ),*; + $mod:ident::$module:ident ) => { - __runtime_impl_json_metadata!( + __impl_json_metadata!( $runtime; - concat!($format_str, r#", {{ "prefix": "{}", "module": {} }}"#); - $( $format_params, )* stringify!($mod), $mod::$module::<$runtime>::json_metadata(); - $( $rest )*) + $( $metadata, )* $crate::metadata::JSONMetadata::Module { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod) + }; + ) }; ( $runtime: ident; - $mod:ident::$module:ident with Storage; + $( $metadata:expr ),*; + $mod:ident::$module:ident with Storage, $( $rest:tt )* ) => { - __runtime_impl_json_metadata!( + __impl_json_metadata!( $runtime; - r#"{{ "prefix": "{}", "module": {}, "storage": {} }}"#; - stringify!($mod), $mod::$module::<$runtime>::json_metadata(), - $mod::$module::<$runtime>::store_json_metadata(); $( $rest )*) + $( $metadata, )* $crate::metadata::JSONMetadata::ModuleWithStorage { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod), + storage: $mod::$module::<$runtime>::store_json_metadata() + }; + $( $rest )* + ) }; ( $runtime: ident; - $format_str:expr; - $( $format_params:expr ),*; - $mod:ident::$module:ident with Storage; - $( $rest:tt )* + $( $metadata:expr ),*; + $mod:ident::$module:ident with Storage ) => { - __runtime_impl_json_metadata!( + __impl_json_metadata!( $runtime; - concat!($format_str, r#", {{ "prefix": "{}", "module": {}, "storage": {} }}"#); - $( $format_params, )* stringify!($mod), $mod::$module::<$runtime>::json_metadata(), - $mod::$module::<$runtime>::store_json_metadata(); $( $rest )*) + $( $metadata, )* $crate::metadata::JSONMetadata::ModuleWithStorage { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod), + storage: $mod::$module::<$runtime>::store_json_metadata() + }; + ) }; ( $runtime:ident; - $format_str:expr; - $( $format_params:expr ),*; + $( $metadata:expr ),*; ) => { - format!(concat!("[ ", $format_str, " ]"), $( $format_params, )*) + vec![ $( $metadata ),* ] }; - // No modules - () => { "null" } } #[cfg(test)] // Do not complain about unused `dispatch` and `dispatch_aux`. #[allow(dead_code)] mod tests { - use serde; - use serde_json; + use super::*; use dispatch::Result; mod system { @@ -123,16 +136,13 @@ mod tests { } decl_module! { - pub struct Module; - - #[derive(Serialize, Deserialize)] - pub enum Call where aux: T::PublicAux { - fn aux_0(aux) -> Result; + pub struct Module for enum Call where origin: T::Origin { + fn aux_0(origin) -> Result; } } impl Module { - fn aux_0(_: &T::PublicAux) -> Result { + fn aux_0(_: T::Origin) -> Result { unreachable!() } } @@ -147,7 +157,7 @@ mod tests { } decl_module! { - pub struct ModuleWithStorage; + pub struct ModuleWithStorage for enum Call where origin: T::Origin {} } decl_storage! { @@ -167,11 +177,11 @@ mod tests { } pub trait Trait { - type PublicAux; + type Origin; } impl Trait for TestRuntime { - type PublicAux = u32; + type Origin = u32; } impl_json_metadata!( @@ -180,25 +190,38 @@ mod tests { event_module2::ModuleWithStorage with Storage ); - const EXPECTED_METADATA: &str = concat!( - r#"{ "events": { "name": "TestEvent", "items": "#, - r#"{ "system": "system::Event", "event_module": "event_module::Event", "#, - r#""event_module2": "event_module2::Event" } }, "#, - r#""modules": [ "#, - r#"{ "prefix": "event_module", "#, - r#""module": { "name": "Module", "calls": [ "#, + const EXPECTED_METADATA: &[JSONMetadata] = &[ + JSONMetadata::Events { + events: concat!( + r#"{ "name": "TestEvent", "items": "#, + r#"{ "system": "system::Event", "#, + r#""event_module": "event_module::Event", "#, + r#""event_module2": "event_module2::Event" } }"#) + }, + JSONMetadata::Module { + module: concat!( + r#"{ "name": "Module", "call": "#, r#"{ "name": "Call", "functions": "#, r#"{ "0": { "name": "aux_0", "params": [ "#, - r#"{ "name": "aux", "type": "T::PublicAux" } ], "#, - r#""description": [ ] } } } ] } }, "#, - r#"{ "prefix": "event_module2", "module": "#, - r#"{ "name": "ModuleWithStorage", "calls": [ ] } } ] }"#); + r#"{ "name": "origin", "type": "T::Origin" } ], "#, + r#""description": [ ] } } } }"# + ), + prefix: "event_module" + }, + JSONMetadata::ModuleWithStorage { + module: r#"{ "name": "ModuleWithStorage", "call": { "name": "Call", "functions": { } } }"#, + prefix: "event_module2", + storage: concat!( + r#"{ "prefix": "TestStorage", "items": { "#, + r#""StorageMethod": { "description": [ ], "modifier": null, "type": "u32" }"#, + r#" } }"# + ) + } + ]; #[test] fn runtime_json_metadata() { let metadata = TestRuntime::json_metadata(); - assert_eq!(EXPECTED_METADATA, metadata); - let _: serde::de::IgnoredAny = - serde_json::from_str(&metadata).expect("Is valid json syntax"); + assert_eq!(EXPECTED_METADATA, &metadata[..]); } } From 8360c25dd397018ff488c5eb899922372584bd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 12:12:48 +0200 Subject: [PATCH 09/13] Implements `Encode` and `Decode` for JSONMetadata --- substrate/runtime-support/src/metadata.rs | 91 ++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 249f9f93b0884..8bdcde38030b9 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +use codec::{Encode, Decode, Output, Input}; + /// Implements the json metadata support for the given runtime and all its modules. /// /// Example: @@ -41,15 +43,93 @@ macro_rules! impl_json_metadata { } } +/// The metadata of a runtime encoded as JSON. #[derive(Eq, PartialEq)] #[cfg_attr(feature = "std", derive(Debug))] -#[allow(dead_code)] pub enum JSONMetadata { Events { events: &'static str }, Module { module: &'static str, prefix: &'static str }, ModuleWithStorage { module: &'static str, prefix: &'static str, storage: &'static str } } +impl Encode for JSONMetadata { + fn encode_to(&self, dest: &mut W) { + match self { + JSONMetadata::Events { events } => { + 0i8.encode_to(dest); + events.encode_to(dest); + }, + JSONMetadata::Module { module, prefix } => { + 1i8.encode_to(dest); + prefix.encode_to(dest); + module.encode_to(dest); + }, + JSONMetadata::ModuleWithStorage { module, prefix, storage } => { + 2i8.encode_to(dest); + prefix.encode_to(dest); + module.encode_to(dest); + storage.encode_to(dest); + } + } + } +} + +/// Utility struct for making `JSONMetadata` decodeable. +#[derive(Eq, PartialEq, Debug)] +#[cfg(feature = "std")] +pub enum JSONMetadataDecodable { + Events { events: String }, + Module { module: String, prefix: String }, + ModuleWithStorage { module: String, prefix: String, storage: String } +} + +#[cfg(feature = "std")] +impl Decode for JSONMetadataDecodable { + fn decode(input: &mut I) -> Option { + i8::decode(input).and_then(|variant| { + match variant { + 0 => String::decode(input) + .and_then(|events| Some(JSONMetadataDecodable::Events { events })), + 1 => String::decode(input) + .and_then(|prefix| String::decode(input).map(|v| (prefix, v))) + .and_then(|(prefix, module)| Some(JSONMetadataDecodable::Module { prefix, module })), + 2 => String::decode(input) + .and_then(|prefix| String::decode(input).map(|v| (prefix, v))) + .and_then(|(prefix, module)| String::decode(input).map(|v| (prefix, module, v))) + .and_then(|(prefix, module, storage)| Some(JSONMetadataDecodable::ModuleWithStorage { prefix, module, storage })), + _ => None, + } + }) + } +} + +#[cfg(test)] +impl PartialEq for JSONMetadataDecodable { + fn eq(&self, other: &JSONMetadata) -> bool { + match (self, other) { + ( + JSONMetadataDecodable::Events { events: left }, + JSONMetadata::Events { events: right } + ) => { + left == right + }, + ( + JSONMetadataDecodable::Module { prefix: lpre, module: lmod }, + JSONMetadata::Module { prefix: rpre, module: rmod } + ) => { + lpre == rpre && lmod == rmod + }, + ( + JSONMetadataDecodable::ModuleWithStorage { prefix: lpre, module: lmod, storage: lstore }, + JSONMetadata::ModuleWithStorage { prefix: rpre, module: rmod, storage: rstore } + ) => { + lpre == rpre && lmod == rmod && lstore == rstore + }, + _ => false, + } + } +} + #[macro_export] #[doc(hidden)] macro_rules! __impl_json_metadata { @@ -224,4 +304,13 @@ mod tests { let metadata = TestRuntime::json_metadata(); assert_eq!(EXPECTED_METADATA, &metadata[..]); } + + #[test] + fn json_metadata_encode_and_decode() { + let metadata = TestRuntime::json_metadata(); + let metadata_encoded = metadata.encode(); + let metadata_decoded = Vec::::decode(&mut &metadata_encoded[..]); + + assert_eq!(&metadata_decoded.unwrap()[..], &metadata[..]); + } } From 2c50f89e8b10dbfc0a3334fcbb81bcc579d5f365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 12:33:05 +0200 Subject: [PATCH 10/13] Make `impl_json_metadata!` compileable on `no_std` --- substrate/runtime-support/src/lib.rs | 10 ++++++++++ substrate/runtime-support/src/metadata.rs | 10 ++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/substrate/runtime-support/src/lib.rs b/substrate/runtime-support/src/lib.rs index f81087e0ad0ed..959338952e304 100644 --- a/substrate/runtime-support/src/lib.rs +++ b/substrate/runtime-support/src/lib.rs @@ -17,6 +17,10 @@ //! Support code for the runtime. #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(alloc))] + +#[cfg(not(feature = "std"))] +extern crate alloc; #[cfg(feature = "std")] extern crate serde; @@ -41,6 +45,12 @@ extern crate substrate_codec_derive; pub extern crate substrate_codec as codec; pub use self::storage::generator::Storage as GenericStorage; +#[cfg(feature = "std")] +pub mod alloc { + pub use std::boxed; + pub use std::vec; +} + #[macro_use] pub mod dispatch; #[macro_use] diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 8bdcde38030b9..5c67b8278b1d2 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -15,6 +15,12 @@ // along with Substrate. If not, see . use codec::{Encode, Decode, Output, Input}; +use alloc; + +/// Make Box available on `std` and `no_std`. +pub type Box = alloc::boxed::Box; +/// Make Vec available on `std` and `no_std`. +pub type Vec = alloc::vec::Vec; /// Implements the json metadata support for the given runtime and all its modules. /// @@ -31,7 +37,7 @@ macro_rules! impl_json_metadata { $( $rest:tt )* ) => { impl $runtime { - pub fn json_metadata() -> Vec<$crate::metadata::JSONMetadata> { + pub fn json_metadata() -> $crate::metadata::Vec<$crate::metadata::JSONMetadata> { __impl_json_metadata!($runtime; $crate::metadata::JSONMetadata::Events { events: Self::outer_event_json_metadata() @@ -191,7 +197,7 @@ macro_rules! __impl_json_metadata { $runtime:ident; $( $metadata:expr ),*; ) => { - vec![ $( $metadata ),* ] + <[_]>::into_vec($crate::metadata::Box::new([ $( $metadata ),* ])) }; } From 8e728397714f8d025a44292d89481cad687e52c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 14:09:12 +0200 Subject: [PATCH 11/13] Adapt the client to decode the correct type for `json_metadata` --- demo/runtime/src/lib.rs | 2 +- substrate/client/src/client.rs | 22 +++++++++++++++++++--- substrate/runtime-support/src/metadata.rs | 20 ++++++++++++++++++++ substrate/test-runtime/src/lib.rs | 4 +++- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 7ac624a58d72e..22890db3ce7d3 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -244,7 +244,7 @@ impl_json_metadata!( pub mod api { impl_stubs!( version => |()| super::VERSION, - metadata => |()| super::Runtime::json_metadata(), + json_metadata => |()| super::Runtime::json_metadata(), authorities => |()| super::Consensus::authorities(), events => |()| super::System::events(), initialise_block => |header| super::Executive::initialise_block(&header), diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index 7b3a841314c5e..62e2c0c7083c3 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -23,6 +23,7 @@ use primitives::AuthorityId; use runtime_primitives::{bft::Justification, generic::{BlockId, SignedBlock, Block as RuntimeBlock}}; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One, As, NumberFor}; use runtime_primitives::BuildStorage; +use runtime_support::metadata::JSONMetadataDecodable; use primitives::{KeccakHasher, RlpCodec}; use primitives::storage::{StorageKey, StorageData}; use codec::Decode; @@ -254,8 +255,23 @@ impl Client where /// Returns the runtime metadata as JSON. pub fn json_metadata(&self, id: &BlockId) -> error::Result { self.executor.call(id, "json_metadata",&[]) - .and_then(|r| String::decode(&mut &r.return_data[..]) - .ok_or("Metadata decoding failed".into())) + .and_then(|r| Vec::::decode(&mut &r.return_data[..]) + .ok_or("JSON Metadata decoding failed".into())) + .and_then(|metadata| { + let mut json = metadata.into_iter().enumerate().fold(String::from("{"), + |mut json, (i, m)| { + if i > 0 { + json.push_str(","); + } + let (mtype, val) = m.into_json_string(); + json.push_str(&format!(r#" "{}": {}"#, mtype, val)); + json + } + ); + json.push_str(" }"); + + Ok(json) + }) } /// Reads storage value at a given block + key, returning read proof. @@ -741,6 +757,6 @@ mod tests { client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap(); - assert_eq!(client.json_metadata(&BlockId::Number(1)).unwrap(), "metadata"); + assert_eq!(client.json_metadata(&BlockId::Number(1)).unwrap(), r#"{ "events": "events" }"#); } } diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 5c67b8278b1d2..7a4aca734b251 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -89,6 +89,26 @@ pub enum JSONMetadataDecodable { ModuleWithStorage { module: String, prefix: String, storage: String } } +#[cfg(feature = "std")] +impl JSONMetadataDecodable { + /// Returns the instance as JSON string. + /// The first value of the tuple is the name of the metadata type and the second in the JSON string. + pub fn into_json_string(self) -> (&'static str, String) { + match self { + JSONMetadataDecodable::Events { events } => { + ("events", events) + }, + JSONMetadataDecodable::Module { prefix, module } => { + ("module", format!(r#"{{ "prefix": "{}", "module": {} }}"#, prefix, module)) + }, + JSONMetadataDecodable::ModuleWithStorage { prefix, module, storage } => { + ("moduleWithStorage", + format!(r#"{{ "prefix": "{}", "module": {}, "storage": {} }}"#, prefix, module, storage)) + } + } + } +} + #[cfg(feature = "std")] impl Decode for JSONMetadataDecodable { fn decode(input: &mut I) -> Option { diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index c7faacf34db55..31712c9f6c61a 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -134,7 +134,9 @@ pub mod api { use system; impl_stubs!( version => |()| super::version(), - json_metadata => |()| String::from("metadata"), + json_metadata => |()| vec![ + ::runtime_support::metadata::JSONMetadata::Events { events: r#""events""# } + ], authorities => |()| system::authorities(), initialise_block => |header| system::initialise_block(header), execute_block => |block| system::execute_block(block), From 8bf5ba25f033e3b4ec5f2cfd1e4fad2d03b0cf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 10 Sep 2018 14:38:11 +0200 Subject: [PATCH 12/13] Fixes compile error and warning --- substrate/runtime-support/src/metadata.rs | 4 +++- substrate/test-runtime/src/lib.rs | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 7a4aca734b251..9d03c07472896 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use codec::{Encode, Decode, Output, Input}; +use codec::{Encode, Output}; +#[cfg(feature = "std")] +use codec::{Decode, Input}; use alloc; /// Make Box available on `std` and `no_std`. diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index 31712c9f6c61a..853fe6cc1f45b 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -134,9 +134,11 @@ pub mod api { use system; impl_stubs!( version => |()| super::version(), - json_metadata => |()| vec![ - ::runtime_support::metadata::JSONMetadata::Events { events: r#""events""# } - ], + json_metadata => |()| { + let mut vec = ::runtime_support::metadata::Vec::new(); + vec.push(::runtime_support::metadata::JSONMetadata::Events { events: r#""events""# }); + vec + }, authorities => |()| system::authorities(), initialise_block => |header| system::initialise_block(header), execute_block => |block| system::execute_block(block), From f6ad1c1bcddb3243f8772f9fe916df128dbea2af Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 10 Sep 2018 18:33:47 +0200 Subject: [PATCH 13/13] Whitespace --- substrate/runtime-support/src/metadata.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs index 9d03c07472896..69c6383acee1f 100644 --- a/substrate/runtime-support/src/metadata.rs +++ b/substrate/runtime-support/src/metadata.rs @@ -40,12 +40,12 @@ macro_rules! impl_json_metadata { ) => { impl $runtime { pub fn json_metadata() -> $crate::metadata::Vec<$crate::metadata::JSONMetadata> { - __impl_json_metadata!($runtime; - $crate::metadata::JSONMetadata::Events { - events: Self::outer_event_json_metadata() - }; - $( $rest )* - ) + __impl_json_metadata!($runtime; + $crate::metadata::JSONMetadata::Events { + events: Self::outer_event_json_metadata() + }; + $( $rest )* + ) } } }