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/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 9429c1a04e6cb..22890db3ce7d3 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -229,9 +229,22 @@ 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, + 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 d74c8ef304da5..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; @@ -251,6 +252,28 @@ 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| 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. pub fn read_proof(&self, id: &BlockId, key: &[u8]) -> error::Result>> { self.state_at(id) @@ -711,4 +734,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(), r#"{ "events": "events" }"#); + } } 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)?; 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 +} 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/lib.rs b/substrate/runtime-support/src/lib.rs index 21823b383fa32..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,12 +45,21 @@ 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] pub mod storage; mod hashable; #[macro_use] mod event; +#[macro_use] +pub mod metadata; pub use self::storage::{StorageVec, StorageList, StorageValue, StorageMap}; pub use self::hashable::Hashable; @@ -169,7 +182,7 @@ macro_rules! impl_outer_origin { impl_outer_origin! { $(#[$attr])* pub enum $name for $trait where system = system { - $( $module ),* + $( $module ),* } } } diff --git a/substrate/runtime-support/src/metadata.rs b/substrate/runtime-support/src/metadata.rs new file mode 100644 index 0000000000000..69c6383acee1f --- /dev/null +++ b/substrate/runtime-support/src/metadata.rs @@ -0,0 +1,344 @@ +// 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 . + +use codec::{Encode, Output}; +#[cfg(feature = "std")] +use codec::{Decode, 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. +/// +/// 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 + $( $rest:tt )* + ) => { + 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 )* + ) + } + } + } +} + +/// The metadata of a runtime encoded as JSON. +#[derive(Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +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 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 { + 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 { + ( + $runtime: ident; + $( $metadata:expr ),*; + $mod:ident::$module:ident, + $( $rest:tt )* + ) => { + __impl_json_metadata!( + $runtime; + $( $metadata, )* $crate::metadata::JSONMetadata::Module { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod) + }; + $( $rest )* + ) + }; + ( + $runtime: ident; + $( $metadata:expr ),*; + $mod:ident::$module:ident + ) => { + __impl_json_metadata!( + $runtime; + $( $metadata, )* $crate::metadata::JSONMetadata::Module { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod) + }; + ) + }; + ( + $runtime: ident; + $( $metadata:expr ),*; + $mod:ident::$module:ident with Storage, + $( $rest:tt )* + ) => { + __impl_json_metadata!( + $runtime; + $( $metadata, )* $crate::metadata::JSONMetadata::ModuleWithStorage { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod), + storage: $mod::$module::<$runtime>::store_json_metadata() + }; + $( $rest )* + ) + }; + ( + $runtime: ident; + $( $metadata:expr ),*; + $mod:ident::$module:ident with Storage + ) => { + __impl_json_metadata!( + $runtime; + $( $metadata, )* $crate::metadata::JSONMetadata::ModuleWithStorage { + module: $mod::$module::<$runtime>::json_metadata(), prefix: stringify!($mod), + storage: $mod::$module::<$runtime>::store_json_metadata() + }; + ) + }; + ( + $runtime:ident; + $( $metadata:expr ),*; + ) => { + <[_]>::into_vec($crate::metadata::Box::new([ $( $metadata ),* ])) + }; +} + +#[cfg(test)] +// Do not complain about unused `dispatch` and `dispatch_aux`. +#[allow(dead_code)] +mod tests { + use super::*; + 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 for enum Call where origin: T::Origin { + fn aux_0(origin) -> Result; + } + } + + impl Module { + fn aux_0(_: T::Origin) -> 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 for enum Call where origin: T::Origin {} + } + + 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 Origin; + } + + impl Trait for TestRuntime { + type Origin = u32; + } + + impl_json_metadata!( + for TestRuntime with modules + event_module::Module, + event_module2::ModuleWithStorage with Storage + ); + + 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": "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[..]); + } + + #[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[..]); + } +} 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. diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index 0bf753a9443a4..853fe6cc1f45b 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -134,6 +134,11 @@ pub mod api { use system; impl_stubs!( version => |()| super::version(), + 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),