Implements json_metadata RPC call (#692)

* Adds `impl_json_metadata!` for generating all metadata of a runtime

* Adds `json_metadata` RPC call

* Adds simple test for the `json_metadata` RPC call

* Implements json metadata in the demo runtime

* Fix indent

* Adds missing copyright headers

* Dispatch json metadata renamings and improvements

* Replaces `format!` & `String` with `Vec<JSONMetadata`

* Implements `Encode` and `Decode` for JSONMetadata

* Make `impl_json_metadata!` compileable on `no_std`

* Adapt the client to decode the correct type for `json_metadata`

* Fixes compile error and warning

* Whitespace
This commit is contained in:
Bastian Köcher
2018-09-10 18:34:17 +02:00
committed by Gav Wood
parent fea750511e
commit ba23d033a1
13 changed files with 470 additions and 10 deletions
+1
View File
@@ -2727,6 +2727,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",
+13
View File
@@ -270,9 +270,22 @@ pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Index, Call>;
pub type Executive = executive::Executive<Runtime, Block, Balances, Balances,
(((((((), Treasury), Council), Democracy), Staking), Session), Timestamp)>;
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(),
initialise_block => |header| super::Executive::initialise_block(&header),
apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic),
+48
View File
@@ -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::{Encode, Decode};
@@ -251,6 +252,28 @@ impl<B, E, Block> Client<B, E, Block> where
&self.executor
}
/// Returns the runtime metadata as JSON.
pub fn json_metadata(&self, id: &BlockId<Block>) -> error::Result<String> {
self.executor.call(id, "json_metadata",&[])
.and_then(|r| Vec::<JSONMetadataDecodable>::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<Block>, key: &[u8]) -> error::Result<Vec<Vec<u8>>> {
self.state_at(id)
@@ -756,4 +779,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" }"#);
}
}
+1
View File
@@ -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"
+1
View File
@@ -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;
@@ -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) {
+11
View File
@@ -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<Hash>) -> Result<Option<u64>>;
/// Returns the runtime metadata as JSON.
#[rpc(name = "state_metadata", alias = ["state_metadataAt", ])]
fn json_metadata(&self, Trailing<Hash>) -> Result<serde_json::Value>;
/// 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<B, E, Block> StateApi<Block::Hash> for State<B, E, Block> where
Ok(self.storage(key, block)?.map(|x| x.0.len() as u64))
}
fn json_metadata(&self, block: Trailing<Block::Hash>) -> Result<serde_json::Value> {
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<StorageKey>, from: Block::Hash, to: Trailing<Block::Hash>) -> Result<Vec<StorageChangeSet<Block::Hash>>> {
let to = self.unwrap_or_best(to)?;
@@ -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<T: Trait> Module<T> {
@@ -581,4 +581,4 @@ mod tests {
let _: serde::de::IgnoredAny =
serde_json::from_str(metadata).expect("Is valid json syntax");
}
}
}
@@ -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 <http://www.gnu.org/licenses/>.
#[macro_export]
macro_rules! impl_outer_event {
($(#[$attr:meta])* pub enum $name:ident for $runtime:ident { $( $module:ident ),* }) => {
+14 -1
View File
@@ -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 ),*
}
}
}
@@ -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 <http://www.gnu.org/licenses/>.
use codec::{Encode, Output};
#[cfg(feature = "std")]
use codec::{Decode, Input};
use alloc;
/// Make Box available on `std` and `no_std`.
pub type Box<T> = alloc::boxed::Box<T>;
/// Make Vec available on `std` and `no_std`.
pub type Vec<T> = alloc::vec::Vec<T>;
/// 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<W: Output>(&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<I: Input>(input: &mut I) -> Option<Self> {
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<JSONMetadata> 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: T,
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn aux_0(origin) -> Result;
}
}
impl<T: Trait> Module<T> {
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: T,
}
decl_module! {
pub struct ModuleWithStorage<T: Trait> for enum Call where origin: T::Origin {}
}
decl_storage! {
trait Store for ModuleWithStorage<T: Trait> 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<TestRuntime>", "#,
r#""event_module2": "event_module2::Event<TestRuntime>" } }"#)
},
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::<JSONMetadataDecodable>::decode(&mut &metadata_encoded[..]);
assert_eq!(&metadata_decoded.unwrap()[..], &metadata[..]);
}
}
@@ -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.
@@ -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),