diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a5493f0739..7bf2fcba01 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -6275,6 +6275,7 @@ name = "substrate-rpc-primitives" version = "2.0.0" dependencies = [ "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-primitives 2.0.0", ] diff --git a/substrate/client/rpc/api/src/chain/mod.rs b/substrate/client/rpc/api/src/chain/mod.rs index 73f4388236..fd7576f988 100644 --- a/substrate/client/rpc/api/src/chain/mod.rs +++ b/substrate/client/rpc/api/src/chain/mod.rs @@ -22,7 +22,7 @@ use jsonrpc_core::Result as RpcResult; use jsonrpc_core::futures::Future; use jsonrpc_derive::rpc; use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; -use rpc_primitives::number; +use rpc_primitives::{number::NumberOrHex, list::ListOrValue}; use self::error::{FutureResult, Result}; pub use self::gen_client::Client as ChainClient; @@ -45,7 +45,10 @@ pub trait ChainApi { /// /// By default returns latest block hash. #[rpc(name = "chain_getBlockHash", alias("chain_getHead"))] - fn block_hash(&self, hash: Option>) -> Result>; + fn block_hash( + &self, + hash: Option>>, + ) -> Result>>; /// Get hash of the last finalized block in the canon chain. #[rpc(name = "chain_getFinalizedHead", alias("chain_getFinalisedHead"))] @@ -67,7 +70,11 @@ pub trait ChainApi { name = "chain_unsubscribeNewHeads", alias("unsubscribe_newHead", "chain_unsubscribeNewHead") )] - fn unsubscribe_new_heads(&self, metadata: Option, id: SubscriptionId) -> RpcResult; + fn unsubscribe_new_heads( + &self, + metadata: Option, + id: SubscriptionId, + ) -> RpcResult; /// New head subscription #[pubsub( @@ -85,5 +92,9 @@ pub trait ChainApi { name = "chain_unsubscribeFinalizedHeads", alias("chain_unsubscribeFinalisedHeads") )] - fn unsubscribe_finalized_heads(&self, metadata: Option, id: SubscriptionId) -> RpcResult; + fn unsubscribe_finalized_heads( + &self, + metadata: Option, + id: SubscriptionId, + ) -> RpcResult; } diff --git a/substrate/client/rpc/src/chain/mod.rs b/substrate/client/rpc/src/chain/mod.rs index 20806c6219..c420360576 100644 --- a/substrate/client/rpc/src/chain/mod.rs +++ b/substrate/client/rpc/src/chain/mod.rs @@ -37,7 +37,7 @@ use client::{ }; use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; use primitives::{H256, Blake2Hasher}; -use rpc_primitives::number; +use rpc_primitives::{number::NumberOrHex, list::ListOrValue}; use sr_primitives::{ generic::{BlockId, SignedBlock}, traits::{Block as BlockT, Header, NumberFor}, @@ -79,7 +79,7 @@ trait ChainBackend: Send + Sync + 'static /// By default returns latest block hash. fn block_hash( &self, - number: Option>>, + number: Option>>, ) -> Result> { Ok(match number { None => Some(self.client().info().chain.best_hash), @@ -211,8 +211,19 @@ impl ChainApi, Block::Hash, Block::Header, Sig self.backend.block(hash) } - fn block_hash(&self, number: Option>>) -> Result> { - self.backend.block_hash(number) + fn block_hash( + &self, + number: Option>>> + ) -> Result>> { + match number { + None => self.backend.block_hash(None).map(ListOrValue::Value), + Some(ListOrValue::Value(number)) => self.backend.block_hash(Some(number)).map(ListOrValue::Value), + Some(ListOrValue::List(list)) => Ok(ListOrValue::List(list + .into_iter() + .map(|number| self.backend.block_hash(Some(number))) + .collect::>()? + )) + } } fn finalized_head(&self) -> Result { diff --git a/substrate/client/rpc/src/chain/tests.rs b/substrate/client/rpc/src/chain/tests.rs index 8b46befee6..4f7c1f65cf 100644 --- a/substrate/client/rpc/src/chain/tests.rs +++ b/substrate/client/rpc/src/chain/tests.rs @@ -21,6 +21,7 @@ use test_client::{ consensus::BlockOrigin, runtime::{H256, Block, Header}, }; +use rpc_primitives::list::ListOrValue; #[test] fn should_return_header() { @@ -120,34 +121,39 @@ fn should_return_block_hash() { assert_matches!( api.block_hash(None.into()), - Ok(Some(ref x)) if x == &client.genesis_hash() + Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() ); assert_matches!( - api.block_hash(Some(0u64.into()).into()), - Ok(Some(ref x)) if x == &client.genesis_hash() + api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), + Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() ); assert_matches!( - api.block_hash(Some(1u64.into()).into()), - Ok(None) + api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), + Ok(ListOrValue::Value(None)) ); let block = client.new_block(Default::default()).unwrap().bake().unwrap(); client.import(BlockOrigin::Own, block.clone()).unwrap(); assert_matches!( - api.block_hash(Some(0u64.into()).into()), - Ok(Some(ref x)) if x == &client.genesis_hash() + api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), + Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() ); assert_matches!( - api.block_hash(Some(1u64.into()).into()), - Ok(Some(ref x)) if x == &block.hash() + api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), + Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() ); assert_matches!( - api.block_hash(Some(::primitives::U256::from(1u64).into()).into()), - Ok(Some(ref x)) if x == &block.hash() + api.block_hash(Some(ListOrValue::Value(primitives::U256::from(1u64).into())).into()), + Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() + ); + + assert_matches!( + api.block_hash(Some(vec![0u64.into(), 1.into(), 2.into()].into())), + Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None] ); } diff --git a/substrate/primitives/rpc/Cargo.toml b/substrate/primitives/rpc/Cargo.toml index 5216882b1a..eb4cd5a723 100644 --- a/substrate/primitives/rpc/Cargo.toml +++ b/substrate/primitives/rpc/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" [dependencies] serde = { version = "1.0.101", features = ["derive"] } primitives = { package = "substrate-primitives", path = "../core" } + +[dev-dependencies] +serde_json = "1.0.41" diff --git a/substrate/primitives/rpc/src/lib.rs b/substrate/primitives/rpc/src/lib.rs index 667b1b1b4b..74007bea8e 100644 --- a/substrate/primitives/rpc/src/lib.rs +++ b/substrate/primitives/rpc/src/lib.rs @@ -19,3 +19,20 @@ #![warn(missing_docs)] pub mod number; +pub mod list; + +/// A util function to assert the result of serialization and deserialization is the same. +#[cfg(test)] +pub(crate) fn assert_deser(s: &str, expected: T) where + T: std::fmt::Debug + serde::ser::Serialize + serde::de::DeserializeOwned + PartialEq +{ + assert_eq!( + serde_json::from_str::(s).unwrap(), + expected + ); + assert_eq!( + serde_json::to_string(&expected).unwrap(), + s + ); +} + diff --git a/substrate/primitives/rpc/src/list.rs b/substrate/primitives/rpc/src/list.rs new file mode 100644 index 0000000000..5cd7b18705 --- /dev/null +++ b/substrate/primitives/rpc/src/list.rs @@ -0,0 +1,75 @@ +// Copyright 2019 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 . + +//! RPC a lenient list or value type. + +use serde::{Serialize, Deserialize}; + +/// RPC list or value wrapper. +/// +/// For some RPCs it's convenient to call them with either +/// a single value or a whole list of values to get a proper response. +/// In theory you could do a batch query, but it's: +/// 1. Less convient in client libraries +/// 2. If the response value is small, the protocol overhead might be dominant. +/// +/// Also it's nice to be able to maintain backward compatibility for methods that +/// were initially taking a value and now we want to expand them to take a list. +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(untagged)] +pub enum ListOrValue { + /// A list of values of given type. + List(Vec), + /// A single value of given type. + Value(T), +} + +impl ListOrValue { + /// Map every contained value using function `F`. + /// + /// This allows to easily convert all values in any of the variants. + pub fn map X, X>(self, f: F) -> ListOrValue { + match self { + ListOrValue::List(v) => ListOrValue::List(v.into_iter().map(f).collect()), + ListOrValue::Value(v) => ListOrValue::Value(f(v)), + } + } +} + +impl From for ListOrValue { + fn from(n: T) -> Self { + ListOrValue::Value(n) + } +} + +impl From> for ListOrValue { + fn from(n: Vec) -> Self { + ListOrValue::List(n) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::assert_deser; + + #[test] + fn should_serialize_and_deserialize() { + assert_deser(r#"5"#, ListOrValue::Value(5_u64)); + assert_deser(r#""str""#, ListOrValue::Value("str".to_string())); + assert_deser(r#"[1,2,3]"#, ListOrValue::List(vec![1_u64, 2_u64, 3_u64])); + } +} diff --git a/substrate/primitives/rpc/src/number.rs b/substrate/primitives/rpc/src/number.rs index 0637e3decf..9c3312e6cc 100644 --- a/substrate/primitives/rpc/src/number.rs +++ b/substrate/primitives/rpc/src/number.rs @@ -27,7 +27,7 @@ use primitives::U256; /// or we attempt to parse given hex value. /// We do that for consistency with the returned type, default generic header /// serializes block number as hex to avoid overflows in JavaScript. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(untagged)] pub enum NumberOrHex { /// The original header number type of block. @@ -72,3 +72,18 @@ impl From for NumberOrHex { NumberOrHex::Hex(n) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::assert_deser; + + #[test] + fn should_serialize_and_deserialize() { + assert_deser(r#""0x1234""#, NumberOrHex::::Hex(0x1234.into())); + assert_deser(r#""0x0""#, NumberOrHex::::Hex(0.into())); + assert_deser(r#"5"#, NumberOrHex::Number(5_u64)); + assert_deser(r#"10000"#, NumberOrHex::Number(10000_u32)); + assert_deser(r#"0"#, NumberOrHex::Number(0_u16)); + } +}