Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Make NumberOrHex a common primitive. #6321

Merged
3 commits merged into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/rpc-api/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub trait ChainApi<Number, Hash, Header, SignedBlock> {
#[rpc(name = "chain_getBlockHash", alias("chain_getHead"))]
fn block_hash(
&self,
hash: Option<ListOrValue<NumberOrHex<Number>>>,
hash: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Hash>>>;

/// Get hash of the last finalized block in the canon chain.
Expand Down
34 changes: 22 additions & 12 deletions client/rpc/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,27 @@ trait ChainBackend<Client, Block: BlockT>: Send + Sync + 'static
/// Get hash of the n-th block in the canon chain.
///
/// By default returns latest block hash.
fn block_hash(
&self,
number: Option<NumberOrHex<NumberFor<Block>>>,
) -> Result<Option<Block::Hash>> {
Ok(match number {
None => Some(self.client().info().best_hash),
Some(num_or_hex) => self.client()
.header(BlockId::number(num_or_hex.to_number()?))
.map_err(client_err)?
.map(|h| h.hash()),
})
fn block_hash(&self, number: Option<NumberOrHex>) -> Result<Option<Block::Hash>> {
match number {
None => Ok(Some(self.client().info().best_hash)),
Some(num_or_hex) => {
use std::convert::TryInto;

// FIXME <2329>: Database seems to limit the block number to u32 for no reason
NikVolf marked this conversation as resolved.
Show resolved Hide resolved
let block_num: u32 = num_or_hex.try_into().map_err(|_| {
Error::from(format!(
"`{:?}` > 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.
Expand Down Expand Up @@ -233,7 +243,7 @@ impl<Block, Client> ChainApi<NumberFor<Block>, Block::Hash, Block::Header, Signe

fn block_hash(
&self,
number: Option<ListOrValue<NumberOrHex<NumberFor<Block>>>>
number: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Block::Hash>>> {
match number {
None => self.backend.block_hash(None).map(ListOrValue::Value),
Expand Down
2 changes: 1 addition & 1 deletion client/rpc/src/chain/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn should_return_block_hash() {
);

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]
);
}
Expand Down
28 changes: 23 additions & 5 deletions frame/contracts/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
};
use std::convert::TryInto;

pub use self::gen_client::Client as ContractsClient;
pub use pallet_contracts_rpc_runtime_api::{
Expand Down Expand Up @@ -80,7 +81,7 @@ pub struct CallRequest<AccountId, Balance> {
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: number::NumberOrHex<u64>,
gas_limit: number::NumberOrHex,
input_data: Bytes,
}

Expand Down Expand Up @@ -203,9 +204,11 @@ where
gas_limit,
input_data,
} = 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,
message: e,
message: format!("{:?} doesn't fit in 64 bit unsigned value", gas_limit),
data: None,
})?;

Expand Down Expand Up @@ -282,15 +285,30 @@ fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
#[cfg(test)]
mod tests {
use super::*;
use sp_core::U256;

#[test]
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 should_serialize_deserialize_properly() {
fn result_should_serialize_deserialize_properly() {
fn test(expected: &str) {
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected);
}

test(r#"{"success":{"status":5,"data":"0x1234"}}"#);
test(r#"{"error":null}"#);
}
Expand Down
97 changes: 56 additions & 41 deletions primitives/rpc/src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,76 +15,91 @@
// See the License for the specific language governing permissions and
// 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 serde::{Serialize, Deserialize};
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. Either we deserialize to the type
/// that is specified in the block type or we attempt to parse given hex value.
///
/// We allow two representations of the block number as input.
/// Either we deserialize to the type that is specified in the block type
/// 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, Debug, PartialEq)]
/// The primary motivation for having this type is to avoid overflows when using big integers in
/// JavaScript (which we consider as an important RPC API consumer).
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(untagged)]
pub enum NumberOrHex<Number> {
/// The original header number type of block.
Number(Number),
/// Hex representation of the block number.
pub enum NumberOrHex {
bkchr marked this conversation as resolved.
Show resolved Hide resolved
/// The number represented directly.
Number(u64),
/// Hex representation of the number.
Hex(U256),
}

impl<Number: TryFrom<u64> + From<u32> + Debug + PartialOrd> NumberOrHex<Number> {
/// Attempts to convert into concrete block number.
///
/// Fails in case hex number is too big.
pub fn to_number(self) -> Result<Number, String> {
let num = match self {
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))
impl NumberOrHex {
/// Converts this number into an U256.
pub fn into_u256(self) -> U256 {
match self {
NumberOrHex::Number(n) => n.into(),
NumberOrHex::Hex(h) => h,
}
Ok(num)
}
}

impl From<u64> for NumberOrHex<u64> {
impl From<u64> for NumberOrHex {
fn from(n: u64) -> Self {
NumberOrHex::Number(n)
}
}

impl<Number> From<U256> for NumberOrHex<Number> {
impl From<U256> for NumberOrHex {
fn from(n: U256) -> Self {
NumberOrHex::Hex(n)
}
}

/// An error type that signals an out-of-range conversion attempt.
pub struct TryFromIntError(pub(crate) ());
bkchr marked this conversation as resolved.
Show resolved Hide resolved

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)]
mod tests {
use super::*;
use crate::assert_deser;

#[test]
fn should_serialize_and_deserialize() {
assert_deser(r#""0x1234""#, NumberOrHex::<u128>::Hex(0x1234.into()));
assert_deser(r#""0x0""#, NumberOrHex::<u64>::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));
assert_deser(r#""0x1234""#, NumberOrHex::Hex(0x1234.into()));
assert_deser(r#""0x0""#, NumberOrHex::Hex(0.into()));
assert_deser(r#"5"#, NumberOrHex::Number(5));
assert_deser(r#"10000"#, NumberOrHex::Number(10000));
assert_deser(r#"0"#, NumberOrHex::Number(0));
assert_deser(r#"1000000000000"#, NumberOrHex::Number(1000000000000));
}
}