From 5baed0c15821c503bd0b5195726018a7c38ae8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Nov 2018 13:00:34 +0000 Subject: [PATCH] Add experimental RPCs flag (#9928) * WiP * Enable experimental RPCs. --- parity/cli/mod.rs | 7 ++ parity/configuration.rs | 3 + parity/rpc_apis.rs | 6 +- parity/run.rs | 3 + rpc/src/v1/helpers/errors.rs | 13 +++ rpc/src/v1/impls/personal.rs | 13 ++- rpc/src/v1/tests/mocked/personal.rs | 118 +++++++++++++++++++++++++++- 7 files changed, 159 insertions(+), 4 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 8718b0b57..cc7e1d4ce 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -471,6 +471,10 @@ usage! { "--jsonrpc-no-keep-alive", "Disable HTTP/1.1 keep alive header. Disabling keep alive will prevent re-using the same TCP connection to fire multiple requests, recommended when using one request per connection.", + FLAG flag_jsonrpc_experimental: (bool) = false, or |c: &Config| c.rpc.as_ref()?.experimental_rpcs.clone(), + "--jsonrpc-experimental", + "Enable experimental RPCs. Enable to have access to methods from unfinalised EIPs in all namespaces", + ARG arg_jsonrpc_port: (u16) = 8545u16, or |c: &Config| c.rpc.as_ref()?.port.clone(), "--jsonrpc-port=[PORT]", "Specify the port portion of the HTTP JSON-RPC API server.", @@ -1224,6 +1228,7 @@ struct Rpc { processing_threads: Option, max_payload: Option, keep_alive: Option, + experimental_rpcs: Option, poll_lifetime: Option, } @@ -1682,6 +1687,7 @@ mod tests { // RPC flag_no_jsonrpc: false, flag_jsonrpc_no_keep_alive: false, + flag_jsonrpc_experimental: false, arg_jsonrpc_port: 8545u16, arg_jsonrpc_interface: "local".into(), arg_jsonrpc_cors: "null".into(), @@ -1965,6 +1971,7 @@ mod tests { processing_threads: None, max_payload: None, keep_alive: None, + experimental_rpcs: None, poll_lifetime: None, }), ipc: Some(Ipc { diff --git a/parity/configuration.rs b/parity/configuration.rs index 54828d0b6..6bc90ec56 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -138,6 +138,7 @@ impl Configuration { let compaction = self.args.arg_db_compaction.parse()?; let warp_sync = !self.args.flag_no_warp; let geth_compatibility = self.args.flag_geth; + let experimental_rpcs = self.args.flag_jsonrpc_experimental; let ipfs_conf = self.ipfs_config(); let secretstore_conf = self.secretstore_config()?; let format = self.format()?; @@ -377,6 +378,7 @@ impl Configuration { warp_sync: warp_sync, warp_barrier: self.args.arg_warp_barrier, geth_compatibility: geth_compatibility, + experimental_rpcs, net_settings: self.network_settings()?, ipfs_conf: ipfs_conf, secretstore_conf: secretstore_conf, @@ -1424,6 +1426,7 @@ mod tests { compaction: Default::default(), vm_type: Default::default(), geth_compatibility: false, + experimental_rpcs: false, net_settings: Default::default(), ipfs_conf: Default::default(), secretstore_conf: Default::default(), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index f2f1bb312..6e922147b 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -228,6 +228,7 @@ pub struct FullDependencies { pub net_service: Arc, pub updater: Arc, pub geth_compatibility: bool, + pub experimental_rpcs: bool, pub ws_address: Option, pub fetch: FetchClient, pub executor: Executor, @@ -316,7 +317,7 @@ impl FullDependencies { } }, Api::Personal => { - handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility).to_delegate()); + handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility, self.experimental_rpcs).to_delegate()); }, Api::Signer => { handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service, self.executor.clone()).to_delegate()); @@ -438,6 +439,7 @@ pub struct LightDependencies { pub ws_address: Option, pub fetch: FetchClient, pub geth_compatibility: bool, + pub experimental_rpcs: bool, pub executor: Executor, pub whisper_rpc: Option<::whisper::RpcFactory>, pub private_tx_service: Option>, @@ -531,7 +533,7 @@ impl LightDependencies { handler.extend_with(EthPubSub::to_delegate(client)); }, Api::Personal => { - handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility).to_delegate()); + handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility, self.experimental_rpcs).to_delegate()); }, Api::Signer => { handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service, self.executor.clone()).to_delegate()); diff --git a/parity/run.rs b/parity/run.rs index 3a176057a..03dbcaffb 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -115,6 +115,7 @@ pub struct RunCmd { pub compaction: DatabaseCompactionProfile, pub vm_type: VMType, pub geth_compatibility: bool, + pub experimental_rpcs: bool, pub net_settings: NetworkSettings, pub ipfs_conf: ipfs::Configuration, pub secretstore_conf: secretstore::Configuration, @@ -312,6 +313,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc) -> Result(cmd: RunCmd, logger: Arc, on_client_rq: net_service: manage_network.clone(), updater: updater.clone(), geth_compatibility: cmd.geth_compatibility, + experimental_rpcs: cmd.experimental_rpcs, ws_address: cmd.ws_conf.address(), fetch: fetch.clone(), executor: runtime.executor(), diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index f64f0efa6..b1e11f580 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -54,6 +54,7 @@ mod codes { pub const NO_LIGHT_PEERS: i64 = -32065; pub const NO_PEERS: i64 = -32066; pub const DEPRECATED: i64 = -32070; + pub const EXPERIMENTAL_RPC: i64 = -32071; } pub fn unimplemented(details: Option) -> Error { @@ -513,3 +514,15 @@ pub fn status_error(has_peers: bool) -> Error { } } +/// Returns a descriptive error in case experimental RPCs are not enabled. +pub fn require_experimental(allow_experimental_rpcs: bool, eip: &str) -> Result<(), Error> { + if allow_experimental_rpcs { + Ok(()) + } else { + Err(Error { + code: ErrorCode::ServerError(codes::EXPERIMENTAL_RPC), + message: format!("This method is not part of the official RPC API yet (EIP-{}). Run with `--jsonrpc-experimental` to enable it.", eip), + data: Some(Value::String(format!("See EIP: https://eips.ethereum.org/EIPS/eip-{}", eip))), + }) + } +} diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 4da0c1023..e42a91b1f 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -47,15 +47,22 @@ pub struct PersonalClient { accounts: Arc, dispatcher: D, allow_perm_unlock: bool, + allow_experimental_rpcs: bool, } impl PersonalClient { /// Creates new PersonalClient - pub fn new(accounts: &Arc, dispatcher: D, allow_perm_unlock: bool) -> Self { + pub fn new( + accounts: &Arc, + dispatcher: D, + allow_perm_unlock: bool, + allow_experimental_rpcs: bool, + ) -> Self { PersonalClient { accounts: accounts.clone(), dispatcher, allow_perm_unlock, + allow_experimental_rpcs, } } } @@ -154,6 +161,8 @@ impl Personal for PersonalClient { } fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture { + try_bf!(errors::require_experimental(self.allow_experimental_rpcs, "191")); + let data = try_bf!(eip191::hash_message(version, data)); let dispatcher = self.dispatcher.clone(); let accounts = self.accounts.clone(); @@ -174,6 +183,8 @@ impl Personal for PersonalClient { } fn sign_typed_data(&self, typed_data: EIP712, account: RpcH160, password: String) -> BoxFuture { + try_bf!(errors::require_experimental(self.allow_experimental_rpcs, "712")); + let data = match hash_structured_data(typed_data) { Ok(d) => d, Err(err) => return Box::new(future::err(errors::invalid_call_data(err.kind()))), diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 5098d807d..143b19fa8 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -57,6 +57,16 @@ fn miner_service() -> Arc { } fn setup() -> PersonalTester { + setup_with(Config { + allow_experimental_rpcs: true + }) +} + +struct Config { + pub allow_experimental_rpcs: bool, +} + +fn setup_with(c: Config) -> PersonalTester { let runtime = Runtime::with_thread_count(1); let accounts = accounts_provider(); let client = blockchain_client(); @@ -64,7 +74,7 @@ fn setup() -> PersonalTester { let reservations = Arc::new(Mutex::new(nonce::Reservations::new(runtime.executor()))); let dispatcher = FullDispatcher::new(client, miner.clone(), reservations, 50); - let personal = PersonalClient::new(&accounts, dispatcher, false); + let personal = PersonalClient::new(&accounts, dispatcher, false, c.allow_experimental_rpcs); let mut io = IoHandler::default(); io.extend_with(personal.to_delegate()); @@ -418,3 +428,109 @@ fn sign_eip191_structured_data() { let response = tester.io.handle_request_sync(&request).unwrap(); assert_eq!(response, expected) } + +#[test] +fn sign_structured_data() { + let tester = setup(); + let secret: Secret = keccak("cow").into(); + let address = tester.accounts.insert_account(secret, &"lol".into()).unwrap(); + let request = r#"{ + "jsonrpc": "2.0", + "method": "personal_signTypedData", + "params": [ + { + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + }, + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" } + ] + } + }, + ""#.to_owned() + &format!("0x{:x}", address) + r#"", + "lol" + ], + "id": 1 + }"#; + let expected = r#"{"jsonrpc":"2.0","result":"0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c","id":1}"#; + let response = tester.io.handle_request_sync(&request).unwrap(); + assert_eq!(response, expected) +} + +#[test] +fn should_disable_experimental_apis() { + // given + let tester = setup_with(Config { + allow_experimental_rpcs: false, + }); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "personal_sign191", + "params": [ + "0x01", + {}, + "0x1234567891234567891234567891234567891234", + "lol" + ], + "id": 1 + }"#; + let r1 = tester.io.handle_request_sync(&request).unwrap(); + let request = r#"{ + "jsonrpc": "2.0", + "method": "personal_signTypedData", + "params": [ + { + "types": {}, + "message": {}, + "domain": { + "name": "", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "primaryType": "" + }, + "0x1234567891234567891234567891234678912344", + "lol" + ], + "id": 1 + }"#; + let r2 = tester.io.handle_request_sync(&request).unwrap(); + + // then + let expected = r#"{"jsonrpc":"2.0","error":{"code":-32071,"message":"This method is not part of the official RPC API yet (EIP-191). Run with `--jsonrpc-experimental` to enable it.","data":"See EIP: https://eips.ethereum.org/EIPS/eip-191"},"id":1}"#; + assert_eq!(r1, expected); + + let expected = r#"{"jsonrpc":"2.0","error":{"code":-32071,"message":"This method is not part of the official RPC API yet (EIP-712). Run with `--jsonrpc-experimental` to enable it.","data":"See EIP: https://eips.ethereum.org/EIPS/eip-712"},"id":1}"#; + assert_eq!(r2, expected); +}