Make NumberOrHex a common primitive. (#6321)

* Make NumberOrHex a common primitive.

* Update primitives/rpc/src/number.rs

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
This commit is contained in:
Sergei Shulepov
2020-06-10 17:08:15 +02:00
committed by GitHub
parent 62412ab03c
commit 3ec4844616
5 changed files with 103 additions and 60 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ pub trait ChainApi<Number, Hash, Header, SignedBlock> {
#[rpc(name = "chain_getBlockHash", alias("chain_getHead"))] #[rpc(name = "chain_getBlockHash", alias("chain_getHead"))]
fn block_hash( fn block_hash(
&self, &self,
hash: Option<ListOrValue<NumberOrHex<Number>>>, hash: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Hash>>>; ) -> Result<ListOrValue<Option<Hash>>>;
/// Get hash of the last finalized block in the canon chain. /// Get hash of the last finalized block in the canon chain.
+22 -12
View File
@@ -75,17 +75,27 @@ trait ChainBackend<Client, Block: BlockT>: Send + Sync + 'static
/// Get hash of the n-th block in the canon chain. /// Get hash of the n-th block in the canon chain.
/// ///
/// By default returns latest block hash. /// By default returns latest block hash.
fn block_hash( fn block_hash(&self, number: Option<NumberOrHex>) -> Result<Option<Block::Hash>> {
&self, match number {
number: Option<NumberOrHex<NumberFor<Block>>>, None => Ok(Some(self.client().info().best_hash)),
) -> Result<Option<Block::Hash>> { Some(num_or_hex) => {
Ok(match number { use std::convert::TryInto;
None => Some(self.client().info().best_hash),
Some(num_or_hex) => self.client() // FIXME <2329>: Database seems to limit the block number to u32 for no reason
.header(BlockId::number(num_or_hex.to_number()?)) let block_num: u32 = num_or_hex.try_into().map_err(|_| {
.map_err(client_err)? Error::from(format!(
.map(|h| h.hash()), "`{:?}` > u32::max_value(), the max block number is u32.",
}) num_or_hex
))
})?;
let block_num = <NumberFor<Block>>::from(block_num);
Ok(self
.client()
.header(BlockId::number(block_num))
.map_err(client_err)?
.map(|h| h.hash()))
}
}
} }
/// Get hash of the last finalized block in the canon chain. /// Get hash of the last finalized block in the canon chain.
@@ -233,7 +243,7 @@ impl<Block, Client> ChainApi<NumberFor<Block>, Block::Hash, Block::Header, Signe
fn block_hash( fn block_hash(
&self, &self,
number: Option<ListOrValue<NumberOrHex<NumberFor<Block>>>> number: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Block::Hash>>> { ) -> Result<ListOrValue<Option<Block::Hash>>> {
match number { match number {
None => self.backend.block_hash(None).map(ListOrValue::Value), None => self.backend.block_hash(None).map(ListOrValue::Value),
+1 -1
View File
@@ -149,7 +149,7 @@ fn should_return_block_hash() {
); );
assert_matches!( assert_matches!(
api.block_hash(Some(vec![0u64.into(), 1.into(), 2.into()].into())), api.block_hash(Some(vec![0u64.into(), 1u64.into(), 2u64.into()].into())),
Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None] Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None]
); );
} }
+23 -5
View File
@@ -32,6 +32,7 @@ use sp_runtime::{
generic::BlockId, generic::BlockId,
traits::{Block as BlockT, Header as HeaderT}, traits::{Block as BlockT, Header as HeaderT},
}; };
use std::convert::TryInto;
pub use self::gen_client::Client as ContractsClient; pub use self::gen_client::Client as ContractsClient;
pub use pallet_contracts_rpc_runtime_api::{ pub use pallet_contracts_rpc_runtime_api::{
@@ -80,7 +81,7 @@ pub struct CallRequest<AccountId, Balance> {
origin: AccountId, origin: AccountId,
dest: AccountId, dest: AccountId,
value: Balance, value: Balance,
gas_limit: number::NumberOrHex<u64>, gas_limit: number::NumberOrHex,
input_data: Bytes, input_data: Bytes,
} }
@@ -203,9 +204,11 @@ where
gas_limit, gas_limit,
input_data, input_data,
} = call_request; } = call_request;
let gas_limit = gas_limit.to_number().map_err(|e| Error {
// Make sure that gas_limit fits into 64 bits.
let gas_limit: u64 = gas_limit.try_into().map_err(|_| Error {
code: ErrorCode::InvalidParams, code: ErrorCode::InvalidParams,
message: e, message: format!("{:?} doesn't fit in 64 bit unsigned value", gas_limit),
data: None, data: None,
})?; })?;
@@ -282,15 +285,30 @@ fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_core::U256;
#[test] #[test]
fn should_serialize_deserialize_properly() { fn call_request_should_serialize_deserialize_properly() {
type Req = CallRequest<String, u128>;
let req: Req = serde_json::from_str(r#"
{
"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",
"value": 0,
"gasLimit": 1000000000000,
"inputData": "0x8c97db39"
}
"#).unwrap();
assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));
}
#[test]
fn result_should_serialize_deserialize_properly() {
fn test(expected: &str) { fn test(expected: &str) {
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap(); let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap(); let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
test(r#"{"success":{"status":5,"data":"0x1234"}}"#); test(r#"{"success":{"status":5,"data":"0x1234"}}"#);
test(r#"{"error":null}"#); test(r#"{"error":null}"#);
} }
+56 -41
View File
@@ -15,65 +15,79 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Chain RPC Block number type. //! A number type that can be serialized both as a number or a string that encodes a number in a
//! string.
use serde::{Serialize, Deserialize};
use std::{convert::TryFrom, fmt::Debug}; use std::{convert::TryFrom, fmt::Debug};
use serde::{Serialize, Deserialize};
use sp_core::U256; use sp_core::U256;
/// RPC Block number type /// A number type that can be serialized both as a number or a string that encodes a number in a
/// string.
/// ///
/// We allow two representations of the block number as input. /// We allow two representations of the block number as input. Either we deserialize to the type
/// Either we deserialize to the type that is specified in the block type /// that is specified in the block type or we attempt to parse given hex value.
/// or we attempt to parse given hex value. ///
/// We do that for consistency with the returned type, default generic header /// The primary motivation for having this type is to avoid overflows when using big integers in
/// serializes block number as hex to avoid overflows in JavaScript. /// JavaScript (which we consider as an important RPC API consumer).
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
pub enum NumberOrHex<Number> { pub enum NumberOrHex {
/// The original header number type of block. /// The number represented directly.
Number(Number), Number(u64),
/// Hex representation of the block number. /// Hex representation of the number.
Hex(U256), Hex(U256),
} }
impl<Number: TryFrom<u64> + From<u32> + Debug + PartialOrd> NumberOrHex<Number> { impl NumberOrHex {
/// Attempts to convert into concrete block number. /// Converts this number into an U256.
/// pub fn into_u256(self) -> U256 {
/// Fails in case hex number is too big. match self {
pub fn to_number(self) -> Result<Number, String> { NumberOrHex::Number(n) => n.into(),
let num = match self { NumberOrHex::Hex(h) => h,
NumberOrHex::Number(n) => n,
NumberOrHex::Hex(h) => {
let l = h.low_u64();
if U256::from(l) != h {
return Err(format!("`{}` does not fit into u64 type; unsupported for now.", h))
} else {
Number::try_from(l)
.map_err(|_| format!("`{}` does not fit into block number type.", h))?
}
},
};
// FIXME <2329>: Database seems to limit the block number to u32 for no reason
if num > Number::from(u32::max_value()) {
return Err(format!("`{:?}` > u32::max_value(), the max block number is u32.", num))
} }
Ok(num)
} }
} }
impl From<u64> for NumberOrHex<u64> { impl From<u64> for NumberOrHex {
fn from(n: u64) -> Self { fn from(n: u64) -> Self {
NumberOrHex::Number(n) NumberOrHex::Number(n)
} }
} }
impl<Number> From<U256> for NumberOrHex<Number> { impl From<U256> for NumberOrHex {
fn from(n: U256) -> Self { fn from(n: U256) -> Self {
NumberOrHex::Hex(n) NumberOrHex::Hex(n)
} }
} }
/// An error type that signals an out-of-range conversion attempt.
pub struct TryFromIntError(pub(crate) ());
impl TryFrom<NumberOrHex> for u32 {
type Error = TryFromIntError;
fn try_from(num_or_hex: NumberOrHex) -> Result<u32, TryFromIntError> {
let num_or_hex = num_or_hex.into_u256();
if num_or_hex > U256::from(u32::max_value()) {
return Err(TryFromIntError(()));
} else {
Ok(num_or_hex.as_u32())
}
}
}
impl TryFrom<NumberOrHex> for u64 {
type Error = TryFromIntError;
fn try_from(num_or_hex: NumberOrHex) -> Result<u64, TryFromIntError> {
let num_or_hex = num_or_hex.into_u256();
if num_or_hex > U256::from(u64::max_value()) {
return Err(TryFromIntError(()));
} else {
Ok(num_or_hex.as_u64())
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -81,10 +95,11 @@ mod tests {
#[test] #[test]
fn should_serialize_and_deserialize() { fn should_serialize_and_deserialize() {
assert_deser(r#""0x1234""#, NumberOrHex::<u128>::Hex(0x1234.into())); assert_deser(r#""0x1234""#, NumberOrHex::Hex(0x1234.into()));
assert_deser(r#""0x0""#, NumberOrHex::<u64>::Hex(0.into())); assert_deser(r#""0x0""#, NumberOrHex::Hex(0.into()));
assert_deser(r#"5"#, NumberOrHex::Number(5_u64)); assert_deser(r#"5"#, NumberOrHex::Number(5));
assert_deser(r#"10000"#, NumberOrHex::Number(10000_u32)); assert_deser(r#"10000"#, NumberOrHex::Number(10000));
assert_deser(r#"0"#, NumberOrHex::Number(0_u16)); assert_deser(r#"0"#, NumberOrHex::Number(0));
assert_deser(r#"1000000000000"#, NumberOrHex::Number(1000000000000));
} }
} }