Add experimental RPCs flag (#9928)

* WiP

* Enable experimental RPCs.
This commit is contained in:
Tomasz Drwięga 2018-11-16 13:00:34 +00:00 committed by Seun LanLege
parent 97c259858c
commit 5baed0c158
7 changed files with 159 additions and 4 deletions

View File

@ -471,6 +471,10 @@ usage! {
"--jsonrpc-no-keep-alive", "--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.", "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(), ARG arg_jsonrpc_port: (u16) = 8545u16, or |c: &Config| c.rpc.as_ref()?.port.clone(),
"--jsonrpc-port=[PORT]", "--jsonrpc-port=[PORT]",
"Specify the port portion of the HTTP JSON-RPC API server.", "Specify the port portion of the HTTP JSON-RPC API server.",
@ -1224,6 +1228,7 @@ struct Rpc {
processing_threads: Option<usize>, processing_threads: Option<usize>,
max_payload: Option<usize>, max_payload: Option<usize>,
keep_alive: Option<bool>, keep_alive: Option<bool>,
experimental_rpcs: Option<bool>,
poll_lifetime: Option<u32>, poll_lifetime: Option<u32>,
} }
@ -1682,6 +1687,7 @@ mod tests {
// RPC // RPC
flag_no_jsonrpc: false, flag_no_jsonrpc: false,
flag_jsonrpc_no_keep_alive: false, flag_jsonrpc_no_keep_alive: false,
flag_jsonrpc_experimental: false,
arg_jsonrpc_port: 8545u16, arg_jsonrpc_port: 8545u16,
arg_jsonrpc_interface: "local".into(), arg_jsonrpc_interface: "local".into(),
arg_jsonrpc_cors: "null".into(), arg_jsonrpc_cors: "null".into(),
@ -1965,6 +1971,7 @@ mod tests {
processing_threads: None, processing_threads: None,
max_payload: None, max_payload: None,
keep_alive: None, keep_alive: None,
experimental_rpcs: None,
poll_lifetime: None, poll_lifetime: None,
}), }),
ipc: Some(Ipc { ipc: Some(Ipc {

View File

@ -138,6 +138,7 @@ impl Configuration {
let compaction = self.args.arg_db_compaction.parse()?; let compaction = self.args.arg_db_compaction.parse()?;
let warp_sync = !self.args.flag_no_warp; let warp_sync = !self.args.flag_no_warp;
let geth_compatibility = self.args.flag_geth; let geth_compatibility = self.args.flag_geth;
let experimental_rpcs = self.args.flag_jsonrpc_experimental;
let ipfs_conf = self.ipfs_config(); let ipfs_conf = self.ipfs_config();
let secretstore_conf = self.secretstore_config()?; let secretstore_conf = self.secretstore_config()?;
let format = self.format()?; let format = self.format()?;
@ -377,6 +378,7 @@ impl Configuration {
warp_sync: warp_sync, warp_sync: warp_sync,
warp_barrier: self.args.arg_warp_barrier, warp_barrier: self.args.arg_warp_barrier,
geth_compatibility: geth_compatibility, geth_compatibility: geth_compatibility,
experimental_rpcs,
net_settings: self.network_settings()?, net_settings: self.network_settings()?,
ipfs_conf: ipfs_conf, ipfs_conf: ipfs_conf,
secretstore_conf: secretstore_conf, secretstore_conf: secretstore_conf,
@ -1424,6 +1426,7 @@ mod tests {
compaction: Default::default(), compaction: Default::default(),
vm_type: Default::default(), vm_type: Default::default(),
geth_compatibility: false, geth_compatibility: false,
experimental_rpcs: false,
net_settings: Default::default(), net_settings: Default::default(),
ipfs_conf: Default::default(), ipfs_conf: Default::default(),
secretstore_conf: Default::default(), secretstore_conf: Default::default(),

View File

@ -228,6 +228,7 @@ pub struct FullDependencies {
pub net_service: Arc<ManageNetwork>, pub net_service: Arc<ManageNetwork>,
pub updater: Arc<Updater>, pub updater: Arc<Updater>,
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub experimental_rpcs: bool,
pub ws_address: Option<Host>, pub ws_address: Option<Host>,
pub fetch: FetchClient, pub fetch: FetchClient,
pub executor: Executor, pub executor: Executor,
@ -316,7 +317,7 @@ impl FullDependencies {
} }
}, },
Api::Personal => { 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 => { Api::Signer => {
handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service, self.executor.clone()).to_delegate()); 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<T> {
pub ws_address: Option<Host>, pub ws_address: Option<Host>,
pub fetch: FetchClient, pub fetch: FetchClient,
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub experimental_rpcs: bool,
pub executor: Executor, pub executor: Executor,
pub whisper_rpc: Option<::whisper::RpcFactory>, pub whisper_rpc: Option<::whisper::RpcFactory>,
pub private_tx_service: Option<Arc<PrivateTransactionManager>>, pub private_tx_service: Option<Arc<PrivateTransactionManager>>,
@ -531,7 +533,7 @@ impl<C: LightChainClient + 'static> LightDependencies<C> {
handler.extend_with(EthPubSub::to_delegate(client)); handler.extend_with(EthPubSub::to_delegate(client));
}, },
Api::Personal => { 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 => { Api::Signer => {
handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service, self.executor.clone()).to_delegate()); handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service, self.executor.clone()).to_delegate());

View File

@ -115,6 +115,7 @@ pub struct RunCmd {
pub compaction: DatabaseCompactionProfile, pub compaction: DatabaseCompactionProfile,
pub vm_type: VMType, pub vm_type: VMType,
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub experimental_rpcs: bool,
pub net_settings: NetworkSettings, pub net_settings: NetworkSettings,
pub ipfs_conf: ipfs::Configuration, pub ipfs_conf: ipfs::Configuration,
pub secretstore_conf: secretstore::Configuration, pub secretstore_conf: secretstore::Configuration,
@ -312,6 +313,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
ws_address: cmd.ws_conf.address(), ws_address: cmd.ws_conf.address(),
fetch: fetch, fetch: fetch,
geth_compatibility: cmd.geth_compatibility, geth_compatibility: cmd.geth_compatibility,
experimental_rpcs: cmd.experimental_rpcs,
executor: runtime.executor(), executor: runtime.executor(),
whisper_rpc: whisper_factory, whisper_rpc: whisper_factory,
private_tx_service: None, //TODO: add this to client. private_tx_service: None, //TODO: add this to client.
@ -712,6 +714,7 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
net_service: manage_network.clone(), net_service: manage_network.clone(),
updater: updater.clone(), updater: updater.clone(),
geth_compatibility: cmd.geth_compatibility, geth_compatibility: cmd.geth_compatibility,
experimental_rpcs: cmd.experimental_rpcs,
ws_address: cmd.ws_conf.address(), ws_address: cmd.ws_conf.address(),
fetch: fetch.clone(), fetch: fetch.clone(),
executor: runtime.executor(), executor: runtime.executor(),

View File

@ -54,6 +54,7 @@ mod codes {
pub const NO_LIGHT_PEERS: i64 = -32065; pub const NO_LIGHT_PEERS: i64 = -32065;
pub const NO_PEERS: i64 = -32066; pub const NO_PEERS: i64 = -32066;
pub const DEPRECATED: i64 = -32070; pub const DEPRECATED: i64 = -32070;
pub const EXPERIMENTAL_RPC: i64 = -32071;
} }
pub fn unimplemented(details: Option<String>) -> Error { pub fn unimplemented(details: Option<String>) -> 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))),
})
}
}

