From d8af9f4e7bb505f13da72a1972b57d6c66548282 Mon Sep 17 00:00:00 2001 From: Wei Tang Date: Tue, 26 Sep 2017 20:17:07 +0800 Subject: [PATCH] Add RPC eth_chainId for querying the current blockchain chain ID (#6329) * Add RPC eth_chainId for querying the current blockchain chain ID Currently although we can use `net_version` RPC call to get the current network ID, there's no RPC for querying the chain ID. This makes it impossible to determine the current actual blockchain using the RPC. An ETH/ETC client can accidentally connect to an ETC/ETH RPC endpoint without knowing it unless it tries to sign a transaction or it fetch a transaction that is known to have signed with a chain ID. This has since caused trouble for application developers, such as MetaMask, to add multi-chain support. The same RPC endpoint is also about to be merged for ETC's go-ethereum: https://github.com/ethereumproject/go-ethereum/pull/336 * Add eth_chainId to js's web3 interface * Add a mocked test for eth_chainId * Add chainId in js's jsonrpc interfaces * Change return type for eth_chainId to `Option` * Change name eth_chainId to parity_chainId * Wrong test name and missed var for rpc_parity_chain_id test * Use U256 to return chainId and fix for master u64 returns decimal integer, and there seems to be no type called U64. So here I use U256 to return the hex integer. * Fix chainID test Before EIP155 fork number, chainID should be null. * Change both parity_chainId and transaction::chainId to use U64 This makes it consistent that all chain ids returned are hex string. * Fix wrong U64 serialization --- js/src/api/rpc/parity/parity.js | 5 +++++ js/src/jsonrpc/interfaces/parity.js | 10 ++++++++++ rpc/src/v1/impls/light/parity.rs | 8 +++++++- rpc/src/v1/impls/parity.rs | 6 +++++- rpc/src/v1/tests/mocked/parity.rs | 11 +++++++++++ rpc/src/v1/traits/parity.rs | 8 +++++++- rpc/src/v1/types/mod.rs | 2 +- rpc/src/v1/types/transaction.rs | 8 ++++---- rpc/src/v1/types/uint.rs | 24 ++++++++++++++++++------ 9 files changed, 68 insertions(+), 14 deletions(-) diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index ece76f8b6..d49e75a78 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -53,6 +53,11 @@ export default class Parity { ); } + chainId () { + return this._transport + .execute('parity_chainId'); + } + chainStatus () { return this._transport .execute('parity_chainStatus') diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index b1e568d5f..997b7d434 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -56,6 +56,16 @@ export default { } }, + chainId: { + desc: 'Returns the current chain ID used for tranaction signing.', + params: [], + returns: { + type: Quantity, + desc: 'The current blockchain chain ID', + example: '0x1' + } + }, + chainStatus: { section: SECTION_NET, desc: 'Returns the information on warp sync blocks', diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 821937edc..fd617ae85 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -39,7 +39,7 @@ use v1::helpers::light_fetch::LightFetch; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ - Bytes, U256, H160, H256, H512, CallRequest, + Bytes, U256, U64, H160, H256, H512, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -50,6 +50,7 @@ use Host; /// Parity implementation for light client. pub struct ParityClient { + client: Arc, light_dispatch: Arc, accounts: Arc, logger: Arc, @@ -84,6 +85,7 @@ impl ParityClient { dapps_address, ws_address, eip86_transition: client.eip86_transition(), + client: client, } } @@ -321,6 +323,10 @@ impl Parity for ParityClient { Err(errors::light_unimplemented(None)) } + fn chain_id(&self) -> Result, Error> { + Ok(self.client.signing_chain_id().map(U64::from)) + } + fn chain(&self) -> Result { Ok(self.settings.chain.clone()) } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index b12ed873b..0f68e654f 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -44,7 +44,7 @@ use v1::helpers::accounts::unwrap_provider; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ - Bytes, U256, H160, H256, H512, CallRequest, + Bytes, U256, U64, H160, H256, H512, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -201,6 +201,10 @@ impl Parity for ParityClient where Ok(self.settings.chain.clone()) } + fn chain_id(&self) -> Result, Error> { + Ok(self.client.signing_chain_id().map(U64::from)) + } + fn chain(&self) -> Result { Ok(self.client.spec_name()) } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index ee714c54b..9153a17b3 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -221,6 +221,17 @@ fn rpc_parity_extra_data() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_chain_id() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_chainId", "params": [], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + #[test] fn rpc_parity_default_extra_data() { use util::misc; diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 7a7f68cec..e902d6052 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -24,7 +24,7 @@ use futures::BoxFuture; use node_health::Health; use v1::types::{ - H160, H256, H512, U256, Bytes, CallRequest, + H160, H256, H512, U256, U64, Bytes, CallRequest, Peers, Transaction, RpcSettings, Histogram, TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, @@ -172,6 +172,12 @@ build_rpc_trait! { #[rpc(name = "parity_mode")] fn mode(&self) -> Result; + /// Returns the chain ID used for transaction signing at the + /// current best block. An empty string is returned if not + /// available. + #[rpc(name = "parity_chainId")] + fn chain_id(&self) -> Result, Error>; + /// Get the chain name. Returns one of: "foundation", "kovan", &c. of a filename. #[rpc(name = "parity_chain")] fn chain(&self) -> Result; diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 1407ebcf2..c6963f2cf 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -76,7 +76,7 @@ pub use self::trace_filter::TraceFilter; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; -pub use self::uint::{U128, U256}; +pub use self::uint::{U128, U256, U64}; pub use self::work::Work; // TODO [ToDr] Refactor to a proper type Vec of enums? diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index d5eb63b44..90d512c86 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -20,7 +20,7 @@ use ethcore::miner; use ethcore::{contract_address, CreateContractAddress}; use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use v1::helpers::errors; -use v1::types::{Bytes, H160, H256, U256, H512, TransactionCondition}; +use v1::types::{Bytes, H160, H256, U256, H512, U64, TransactionCondition}; /// Transaction #[derive(Debug, Default, Clone, PartialEq, Serialize)] @@ -60,7 +60,7 @@ pub struct Transaction { pub public_key: Option, /// The network id of the transaction, if any. #[serde(rename="chainId")] - pub chain_id: Option, + pub chain_id: Option, /// The standardised V field of the signature (0 or 1). #[serde(rename="standardV")] pub standard_v: U256, @@ -196,7 +196,7 @@ impl Transaction { }, raw: ::rlp::encode(&t.signed).into_vec().into(), public_key: t.recover_public().ok().map(Into::into), - chain_id: t.chain_id(), + chain_id: t.chain_id().map(U64::from), standard_v: t.standard_v().into(), v: t.original_v().into(), r: signature.r().into(), @@ -230,7 +230,7 @@ impl Transaction { }, raw: ::rlp::encode(&t).into_vec().into(), public_key: t.public_key().map(Into::into), - chain_id: t.chain_id(), + chain_id: t.chain_id().map(U64::from), standard_v: t.standard_v().into(), v: t.original_v().into(), r: signature.r().into(), diff --git a/rpc/src/v1/types/uint.rs b/rpc/src/v1/types/uint.rs index 2de1ad46c..6c08370c5 100644 --- a/rpc/src/v1/types/uint.rs +++ b/rpc/src/v1/types/uint.rs @@ -59,12 +59,6 @@ macro_rules! impl_uint { } } - impl serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { - serializer.serialize_str(&format!("0x{}", self.0.to_hex())) - } - } - impl<'a> serde::Deserialize<'a> for $name { fn deserialize(deserializer: D) -> Result<$name, D::Error> where D: serde::Deserializer<'a> { @@ -104,7 +98,25 @@ macro_rules! impl_uint { impl_uint!(U128, EthU128, 2); impl_uint!(U256, EthU256, 4); +impl_uint!(U64, u64, 1); +impl serde::Serialize for U128 { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(&format!("0x{}", self.0.to_hex())) + } +} + +impl serde::Serialize for U256 { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(&format!("0x{}", self.0.to_hex())) + } +} + +impl serde::Serialize for U64 { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(&format!("0x{:x}", self.0)) + } +} #[cfg(test)] mod tests {