View File

@ -47,15 +47,22 @@ pub struct PersonalClient<D: Dispatcher> {
accounts: Arc<AccountProvider>, accounts: Arc<AccountProvider>,
dispatcher: D, dispatcher: D,
allow_perm_unlock: bool, allow_perm_unlock: bool,
allow_experimental_rpcs: bool,
} }
impl<D: Dispatcher> PersonalClient<D> { impl<D: Dispatcher> PersonalClient<D> {
/// Creates new PersonalClient /// Creates new PersonalClient
pub fn new(accounts: &Arc<AccountProvider>, dispatcher: D, allow_perm_unlock: bool) -> Self { pub fn new(
accounts: &Arc<AccountProvider>,
dispatcher: D,
allow_perm_unlock: bool,
allow_experimental_rpcs: bool,
) -> Self {
PersonalClient { PersonalClient {
accounts: accounts.clone(), accounts: accounts.clone(),
dispatcher, dispatcher,
allow_perm_unlock, allow_perm_unlock,
allow_experimental_rpcs,
} }
} }
} }
@ -154,6 +161,8 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
} }
fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture<RpcH520> { fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
try_bf!(errors::require_experimental(self.allow_experimental_rpcs, "191"));
let data = try_bf!(eip191::hash_message(version, data)); let data = try_bf!(eip191::hash_message(version, data));
let dispatcher = self.dispatcher.clone(); let dispatcher = self.dispatcher.clone();
let accounts = self.accounts.clone(); let accounts = self.accounts.clone();
@ -174,6 +183,8 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
} }
fn sign_typed_data(&self, typed_data: EIP712, account: RpcH160, password: String) -> BoxFuture<RpcH520> { fn sign_typed_data(&self, typed_data: EIP712, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
try_bf!(errors::require_experimental(self.allow_experimental_rpcs, "712"));
let data = match hash_structured_data(typed_data) { let data = match hash_structured_data(typed_data) {
Ok(d) => d, Ok(d) => d,
Err(err) => return Box::new(future::err(errors::invalid_call_data(err.kind()))), Err(err) => return Box::new(future::err(errors::invalid_call_data(err.kind()))),

View File

@ -57,6 +57,16 @@ fn miner_service() -> Arc<TestMinerService> {
} }
fn setup() -> PersonalTester { 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 runtime = Runtime::with_thread_count(1);
let accounts = accounts_provider(); let accounts = accounts_provider();
let client = blockchain_client(); let client = blockchain_client();
@ -64,7 +74,7 @@ fn setup() -> PersonalTester {
let reservations = Arc::new(Mutex::new(nonce::Reservations::new(runtime.executor()))); let reservations = Arc::new(Mutex::new(nonce::Reservations::new(runtime.executor())));
let dispatcher = FullDispatcher::new(client, miner.clone(), reservations, 50); 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(); let mut io = IoHandler::default();
io.extend_with(personal.to_delegate()); io.extend_with(personal.to_delegate());
@ -418,3 +428,109 @@ fn sign_eip191_structured_data() {
let response = tester.io.handle_request_sync(&request).unwrap(); let response = tester.io.handle_request_sync(&request).unwrap();
assert_eq!(response, expected) 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);
}