From 4a2ad9fc2a8fbad60b2e434f7e1be4650950e529 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 5 May 2017 08:38:11 +0000 Subject: [PATCH 1/8] [ci skip] js-precompiled 20170505-083410 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6fb6ca6b..ecb118d56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1778,7 +1778,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/paritytech/js-precompiled.git#4ad25201e9bc25a3873fd1e7a6d8a7b49861c946" +source = "git+https://github.com/paritytech/js-precompiled.git#b83f10bd5c196d59cc9cd031a14a7a24e51ed41a" dependencies = [ "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index a29e190ab..282a213b5 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "1.7.66", + "version": "1.7.67", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0d8920347a72fc50e82b540855eba94c8bbb2c0f Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 5 May 2017 08:50:23 +0000 Subject: [PATCH 2/8] [ci skip] js-precompiled 20170505-084643 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ecb118d56..5f3131708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1778,7 +1778,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/paritytech/js-precompiled.git#b83f10bd5c196d59cc9cd031a14a7a24e51ed41a" +source = "git+https://github.com/paritytech/js-precompiled.git#b6fbfc59f044546ccd3b928fb17daa3812ae856e" dependencies = [ "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 282a213b5..4aaa18e15 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "1.7.67", + "version": "1.7.68", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 8b9adb4d74080fa53af42f1941909289ca0ca542 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 5 May 2017 16:57:29 +0300 Subject: [PATCH 3/8] Secretstore RPCs + integration (#5439) * ECDKG protocol prototype * added test for enc/dec math * get rid of decryption_session * added licenses * fix after merge * get rid of unused serde dependency * doc * decryption session [without commutative enc] * failed_dec_session * fixed tests * added commen * added more decryption session tests * helper to localize an issue * more computations to localize error * decryption_session::SessionParams * added tests for EC math to localize problem * secretstore network transport * encryption_session_works_over_network * network errors processing * connecting to KeyServer * licenses * get rid of debug println-s * fixed secretstore args * encryption results are stored in KS database * decryption protocol works over network * enc/dec Session traits * fixing warnings * fix after merge * on-chain ACL checker proto * fixed compilation * fixed compilation * finally fixed -of-N-scheme * temporary commented test * 1-of-N works in math * scheme 1-of-N works * updated AclStorage with real contract ABI * remove unnecessary unsafety * fixed grumbles * wakeup on access denied * encrypt secretstore messages * 'shadow' decryption * fix grumbles * lost files * secretstore cli-options * decryption seccion when ACL check failed on master * disallow regenerating key for existing document * removed obsolete TODO * fix after merge * switched to tokio_io * fix after merge * fix after merge * fix after merge * fix after merge * fix after merge * fixed test * fix after merge * encryption session errors are now fatal * session timeouts * autorestart decryption session * remove sessions on completion * exclude disconnected nodes from decryption session * test for enc/dec session over network with 1 node * remove debug printlns * fixed 1-of-1 scheme * drop for KeyServerHttpListener * Use standard encryption and decryption (as in RPC) * added some tests * moved DEFAULT_MAC to ethcrypto * rpc_secretstore_encrypt_and_decrypt * serialization with "0x" prefix (RPC compatibility) * secretstore RPC API * fix after merge * fixed typo * secretstore_shadowDecrypt RPC * enable secretstore RPCs by default * fixed test * SecStore RPCs available without SecStore feature * fixed grumbles * lost files * added password argument to Parity RPCs * update docs * lost file --- Cargo.lock | 1 + ethcrypto/src/lib.rs | 3 + parity/cli/config.full.toml | 6 +- parity/cli/mod.rs | 12 +- parity/rpc_apis.rs | 30 +++-- rpc/Cargo.toml | 1 + rpc/src/lib.rs | 1 + rpc/src/v1/helpers/dispatch.rs | 4 +- rpc/src/v1/helpers/mod.rs | 1 + rpc/src/v1/helpers/secretstore.rs | 127 ++++++++++++++++++ rpc/src/v1/impls/light/parity.rs | 3 +- rpc/src/v1/impls/mod.rs | 2 + rpc/src/v1/impls/parity.rs | 2 +- rpc/src/v1/impls/secretstore.rs | 85 ++++++++++++ rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/mod.rs | 1 + rpc/src/v1/tests/mocked/secretstore.rs | 98 ++++++++++++++ rpc/src/v1/traits/mod.rs | 3 +- rpc/src/v1/traits/secretstore.rs | 41 ++++++ secret_store/src/http_listener.rs | 7 +- secret_store/src/key_server.rs | 16 +-- .../key_server_cluster/decryption_session.rs | 10 +- secret_store/src/serialization.rs | 88 +++++++++--- secret_store/src/types/all.rs | 10 ++ 24 files changed, 497 insertions(+), 57 deletions(-) create mode 100644 rpc/src/v1/helpers/secretstore.rs create mode 100644 rpc/src/v1/impls/secretstore.rs create mode 100644 rpc/src/v1/tests/mocked/secretstore.rs create mode 100644 rpc/src/v1/traits/secretstore.rs diff --git a/Cargo.lock b/Cargo.lock index 5f3131708..f59140ada 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1712,6 +1712,7 @@ dependencies = [ "parity-reactor 0.1.0", "parity-updater 1.7.0", "pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcrypto/src/lib.rs b/ethcrypto/src/lib.rs index ea0ea9754..4f14cf4d9 100644 --- a/ethcrypto/src/lib.rs +++ b/ethcrypto/src/lib.rs @@ -34,6 +34,9 @@ pub const KEY_LENGTH: usize = 32; pub const KEY_ITERATIONS: usize = 10240; pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2; +/// Default MAC to use (in RPC). +pub const DEFAULT_MAC: [u8; 2] = [0, 0]; + #[derive(PartialEq, Debug)] pub enum ScryptError { // log(N) < r / 16 diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index a88e01336..624c0ccdf 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -50,7 +50,7 @@ disable = false port = 8545 interface = "local" cors = "null" -apis = ["web3", "eth", "net", "parity", "traces", "rpc"] +apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] hosts = ["none"] [websockets] @@ -58,13 +58,13 @@ disable = false port = 8546 interface = "local" origins = ["none"] -apis = ["web3", "eth", "net", "parity", "traces", "rpc"] +apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] hosts = ["none"] [ipc] disable = false path = "$HOME/.parity/jsonrpc.ipc" -apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc"] +apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc", "secretstore"] [dapps] disable = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index fe21070f8..f36622357 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -165,7 +165,7 @@ usage! { or |c: &Config| otry!(c.rpc).interface.clone(), flag_jsonrpc_cors: Option = None, or |c: &Config| otry!(c.rpc).cors.clone().map(Some), - flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc", + flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc,secretstore", or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")), @@ -179,7 +179,7 @@ usage! { or |c: &Config| otry!(c.websockets).port.clone(), flag_ws_interface: String = "local", or |c: &Config| otry!(c.websockets).interface.clone(), - flag_ws_apis: String = "web3,eth,net,parity,traces,rpc", + flag_ws_apis: String = "web3,eth,net,parity,traces,rpc,secretstore", or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")), flag_ws_origins: String = "none", or |c: &Config| otry!(c.websockets).origins.as_ref().map(|vec| vec.join(",")), @@ -191,7 +191,7 @@ usage! { or |c: &Config| otry!(c.ipc).disable.clone(), flag_ipc_path: String = "$BASE/jsonrpc.ipc", or |c: &Config| otry!(c.ipc).path.clone(), - flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc", + flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc,secretstore", or |c: &Config| otry!(c.ipc).apis.as_ref().map(|vec| vec.join(",")), // DAPPS @@ -723,7 +723,7 @@ mod tests { flag_jsonrpc_port: 8545u16, flag_jsonrpc_interface: "local".into(), flag_jsonrpc_cors: Some("null".into()), - flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".into(), + flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(), flag_jsonrpc_hosts: "none".into(), flag_jsonrpc_threads: None, @@ -731,14 +731,14 @@ mod tests { flag_no_ws: false, flag_ws_port: 8546u16, flag_ws_interface: "local".into(), - flag_ws_apis: "web3,eth,net,parity,traces,rpc".into(), + flag_ws_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(), flag_ws_origins: "none".into(), flag_ws_hosts: "none".into(), // IPC flag_no_ipc: false, flag_ipc_path: "$HOME/.parity/jsonrpc.ipc".into(), - flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(), + flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc,secretstore".into(), // DAPPS flag_dapps_path: "$HOME/.parity/dapps".into(), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 7643e0010..0ff204aba 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -59,6 +59,8 @@ pub enum Api { Traces, /// Rpc (Safe) Rpc, + /// SecretStore (Safe) + SecretStore, } impl FromStr for Api { @@ -78,6 +80,7 @@ impl FromStr for Api { "parity_set" => Ok(ParitySet), "traces" => Ok(Traces), "rpc" => Ok(Rpc), + "secretstore" => Ok(SecretStore), api => Err(format!("Unknown api: {}", api)) } } @@ -156,6 +159,7 @@ fn to_modules(apis: &[Api]) -> BTreeMap { Api::ParitySet => ("parity_set", "1.0"), Api::Traces => ("traces", "1.0"), Api::Rpc => ("rpc", "1.0"), + Api::SecretStore => ("secretstore", "1.0"), }; modules.insert(name.into(), version.into()); } @@ -295,7 +299,10 @@ impl Dependencies for FullDependencies { Api::Rpc => { let modules = to_modules(&apis); handler.extend_with(RpcClient::new(modules).to_delegate()); - } + }, + Api::SecretStore => { + handler.extend_with(SecretStoreClient::new(&self.secret_store).to_delegate()); + }, } } } @@ -424,7 +431,11 @@ impl Dependencies for LightDependencies { Api::Rpc => { let modules = to_modules(&apis); handler.extend_with(RpcClient::new(modules).to_delegate()); - } + }, + Api::SecretStore => { + let secret_store = Some(self.secret_store.clone()); + handler.extend_with(SecretStoreClient::new(&secret_store).to_delegate()); + }, } } } @@ -438,7 +449,7 @@ impl ApiSet { pub fn list_apis(&self) -> HashSet { let mut public_list = vec![ - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Rpc, Api::SecretStore, ].into_iter().collect(); match *self { ApiSet::List(ref apis) => apis.clone(), @@ -496,6 +507,7 @@ mod test { assert_eq!(Api::ParitySet, "parity_set".parse().unwrap()); assert_eq!(Api::Traces, "traces".parse().unwrap()); assert_eq!(Api::Rpc, "rpc".parse().unwrap()); + assert_eq!(Api::SecretStore, "secretstore".parse().unwrap()); assert!("rp".parse::().is_err()); } @@ -513,7 +525,7 @@ mod test { fn test_api_set_unsafe_context() { let expected = vec![ // make sure this list contains only SAFE methods - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore ].into_iter().collect(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } @@ -522,7 +534,7 @@ mod test { fn test_api_set_ipc_context() { let expected = vec![ // safe - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore, // semi-safe Api::ParityAccounts ].into_iter().collect(); @@ -533,7 +545,7 @@ mod test { fn test_api_set_safe_context() { let expected = vec![ // safe - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore, // semi-safe Api::ParityAccounts, // Unsafe @@ -545,7 +557,7 @@ mod test { #[test] fn test_all_apis() { assert_eq!("all".parse::().unwrap(), ApiSet::List(vec![ - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore, Api::ParityAccounts, Api::ParitySet, Api::Signer, Api::Personal @@ -555,7 +567,7 @@ mod test { #[test] fn test_all_without_personal_apis() { assert_eq!("personal,all,-personal".parse::().unwrap(), ApiSet::List(vec![ - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore, Api::ParityAccounts, Api::ParitySet, Api::Signer, ].into_iter().collect())); @@ -564,7 +576,7 @@ mod test { #[test] fn test_safe_parsing() { assert_eq!("safe".parse::().unwrap(), ApiSet::List(vec![ - Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore, ].into_iter().collect())); } } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 70ff7202d..0067bdeaa 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -21,6 +21,7 @@ transient-hashmap = "0.4" cid = "0.2.1" multihash = "0.5" rust-crypto = "0.2.36" +rand = "0.3" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index ae51d879c..291c5bcd8 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -30,6 +30,7 @@ extern crate transient_hashmap; extern crate cid; extern crate multihash; extern crate crypto as rust_crypto; +extern crate rand; extern crate jsonrpc_core; extern crate jsonrpc_http_server as http; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 96c38d931..1588be5a9 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -37,6 +37,7 @@ use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction}; use ethcore::account_provider::AccountProvider; +use crypto::DEFAULT_MAC; use jsonrpc_core::Error; use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload}; @@ -400,9 +401,6 @@ impl Dispatcher for LightDispatcher { } } -/// default MAC to use. -pub const DEFAULT_MAC: [u8; 2] = [0, 0]; - /// Single-use account token. pub type AccountToken = String; diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index b85e6600f..0a4265616 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -25,6 +25,7 @@ pub mod light_fetch; pub mod informant; pub mod oneshot; pub mod ipfs; +pub mod secretstore; mod network_settings; mod poll_manager; diff --git a/rpc/src/v1/helpers/secretstore.rs b/rpc/src/v1/helpers/secretstore.rs new file mode 100644 index 000000000..43c2c8943 --- /dev/null +++ b/rpc/src/v1/helpers/secretstore.rs @@ -0,0 +1,127 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::iter::repeat; +use rand::{Rng, OsRng}; +use ethkey::{Public, Secret, math}; +use crypto; +use util::Bytes; +use jsonrpc_core::Error; +use v1::helpers::errors; + +/// Initialization vector length. +const INIT_VEC_LEN: usize = 16; + +/// Encrypt document with distributely generated key. +pub fn encrypt_document(key: Bytes, document: Bytes) -> Result { + // make document key + let key = into_document_key(key)?; + + // use symmetric encryption to encrypt document + let iv = initialization_vector(); + let mut encrypted_document = Vec::with_capacity(document.len() + iv.len()); + encrypted_document.extend(repeat(0).take(document.len())); + crypto::aes::encrypt(&key, &iv, &document, &mut encrypted_document); + encrypted_document.extend_from_slice(&iv); + + Ok(encrypted_document) +} + +/// Decrypt document with distributely generated key. +pub fn decrypt_document(key: Bytes, mut encrypted_document: Bytes) -> Result { + // initialization vector takes INIT_VEC_LEN bytes + let encrypted_document_len = encrypted_document.len(); + if encrypted_document_len < INIT_VEC_LEN { + return Err(errors::invalid_params("encrypted_document", "invalid encrypted data")); + } + + // make document key + let key = into_document_key(key)?; + + // use symmetric decryption to decrypt document + let iv = encrypted_document.split_off(encrypted_document_len - INIT_VEC_LEN); + let mut document = Vec::with_capacity(encrypted_document_len - INIT_VEC_LEN); + document.extend(repeat(0).take(encrypted_document_len - INIT_VEC_LEN)); + crypto::aes::decrypt(&key, &iv, &encrypted_document, &mut document); + + Ok(document) +} + +pub fn decrypt_document_with_shadow(decrypted_secret: Public, common_point: Public, shadows: Vec, encrypted_document: Bytes) -> Result { + let key = decrypt_with_shadow_coefficients(decrypted_secret, common_point, shadows)?; + decrypt_document(key.to_vec(), encrypted_document) +} + +fn into_document_key(key: Bytes) -> Result { + // key is a previously distributely generated Public + if key.len() != 64 { + return Err(errors::invalid_params("key", "invalid public key length")); + } + + // use x coordinate of distributely generated point as encryption key + Ok(key[..INIT_VEC_LEN].into()) +} + +fn initialization_vector() -> [u8; INIT_VEC_LEN] { + let mut result = [0u8; INIT_VEC_LEN]; + let mut rng = OsRng::new().unwrap(); + rng.fill_bytes(&mut result); + result +} + +fn decrypt_with_shadow_coefficients(mut decrypted_shadow: Public, mut common_shadow_point: Public, shadow_coefficients: Vec) -> Result { + let mut shadow_coefficients_sum = shadow_coefficients[0].clone(); + for shadow_coefficient in shadow_coefficients.iter().skip(1) { + shadow_coefficients_sum.add(shadow_coefficient) + .map_err(errors::encryption_error)?; + } + + math::public_mul_secret(&mut common_shadow_point, &shadow_coefficients_sum) + .map_err(errors::encryption_error)?; + math::public_add(&mut decrypted_shadow, &common_shadow_point) + .map_err(errors::encryption_error)?; + Ok(decrypted_shadow) +} + +#[cfg(test)] +mod tests { + use util::Bytes; + use rustc_serialize::hex::FromHex; + use super::{encrypt_document, decrypt_document, decrypt_document_with_shadow}; + + #[test] + fn encrypt_and_decrypt_document() { + let document_key: Bytes = "cac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325".from_hex().unwrap(); + let document: Bytes = b"Hello, world!!!"[..].into(); + + let encrypted_document = encrypt_document(document_key.clone(), document.clone()).unwrap(); + assert!(document != encrypted_document); + + let decrypted_document = decrypt_document(document_key.clone(), encrypted_document).unwrap(); + assert_eq!(decrypted_document, document); + } + + #[test] + fn encrypt_and_shadow_decrypt_document() { + let document: Bytes = "deadbeef".from_hex().unwrap(); + let encrypted_document = "2ddec1f96229efa2916988d8b2a82a47ef36f71c".from_hex().unwrap(); + let decrypted_secret = "843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91".parse().unwrap(); + let common_point = "07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3".parse().unwrap(); + let shadows = vec!["46f542416216f66a7d7881f5a283d2a1ab7a87b381cbc5f29d0b093c7c89ee31".parse().unwrap()]; + let decrypted_document = decrypt_document_with_shadow(decrypted_secret, common_point, shadows, encrypted_document).unwrap(); + assert_eq!(decrypted_document, document); + } +} diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index cacf33db5..63e1c64b6 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -27,13 +27,14 @@ use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::LightSyncProvider; use ethcore::account_provider::AccountProvider; +use crypto::DEFAULT_MAC; use light::client::LightChainClient; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings}; -use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC}; +use v1::helpers::dispatch::LightDispatcher; use v1::helpers::light_fetch::LightFetch; use v1::metadata::Metadata; use v1::traits::Parity; diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index f5149d721..a128e7104 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -27,6 +27,7 @@ mod signer; mod signing; mod signing_unsafe; mod rpc; +mod secretstore; mod traces; mod web3; @@ -45,3 +46,4 @@ pub use self::signing::SigningQueueClient; pub use self::signing_unsafe::SigningUnsafeClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; +pub use self::secretstore::SecretStoreClient; diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 1b62b7f81..689ba3e70 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -34,12 +34,12 @@ use ethcore::client::{MiningBlockChainClient}; use ethcore::mode::Mode; use ethcore::account_provider::AccountProvider; use updater::{Service as UpdateService}; +use crypto::DEFAULT_MAC; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::accounts::unwrap_provider; -use v1::helpers::dispatch::DEFAULT_MAC; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ diff --git a/rpc/src/v1/impls/secretstore.rs b/rpc/src/v1/impls/secretstore.rs new file mode 100644 index 000000000..bf09baeb6 --- /dev/null +++ b/rpc/src/v1/impls/secretstore.rs @@ -0,0 +1,85 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! SecretStore-specific rpc implementation. + +use std::sync::{Arc, Weak}; + +use crypto::DEFAULT_MAC; +use ethkey::Secret; +use ethcore::account_provider::AccountProvider; + +use jsonrpc_core::Error; +use v1::helpers::errors; +use v1::helpers::accounts::unwrap_provider; +use v1::helpers::secretstore::{encrypt_document, decrypt_document, decrypt_document_with_shadow}; +use v1::traits::SecretStore; +use v1::types::{H160, H512, Bytes}; + +/// Parity implementation. +pub struct SecretStoreClient { + accounts: Option>, +} + +impl SecretStoreClient { + /// Creates new SecretStoreClient + pub fn new(store: &Option>) -> Self { + SecretStoreClient { + accounts: store.as_ref().map(Arc::downgrade), + } + } + + /// Attempt to get the `Arc`, errors if provider was not + /// set, or if upgrading the weak reference failed. + fn account_provider(&self) -> Result, Error> { + unwrap_provider(&self.accounts) + } + + /// Decrypt public key using account' private key + fn decrypt_key(&self, address: H160, password: String, key: Bytes) -> Result, Error> { + let store = self.account_provider()?; + store.decrypt(address.into(), Some(password), &DEFAULT_MAC, &key.0) + .map_err(|e| errors::account("Could not decrypt key.", e)) + } + + /// Decrypt secret key using account' private key + fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result { + self.decrypt_key(address, password, key) + .and_then(|s| Secret::from_slice(&s).map_err(|e| errors::account("invalid secret", e))) + } +} + +impl SecretStore for SecretStoreClient { + fn encrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result { + encrypt_document(self.decrypt_key(address, password, key)?, data.0) + .map(Into::into) + } + + fn decrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result { + decrypt_document(self.decrypt_key(address, password, key)?, data.0) + .map(Into::into) + } + + fn shadow_decrypt(&self, address: H160, password: String, decrypted_secret: H512, common_point: H512, decrypt_shadows: Vec, data: Bytes) -> Result { + let mut shadows = Vec::with_capacity(decrypt_shadows.len()); + for decrypt_shadow in decrypt_shadows { + shadows.push(self.decrypt_secret(address.clone(), password.clone(), decrypt_shadow)?); + } + + decrypt_document_with_shadow(decrypted_secret.into(), common_point.into(), shadows, data.0) + .map(Into::into) + } +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index bd0656219..e591cbd43 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -58,7 +58,7 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc, SecretStore}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; pub use self::metadata::Metadata; diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index aec2021a1..fed358574 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -25,6 +25,7 @@ mod parity_accounts; mod parity_set; mod personal; mod rpc; +mod secretstore; mod signer; mod signing; mod traces; diff --git a/rpc/src/v1/tests/mocked/secretstore.rs b/rpc/src/v1/tests/mocked/secretstore.rs new file mode 100644 index 000000000..ed278ced8 --- /dev/null +++ b/rpc/src/v1/tests/mocked/secretstore.rs @@ -0,0 +1,98 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::sync::Arc; + +use ethcore::account_provider::AccountProvider; + +use serde_json; +use jsonrpc_core::{IoHandler, Success}; +use v1::metadata::Metadata; +use v1::SecretStoreClient; +use v1::traits::secretstore::SecretStore; + +struct Dependencies { + pub accounts: Arc, +} + +impl Dependencies { + pub fn new() -> Self { + Dependencies { + accounts: Arc::new(AccountProvider::transient_provider()), + } + } + + pub fn client(&self) -> SecretStoreClient { + SecretStoreClient::new(&Some(self.accounts.clone())) + } + + fn default_client(&self) -> IoHandler { + let mut io = IoHandler::default(); + io.extend_with(self.client().to_delegate()); + io + } +} + +#[test] +fn rpc_secretstore_encrypt_and_decrypt() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + // insert new account && unlock it + let secret = "c1f1cfe279a5c350d13795bce162941967340c8a228e6ba175489afc564a5bef".parse().unwrap(); + deps.accounts.insert_account(secret, "password").unwrap(); + + // execute encryption request + let encryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_encrypt", "params":[ + "0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password", + "0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf", + "0xdeadbeef" + ], "id": 1}"#; + let encryption_response = io.handle_request_sync(encryption_request).unwrap(); + let encryption_response: Success = serde_json::from_str(&encryption_response).unwrap(); + + // execute decryption request + let decryption_request_left = r#"{"jsonrpc": "2.0", "method": "secretstore_decrypt", "params":[ + "0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password", + "0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf",""#; + let decryption_request_mid = encryption_response.result.as_str().unwrap(); + let decryption_request_right = r#"" + ], "id": 2}"#; + let decryption_request = decryption_request_left.to_owned() + decryption_request_mid + decryption_request_right; + let decryption_response = io.handle_request_sync(&decryption_request).unwrap(); + assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":2}"#); +} + +#[test] +fn rpc_secretstore_shadow_decrypt() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + // insert new account && unlock it + let secret = "82758356bf46b42710d3946a8efa612b7bf5e125e4d49f28facf1139db4a46f4".parse().unwrap(); + deps.accounts.insert_account(secret, "password").unwrap(); + + // execute decryption request + let decryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_shadowDecrypt", "params":[ + "0x00dfE63B22312ab4329aD0d28CaD8Af987A01932", "password", + "0x843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91", + "0x07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3", + ["0x049ce50bbadb6352574f2c59742f78df83333975cbd5cbb151c6e8628749a33dc1fa93bb6dffae5994e3eb98ae859ed55ee82937538e6adb054d780d1e89ff140f121529eeadb1161562af9d3342db0008919ca280a064305e5a4e518e93279de7a9396fe5136a9658e337e8e276221248c381c5384cd1ad28e5921f46ff058d5fbcf8a388fc881d0dd29421c218d51761"], + "0x2ddec1f96229efa2916988d8b2a82a47ef36f71c" + ], "id": 1}"#; + let decryption_response = io.handle_request_sync(&decryption_request).unwrap(); + assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":1}"#); +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 6279ca0b4..3f4125341 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -28,6 +28,7 @@ pub mod personal; pub mod signer; pub mod traces; pub mod rpc; +pub mod secretstore; pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter}; @@ -41,4 +42,4 @@ pub use self::personal::Personal; pub use self::signer::Signer; pub use self::traces::Traces; pub use self::rpc::Rpc; - +pub use self::secretstore::SecretStore; diff --git a/rpc/src/v1/traits/secretstore.rs b/rpc/src/v1/traits/secretstore.rs new file mode 100644 index 000000000..c13367562 --- /dev/null +++ b/rpc/src/v1/traits/secretstore.rs @@ -0,0 +1,41 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! SecretStore-specific rpc interface. + +use jsonrpc_core::Error; + +use v1::types::{H160, H512, Bytes}; + +build_rpc_trait! { + /// Parity-specific rpc interface. + pub trait SecretStore { + /// Encrypt data with key, received from secret store. + /// Arguments: `account`, `password`, `key`, `data`. + #[rpc(name = "secretstore_encrypt")] + fn encrypt(&self, H160, String, Bytes, Bytes) -> Result; + + /// Decrypt data with key, received from secret store. + /// Arguments: `account`, `password`, `key`, `data`. + #[rpc(name = "secretstore_decrypt")] + fn decrypt(&self, H160, String, Bytes, Bytes) -> Result; + + /// Decrypt data with shadow key, received from secret store. + /// Arguments: `account`, `password`, `decrypted_secret`, `common_point`, `decrypt_shadows`, `data`. + #[rpc(name = "secretstore_shadowDecrypt")] + fn shadow_decrypt(&self, H160, String, H512, H512, Vec, Bytes) -> Result; + } +} diff --git a/secret_store/src/http_listener.rs b/secret_store/src/http_listener.rs index 482fa92f6..bc51811de 100644 --- a/secret_store/src/http_listener.rs +++ b/secret_store/src/http_listener.rs @@ -24,9 +24,8 @@ use hyper::server::{Server as HttpServer, Request as HttpRequest, Response as Ht use serde_json; use url::percent_encoding::percent_decode; -use util::ToPretty; use traits::KeyServer; -use serialization::SerializableDocumentEncryptedKeyShadow; +use serialization::{SerializableDocumentEncryptedKeyShadow, SerializableBytes}; use types::all::{Error, NodeAddress, RequestSignature, DocumentAddress, DocumentEncryptedKey, DocumentEncryptedKeyShadow}; /// Key server http-requests listener @@ -168,9 +167,10 @@ impl HttpHandler for KeyServerHttpHandler where T: KeyServer + 'static { } fn return_document_key(req: HttpRequest, mut res: HttpResponse, document_key: Result) { + let document_key = document_key. + and_then(|k| serde_json::to_vec(&SerializableBytes(k)).map_err(|e| Error::Serde(e.to_string()))); match document_key { Ok(document_key) => { - let document_key = document_key.to_hex().into_bytes(); res.headers_mut().set(header::ContentType::plaintext()); if let Err(err) = res.send(&document_key) { // nothing to do, but to log an error @@ -186,6 +186,7 @@ fn return_error(mut res: HttpResponse, err: Error) { Error::BadSignature => *res.status_mut() = HttpStatusCode::BadRequest, Error::AccessDenied => *res.status_mut() = HttpStatusCode::Forbidden, Error::DocumentNotFound => *res.status_mut() = HttpStatusCode::NotFound, + Error::Serde(_) => *res.status_mut() = HttpStatusCode::BadRequest, Error::Database(_) => *res.status_mut() = HttpStatusCode::InternalServerError, Error::Internal(_) => *res.status_mut() = HttpStatusCode::InternalServerError, } diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs index 49016ac8e..f960c5cc5 100644 --- a/secret_store/src/key_server.rs +++ b/secret_store/src/key_server.rs @@ -67,7 +67,7 @@ impl KeyServer for KeyServerImpl { let document_key = encryption_session.wait(None)?; // encrypt document key with requestor public key - let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) + let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key) .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; Ok(document_key) } @@ -83,13 +83,13 @@ impl KeyServer for KeyServerImpl { let document_key = decryption_session.wait()?.decrypted_secret; // encrypt document key with requestor public key - let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) + let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key) .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; Ok(document_key) } fn document_key_shadow(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { - let decryption_session = self.data.lock().cluster.new_decryption_session(document.clone(), signature.clone(), false)?; + let decryption_session = self.data.lock().cluster.new_decryption_session(document.clone(), signature.clone(), true)?; decryption_session.wait().map_err(Into::into) } } @@ -118,7 +118,7 @@ impl KeyServerCore { return; }, }; - + let cluster = ClusterCore::new(el.handle(), config); let cluster_client = cluster.and_then(|c| c.run().map(|_| c.client())); tx.send(cluster_client.map_err(Into::into)).expect("Rx is blocking upper thread."); @@ -229,12 +229,12 @@ pub mod tests { let secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&secret, &document).unwrap(); let generated_key = key_servers[0].generate_document_key(&signature, &document, threshold).unwrap(); - let generated_key = ethcrypto::ecies::decrypt_single_message(&secret, &generated_key).unwrap(); + let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap(); // now let's try to retrieve key back for key_server in key_servers.iter() { let retrieved_key = key_server.document_key(&signature, &document).unwrap(); - let retrieved_key = ethcrypto::ecies::decrypt_single_message(&secret, &retrieved_key).unwrap(); + let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); assert_eq!(retrieved_key, generated_key); } } @@ -251,12 +251,12 @@ pub mod tests { let secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&secret, &document).unwrap(); let generated_key = key_servers[0].generate_document_key(&signature, &document, *threshold).unwrap(); - let generated_key = ethcrypto::ecies::decrypt_single_message(&secret, &generated_key).unwrap(); + let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap(); // now let's try to retrieve key back for key_server in key_servers.iter() { let retrieved_key = key_server.document_key(&signature, &document).unwrap(); - let retrieved_key = ethcrypto::ecies::decrypt_single_message(&secret, &retrieved_key).unwrap(); + let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); assert_eq!(retrieved_key, generated_key); } } diff --git a/secret_store/src/key_server_cluster/decryption_session.rs b/secret_store/src/key_server_cluster/decryption_session.rs index f4d64c246..8f5ecff7d 100644 --- a/secret_store/src/key_server_cluster/decryption_session.rs +++ b/secret_store/src/key_server_cluster/decryption_session.rs @@ -18,7 +18,8 @@ use std::cmp::{Ord, PartialOrd, Ordering}; use std::collections::{BTreeSet, BTreeMap}; use std::sync::Arc; use parking_lot::{Mutex, Condvar}; -use ethcrypto::ecies::encrypt_single_message; +use ethcrypto::ecies::encrypt; +use ethcrypto::DEFAULT_MAC; use ethkey::{self, Secret, Public, Signature}; use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId, DocumentEncryptedKeyShadow}; use key_server_cluster::cluster::Cluster; @@ -688,7 +689,7 @@ fn do_partial_decryption(node: &NodeId, requestor_public: &Public, is_shadow_dec shadow_point: shadow_point, decrypt_shadow: match decrypt_shadow { None => None, - Some(decrypt_shadow) => Some(encrypt_single_message(requestor_public, &**decrypt_shadow)?), + Some(decrypt_shadow) => Some(encrypt(requestor_public, &DEFAULT_MAC, &**decrypt_shadow)?), }, }) } @@ -1085,9 +1086,10 @@ mod tests { assert!(decrypted_secret.common_point.is_some()); assert!(decrypted_secret.decrypt_shadows.is_some()); // check that KS client is able to restore original secret - use ethcrypto::ecies::decrypt_single_message; + use ethcrypto::DEFAULT_MAC; + use ethcrypto::ecies::decrypt; let decrypt_shadows: Vec<_> = decrypted_secret.decrypt_shadows.unwrap().into_iter() - .map(|c| Secret::from_slice(&decrypt_single_message(key_pair.secret(), &c).unwrap()).unwrap()) + .map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()).unwrap()) .collect(); let decrypted_secret = math::decrypt_with_shadow_coefficients(decrypted_secret.decrypted_secret, decrypted_secret.common_point.unwrap(), decrypt_shadows).unwrap(); assert_eq!(decrypted_secret, SECRET_PLAIN.into()); diff --git a/secret_store/src/serialization.rs b/secret_store/src/serialization.rs index e918348fb..c88e9edf7 100644 --- a/secret_store/src/serialization.rs +++ b/secret_store/src/serialization.rs @@ -34,9 +34,9 @@ pub struct SerializableDocumentEncryptedKeyShadow { pub decrypt_shadows: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// Serializable Bytes. -pub struct SerializableBytes(Bytes); +pub struct SerializableBytes(pub Bytes); impl From for SerializableBytes where Bytes: From { fn from(s: T) -> SerializableBytes { @@ -60,7 +60,9 @@ impl Deref for SerializableBytes { impl Serialize for SerializableBytes { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_str(&(*self.0).to_hex()) + let mut serialized = "0x".to_owned(); + serialized.push_str(self.0.to_hex().as_ref()); + serializer.serialize_str(serialized.as_ref()) } } @@ -69,14 +71,18 @@ impl Deserialize for SerializableBytes { where D: Deserializer { let s = String::deserialize(deserializer)?; - let data = s.from_hex().map_err(SerdeError::custom)?; - Ok(SerializableBytes(data)) + if s.len() >= 2 && &s[0..2] == "0x" && s.len() & 1 == 0 { + let data = s[2..].from_hex().map_err(SerdeError::custom)?; + Ok(SerializableBytes(data)) + } else { + Err(SerdeError::custom("invalid format")) + } } } #[derive(Clone, Debug)] /// Serializable Signature. -pub struct SerializableSignature(Signature); +pub struct SerializableSignature(pub Signature); impl From for SerializableSignature where Signature: From { fn from(s: T) -> SerializableSignature { @@ -100,7 +106,9 @@ impl Deref for SerializableSignature { impl Serialize for SerializableSignature { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_str(&(*self.0).to_hex()) + let mut serialized = "0x".to_owned(); + serialized.push_str(self.0.to_hex().as_ref()); + serializer.serialize_str(serialized.as_ref()) } } @@ -116,7 +124,11 @@ impl Deserialize for SerializableSignature { } fn visit_str(self, value: &str) -> Result where E: SerdeError { - value.parse().map(|s| SerializableSignature(s)).map_err(SerdeError::custom) + if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { + value[2..].parse().map(|s| SerializableSignature(s)).map_err(SerdeError::custom) + } else { + Err(SerdeError::custom("invalid format")) + } } fn visit_string(self, value: String) -> Result where E: SerdeError { @@ -130,7 +142,7 @@ impl Deserialize for SerializableSignature { #[derive(Clone, Debug)] /// Serializable H256. -pub struct SerializableH256(H256); +pub struct SerializableH256(pub H256); impl From for SerializableH256 where H256: From { fn from(s: T) -> SerializableH256 { @@ -154,7 +166,9 @@ impl Deref for SerializableH256 { impl Serialize for SerializableH256 { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_str(&(*self.0).to_hex()) + let mut serialized = "0x".to_owned(); + serialized.push_str(self.0.to_hex().as_ref()); + serializer.serialize_str(serialized.as_ref()) } } @@ -170,7 +184,11 @@ impl Deserialize for SerializableH256 { } fn visit_str(self, value: &str) -> Result where E: SerdeError { - value.parse().map(|s| SerializableH256(s)).map_err(SerdeError::custom) + if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { + value[2..].parse().map(|s| SerializableH256(s)).map_err(SerdeError::custom) + } else { + Err(SerdeError::custom("invalid format")) + } } fn visit_string(self, value: String) -> Result where E: SerdeError { @@ -184,7 +202,7 @@ impl Deserialize for SerializableH256 { #[derive(Clone, Debug)] /// Serializable EC scalar/secret key. -pub struct SerializableSecret(Secret); +pub struct SerializableSecret(pub Secret); impl From for SerializableSecret where Secret: From { fn from(s: T) -> SerializableSecret { @@ -208,7 +226,9 @@ impl Deref for SerializableSecret { impl Serialize for SerializableSecret { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_str(&(*self.0).to_hex()) + let mut serialized = "0x".to_owned(); + serialized.push_str(self.0.to_hex().as_ref()); + serializer.serialize_str(serialized.as_ref()) } } @@ -224,7 +244,11 @@ impl Deserialize for SerializableSecret { } fn visit_str(self, value: &str) -> Result where E: SerdeError { - value.parse().map(|s| SerializableSecret(s)).map_err(SerdeError::custom) + if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { + value[2..].parse().map(|s| SerializableSecret(s)).map_err(SerdeError::custom) + } else { + Err(SerdeError::custom("invalid format")) + } } fn visit_string(self, value: String) -> Result where E: SerdeError { @@ -238,7 +262,7 @@ impl Deserialize for SerializableSecret { #[derive(Clone, Debug)] /// Serializable EC point/public key. -pub struct SerializablePublic(Public); +pub struct SerializablePublic(pub Public); impl From for SerializablePublic where Public: From { fn from(p: T) -> SerializablePublic { @@ -282,7 +306,9 @@ impl PartialOrd for SerializablePublic { impl Serialize for SerializablePublic { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_str(&(*self.0).to_hex()) + let mut serialized = "0x".to_owned(); + serialized.push_str(self.0.to_hex().as_ref()); + serializer.serialize_str(serialized.as_ref()) } } @@ -298,7 +324,11 @@ impl Deserialize for SerializablePublic { } fn visit_str(self, value: &str) -> Result where E: SerdeError { - value.parse().map(|s| SerializablePublic(s)).map_err(SerdeError::custom) + if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { + value[2..].parse().map(|s| SerializablePublic(s)).map_err(SerdeError::custom) + } else { + Err(SerdeError::custom("invalid format")) + } } fn visit_string(self, value: String) -> Result where E: SerdeError { @@ -309,3 +339,27 @@ impl Deserialize for SerializablePublic { deserializer.deserialize(HashVisitor) } } + +#[cfg(test)] +mod tests { + use serde_json; + use super::{SerializableBytes, SerializablePublic}; + + #[test] + fn serialize_and_deserialize_bytes() { + let bytes = SerializableBytes(vec![1, 2, 3, 4]); + let bytes_serialized = serde_json::to_string(&bytes).unwrap(); + assert_eq!(&bytes_serialized, r#""0x01020304""#); + let bytes_deserialized: SerializableBytes = serde_json::from_str(&bytes_serialized).unwrap(); + assert_eq!(bytes_deserialized, bytes); + } + + #[test] + fn serialize_and_deserialize_public() { + let public = SerializablePublic("cac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325".parse().unwrap()); + let public_serialized = serde_json::to_string(&public).unwrap(); + assert_eq!(&public_serialized, r#""0xcac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325""#); + let public_deserialized: SerializablePublic = serde_json::from_str(&public_serialized).unwrap(); + assert_eq!(public_deserialized, public); + } +} diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs index e3302a699..905841ea7 100644 --- a/secret_store/src/types/all.rs +++ b/secret_store/src/types/all.rs @@ -16,6 +16,7 @@ use std::fmt; use std::collections::BTreeMap; +use serde_json; use ethkey; use util; @@ -44,6 +45,8 @@ pub enum Error { AccessDenied, /// Requested document not found DocumentNotFound, + /// Serialization/deserialization error + Serde(String), /// Database-related error Database(String), /// Internal error @@ -107,12 +110,19 @@ impl fmt::Display for Error { Error::BadSignature => write!(f, "Bad signature"), Error::AccessDenied => write!(f, "Access dened"), Error::DocumentNotFound => write!(f, "Document not found"), + Error::Serde(ref msg) => write!(f, "Serialization error: {}", msg), Error::Database(ref msg) => write!(f, "Database error: {}", msg), Error::Internal(ref msg) => write!(f, "Internal error: {}", msg), } } } +impl From for Error { + fn from(err: serde_json::Error) -> Self { + Error::Serde(err.to_string()) + } +} + impl From for Error { fn from(err: ethkey::Error) -> Self { Error::Internal(err.into()) From ed7c366b909a83ae1cdccb3aad70fe12cf2f4a6f Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 5 May 2017 16:00:40 +0200 Subject: [PATCH 4/8] EIP-86 fixes (#5506) --- ethcore/src/client/client.rs | 13 ++++++------- ethcore/src/engines/mod.rs | 6 ++++++ ethcore/src/evm/instructions.rs | 4 ++-- ethcore/src/evm/interpreter/gasometer.rs | 2 +- ethcore/src/evm/interpreter/mod.rs | 6 +++--- ethcore/src/evm/schedule.rs | 13 ++++--------- ethcore/src/executive.rs | 7 ++++--- ethcore/src/miner/miner.rs | 2 +- ethcore/src/state/mod.rs | 5 +++++ 9 files changed, 32 insertions(+), 26 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b80d68317..ccf106521 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1354,8 +1354,7 @@ impl BlockChainClient for Client { .collect(); match (transaction, previous_receipts) { (Some(transaction), Some(previous_receipts)) => { - let schedule = self.engine().schedule(block_number); - Some(transaction_receipt(&schedule, transaction, previous_receipts)) + Some(transaction_receipt(self.engine(), transaction, previous_receipts)) }, _ => None, } @@ -1748,7 +1747,7 @@ impl Drop for Client { /// Returns `LocalizedReceipt` given `LocalizedTransaction` /// and a vector of receipts from given block up to transaction index. -fn transaction_receipt(schedule: &Schedule, mut tx: LocalizedTransaction, mut receipts: Vec) -> LocalizedReceipt { +fn transaction_receipt(engine: &Engine, mut tx: LocalizedTransaction, mut receipts: Vec) -> LocalizedReceipt { assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided."); let sender = tx.sender(); @@ -1772,7 +1771,7 @@ fn transaction_receipt(schedule: &Schedule, mut tx: LocalizedTransaction, mut re gas_used: receipt.gas_used - prior_gas_used, contract_address: match tx.action { Action::Call(_) => None, - Action::Create => Some(contract_address(schedule.create_address, &sender, &tx.nonce, &tx.data.sha3())) + Action::Create => Some(contract_address(engine.create_address_scheme(block_number), &sender, &tx.nonce, &tx.data.sha3())) }, logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry { entry: log, @@ -1827,17 +1826,17 @@ mod tests { #[test] fn should_return_correct_log_index() { use super::transaction_receipt; - use evm::schedule::Schedule; use ethkey::KeyPair; use log_entry::{LogEntry, LocalizedLogEntry}; use receipt::{Receipt, LocalizedReceipt}; use transaction::{Transaction, LocalizedTransaction, Action}; use util::Hashable; + use tests::helpers::TestEngine; // given let key = KeyPair::from_secret_slice(&"test".sha3()).unwrap(); let secret = key.secret(); - let schedule = Schedule::new_homestead(); + let engine = TestEngine::new(0); let block_number = 1; let block_hash = 5.into(); @@ -1881,7 +1880,7 @@ mod tests { }]; // when - let receipt = transaction_receipt(&schedule, transaction, receipts); + let receipt = transaction_receipt(&engine, transaction, receipts); // then assert_eq!(receipt, LocalizedReceipt { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ae57976b4..7041048b8 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -48,6 +48,7 @@ use receipt::Receipt; use snapshot::SnapshotComponents; use spec::CommonParams; use transaction::{UnverifiedTransaction, SignedTransaction}; +use evm::CreateContractAddress; use ethkey::Signature; use util::*; @@ -294,4 +295,9 @@ pub trait Engine : Sync + Send { fn snapshot_components(&self) -> Option> { None } + + /// Returns new contract address generation scheme at given block number. + fn create_address_scheme(&self, number: BlockNumber) -> CreateContractAddress { + if number >= self.params().eip86_transition { CreateContractAddress::FromCodeHash } else { CreateContractAddress::FromSenderAndNonce } + } } diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index 41c9e1ea1..eef1a9e3b 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -278,7 +278,7 @@ lazy_static! { arr[RETURN as usize] = InstructionInfo::new("RETURN", 0, 2, 0, true, GasPriceTier::Zero); arr[DELEGATECALL as usize] = InstructionInfo::new("DELEGATECALL", 0, 6, 1, true, GasPriceTier::Special); arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Special); - arr[CREATE_P2SH as usize] = InstructionInfo::new("CREATE_P2SH", 0, 3, 1, true, GasPriceTier::Special); + arr[CREATE2 as usize] = InstructionInfo::new("CREATE2", 0, 3, 1, true, GasPriceTier::Special); arr }; } @@ -555,7 +555,7 @@ pub const RETURN: Instruction = 0xf3; /// like CALLCODE but keeps caller's value and sender pub const DELEGATECALL: Instruction = 0xf4; /// create a new account and set creation address to sha3(sender + sha3(init code)) % 2**160 -pub const CREATE_P2SH: Instruction = 0xfb; +pub const CREATE2: Instruction = 0xfb; /// halt execution and register account for later deletion pub const SUICIDE: Instruction = 0xff; diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index fb0c86d35..246c93bad 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -223,7 +223,7 @@ impl Gasometer { Request::GasMemProvide(gas, mem, Some(requested)) }, - instructions::CREATE | instructions::CREATE_P2SH => { + instructions::CREATE | instructions::CREATE2 => { let gas = Gas::from(schedule.create_gas); let mem = mem_needed(stack.peek(1), stack.peek(2))?; diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 7fbab7ebc..f08737d24 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -183,7 +183,7 @@ impl Interpreter { let schedule = ext.schedule(); if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call) || - (instruction == instructions::CREATE_P2SH && !schedule.have_create_p2sh) { + (instruction == instructions::CREATE2 && !schedule.have_create2) { return Err(evm::Error::BadInstruction { instruction: instruction @@ -268,12 +268,12 @@ impl Interpreter { instructions::JUMPDEST => { // ignore }, - instructions::CREATE | instructions::CREATE_P2SH => { + instructions::CREATE | instructions::CREATE2 => { let endowment = stack.pop_back(); let init_off = stack.pop_back(); let init_size = stack.pop_back(); - let address_scheme = if instruction == instructions::CREATE { ext.schedule().create_address } else { CreateContractAddress::FromSenderAndCodeHash }; + let address_scheme = if instruction == instructions::CREATE { CreateContractAddress::FromSenderAndNonce } else { CreateContractAddress::FromSenderAndCodeHash }; let create_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is `CREATE`; qed"); let contract_code = self.mem.read_slice(init_off, init_size); diff --git a/ethcore/src/evm/schedule.rs b/ethcore/src/evm/schedule.rs index 97df4d784..3e01f3925 100644 --- a/ethcore/src/evm/schedule.rs +++ b/ethcore/src/evm/schedule.rs @@ -15,7 +15,6 @@ // along with Parity. If not, see . //! Cost schedule and other parameterisations for the EVM. -use evm::CreateContractAddress; /// Definition of the cost schedule and other parameterisations for the EVM. pub struct Schedule { @@ -24,7 +23,7 @@ pub struct Schedule { /// Does it have a delegate cal pub have_delegate_call: bool, /// Does it have a CREATE_P2SH instruction - pub have_create_p2sh: bool, + pub have_create2: bool, /// VM stack limit pub stack_limit: usize, /// Max number of nested calls/creates @@ -102,8 +101,6 @@ pub struct Schedule { pub no_empty: bool, /// Kill empty accounts if touched. pub kill_empty: bool, - /// Contract address generation scheme - pub create_address: CreateContractAddress, } impl Schedule { @@ -118,11 +115,11 @@ impl Schedule { } /// Schedule for the post-EIP-150-era of the Ethereum main net. - pub fn new_post_eip150(max_code_size: usize, fix_exp: bool, no_empty: bool, kill_empty: bool, have_create_p2sh: bool) -> Schedule { + pub fn new_post_eip150(max_code_size: usize, fix_exp: bool, no_empty: bool, kill_empty: bool, have_metropolis_instructions: bool) -> Schedule { Schedule { exceptional_failed_code_deposit: true, have_delegate_call: true, - have_create_p2sh: have_create_p2sh, + have_create2: have_metropolis_instructions, stack_limit: 1024, max_depth: 1024, tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0], @@ -161,7 +158,6 @@ impl Schedule { sub_gas_cap_divisor: Some(64), no_empty: no_empty, kill_empty: kill_empty, - create_address: if have_create_p2sh { CreateContractAddress::FromCodeHash } else { CreateContractAddress::FromSenderAndNonce }, } } @@ -174,7 +170,7 @@ impl Schedule { Schedule { exceptional_failed_code_deposit: efcd, have_delegate_call: hdc, - have_create_p2sh: false, + have_create2: false, stack_limit: 1024, max_depth: 1024, tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0], @@ -213,7 +209,6 @@ impl Schedule { sub_gas_cap_divisor: None, no_empty: false, kill_empty: false, - create_address: CreateContractAddress::FromSenderAndNonce, } } } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 494d59db9..1974a6c8d 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -185,7 +185,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let (gas_left, output) = match t.action { Action::Create => { let code_hash = t.data.sha3(); - let new_address = contract_address(schedule.create_address, &sender, &nonce, &code_hash); + let new_address = contract_address(self.engine.create_address_scheme(self.info.number), &sender, &nonce, &code_hash); let params = ActionParams { code_address: new_address.clone(), code_hash: code_hash, @@ -386,8 +386,8 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { vm_tracer: &mut V, ) -> evm::Result where T: Tracer, V: VMTracer { - let schedule = self.engine.schedule(self.info.number); - if schedule.create_address != CreateContractAddress::FromSenderAndNonce && self.state.exists(¶ms.address)? { + let scheme = self.engine.create_address_scheme(self.info.number); + if scheme != CreateContractAddress::FromSenderAndNonce && self.state.exists_and_has_code(¶ms.address)? { return Err(evm::Error::OutOfGas); } @@ -398,6 +398,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let mut unconfirmed_substate = Substate::new(); // create contract and transfer value to it if necessary + let schedule = self.engine.schedule(self.info.number); let nonce_offset = if schedule.no_empty {1} else {0}.into(); let prev_bal = self.state.balance(¶ms.address)?; if let ActionValue::Transfer(val) = params.value { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ed551e2f3..38e97f683 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1048,7 +1048,7 @@ impl MinerService for Miner { Action::Call(_) => None, Action::Create => { let sender = tx.sender(); - Some(contract_address(self.engine.schedule(pending.header().number()).create_address, &sender, &tx.nonce, &tx.data.sha3())) + Some(contract_address(self.engine.create_address_scheme(pending.header().number()), &sender, &tx.nonce, &tx.data.sha3())) } }, logs: receipt.logs.clone(), diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 639fac053..11ca9154f 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -433,6 +433,11 @@ impl State { self.ensure_cached(a, RequireCache::None, false, |a| a.map_or(false, |a| !a.is_null())) } + /// Determine whether an account exists and has code. + pub fn exists_and_has_code(&self, a: &Address) -> trie::Result { + self.ensure_cached(a, RequireCache::CodeSize, false, |a| a.map_or(false, |a| a.code_size().map_or(false, |size| size != 0))) + } + /// Get the balance of account `a`. pub fn balance(&self, a: &Address) -> trie::Result { self.ensure_cached(a, RequireCache::None, true, From 6dd1fe03125b9b9836020820a45e991598a52a6c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 5 May 2017 16:01:19 +0200 Subject: [PATCH 5/8] reorg into blocks before minimum history (#5558) --- ethcore/src/client/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ccf106521..a1f47251c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -377,7 +377,7 @@ impl Client { let chain = self.chain.read(); // Check the block isn't so old we won't be able to enact it. let best_block_number = chain.best_block_number(); - if best_block_number >= self.history && header.number() <= best_block_number - self.history { + if self.pruning_info().earliest_state > header.number() { warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number); return Err(()); } @@ -770,7 +770,7 @@ impl Client { let db = self.state_db.lock().boxed_clone(); // early exit for pruned blocks - if db.is_pruned() && self.chain.read().best_block_number() >= block_number + self.history { + if db.is_pruned() && self.pruning_info().earliest_state > block_number { return None; } @@ -871,7 +871,7 @@ impl Client { let best_block_number = self.chain_info().best_block_number; let block_number = self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))?; - if best_block_number > self.history + block_number && db.is_pruned() { + if db.is_pruned() && self.pruning_info().earliest_state > block_number { return Err(snapshot::Error::OldBlockPrunedDB.into()); } From 91d6f14e3c48c802fb4447c3ab681ed7f7acb10a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 6 May 2017 13:24:00 +0200 Subject: [PATCH 6/8] Fix CI paths. (#5570) --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 52dc50931..353941ae0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -254,7 +254,7 @@ linux-armv7: - cat .cargo/config - cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/$PLATFORM/release/parity - - export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h) + - export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h) - md5sum target/$PLATFORM/release/parity > parity.md5 - sh scripts/deb-build.sh armhf - cp target/$PLATFORM/release/parity deb/usr/bin/parity @@ -300,7 +300,7 @@ linux-arm: - cat .cargo/config - cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/$PLATFORM/release/parity - - export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h) + - export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h) - md5sum target/$PLATFORM/release/parity > parity.md5 - sh scripts/deb-build.sh armhf - cp target/$PLATFORM/release/parity deb/usr/bin/parity @@ -346,7 +346,7 @@ linux-armv6: - cat .cargo/config - cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS - arm-linux-gnueabi-strip target/$PLATFORM/release/parity - - export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h) + - export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h) - md5sum target/$PLATFORM/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret @@ -385,7 +385,7 @@ linux-aarch64: - cat .cargo/config - cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS - aarch64-linux-gnu-strip target/$PLATFORM/release/parity - - export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h) + - export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h) - md5sum target/$PLATFORM/release/parity > parity.md5 - sh scripts/deb-build.sh arm64 - cp target/$PLATFORM/release/parity deb/usr/bin/parity From 1617264b69e0901600e6ea331d42cf7ae8c4c17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 6 May 2017 13:24:18 +0200 Subject: [PATCH 7/8] Generic PubSub implementation (#5456) * Generic PubSub * Adding more tests. * Fix submodules. * Remove PartialEq * Actually remove the implementation. * Update mod.rs * Update mod.rs --- Cargo.lock | 12 ++ parity/rpc.rs | 61 ++++++- parity/rpc_apis.rs | 53 +++++-- parity/run.rs | 1 + rpc/Cargo.toml | 2 + rpc/src/lib.rs | 5 +- rpc/src/v1/helpers/mod.rs | 2 + rpc/src/v1/helpers/subscription_manager.rs | 175 +++++++++++++++++++++ rpc/src/v1/impls/mod.rs | 4 +- rpc/src/v1/impls/pubsub.rs | 100 ++++++++++++ rpc/src/v1/metadata.rs | 16 +- rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/mod.rs | 1 + rpc/src/v1/tests/mocked/pubsub.rs | 76 +++++++++ rpc/src/v1/traits/mod.rs | 2 + rpc/src/v1/traits/pubsub.rs | 39 +++++ 16 files changed, 524 insertions(+), 27 deletions(-) create mode 100644 rpc/src/v1/helpers/subscription_manager.rs create mode 100644 rpc/src/v1/impls/pubsub.rs create mode 100644 rpc/src/v1/tests/mocked/pubsub.rs create mode 100644 rpc/src/v1/traits/pubsub.rs diff --git a/Cargo.lock b/Cargo.lock index f59140ada..e92bae295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,6 +1705,7 @@ dependencies = [ "jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-minihttp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", + "jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-ws-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1722,6 +1723,7 @@ dependencies = [ "serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2554,6 +2556,15 @@ dependencies = [ "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-timer" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-uds" version = "0.1.4" @@ -2987,6 +2998,7 @@ dependencies = [ "checksum tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)" = "" "checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum tokio-timer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86f33def658c14724fc13ec6289b3875a8152ee8ae767a5b1ccbded363b03db8" "checksum tokio-uds 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bd209039933255ea77c6d7a1d18abc20b997d161acb900acca6eb74cdd049f31" "checksum toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "fcd27a04ca509aff336ba5eb2abc58d456f52c4ff64d9724d88acb85ead560b6" "checksum toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a442dfc13508e603c3f763274361db7f79d7469a0e95c411cde53662ab30fc72" diff --git a/parity/rpc.rs b/parity/rpc.rs index 1fc503767..2ac93baf7 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -22,7 +22,7 @@ use dir::default_data_path; use parity_rpc::informant::{RpcStats, Middleware}; use parity_rpc::{self as rpc, HttpServerError, Metadata, Origin, DomainsValidation}; use helpers::parity_ipc_path; -use jsonrpc_core::MetaIoHandler; +use jsonrpc_core::{futures, MetaIoHandler}; use parity_reactor::TokioRemote; use rpc_apis::{self, ApiSet}; @@ -126,11 +126,53 @@ impl rpc::IpcMetaExtractor for RpcExtractor { } } -impl rpc::ws::MetaExtractor for RpcExtractor { +struct Sender(rpc::ws::ws::Sender, futures::sync::mpsc::Receiver); + +impl futures::Future for Sender { + type Item = (); + type Error = (); + + fn poll(&mut self) -> futures::Poll { + use self::futures::Stream; + + let item = self.1.poll()?; + match item { + futures::Async::NotReady => { + Ok(futures::Async::NotReady) + }, + futures::Async::Ready(None) => { + Ok(futures::Async::Ready(())) + }, + futures::Async::Ready(Some(val)) => { + if let Err(e) = self.0.send(val) { + warn!("Error sending a subscription update: {:?}", e); + } + self.poll() + }, + } + } +} + +struct WsRpcExtractor { + remote: TokioRemote, +} + +impl WsRpcExtractor { + fn wrap_out(&self, out: rpc::ws::ws::Sender) -> futures::sync::mpsc::Sender { + let (sender, receiver) = futures::sync::mpsc::channel(8); + self.remote.spawn(move |_| Sender(out, receiver)); + sender + } +} + +impl rpc::ws::MetaExtractor for WsRpcExtractor { fn extract(&self, req: &rpc::ws::RequestContext) -> Metadata { let mut metadata = Metadata::default(); let id = req.session_id as u64; metadata.origin = Origin::Ws(id.into()); + metadata.session = Some(Arc::new(rpc::PubSubSession::new( + self.wrap_out(req.out.clone()) + ))); metadata } } @@ -173,10 +215,12 @@ pub fn new_ws( let start_result = rpc::start_ws( &addr, handler, - remote, + remote.clone(), allowed_origins, allowed_hosts, - RpcExtractor, + WsRpcExtractor { + remote: remote, + }, WsStats { stats: deps.stats.clone(), }, @@ -247,7 +291,14 @@ pub fn new_ipc( let handler = setup_apis(conf.apis, dependencies); let remote = dependencies.remote.clone(); - match rpc::start_ipc(&conf.socket_addr, handler, remote, RpcExtractor) { + let ipc = rpc::start_ipc( + &conf.socket_addr, + handler, + remote, + RpcExtractor, + ); + + match ipc { Ok(server) => Ok(Some(server)), Err(io_error) => Err(format!("IPC error: {}", io_error)), } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 0ff204aba..48e66e322 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -31,11 +31,12 @@ use parity_rpc::informant::{ActivityNotifier, Middleware, RpcStats, ClientNotifi use parity_rpc::dispatch::{FullDispatcher, LightDispatcher}; use ethsync::{ManageNetwork, SyncProvider, LightSync}; use hash_fetch::fetch::Client as FetchClient; -use jsonrpc_core::{MetaIoHandler}; +use jsonrpc_core::{self as core, MetaIoHandler}; use light::{TransactionQueue as LightTransactionQueue, Cache as LightDataCache}; use updater::Updater; use util::{Mutex, RwLock}; use ethcore_logger::RotatingLogger; +use parity_reactor; #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Api { @@ -195,18 +196,16 @@ pub struct FullDependencies { pub dapps_interface: Option, pub dapps_port: Option, pub fetch: FetchClient, + pub remote: parity_reactor::Remote, } -impl Dependencies for FullDependencies { - type Notifier = ClientNotifier; - - fn activity_notifier(&self) -> ClientNotifier { - ClientNotifier { - client: self.client.clone(), - } - } - - fn extend_with_set(&self, handler: &mut MetaIoHandler, apis: &[Api]) { +impl FullDependencies { + fn extend_api>( + &self, + handler: &mut MetaIoHandler, + apis: &[Api], + for_generic_pubsub: bool, + ) { use parity_rpc::v1::*; macro_rules! add_signing_methods { @@ -248,10 +247,12 @@ impl Dependencies for FullDependencies { ); handler.extend_with(client.to_delegate()); - let filter_client = EthFilterClient::new(self.client.clone(), self.miner.clone()); - handler.extend_with(filter_client.to_delegate()); + if !for_generic_pubsub { + let filter_client = EthFilterClient::new(self.client.clone(), self.miner.clone()); + handler.extend_with(filter_client.to_delegate()); - add_signing_methods!(EthSigning, handler, self); + add_signing_methods!(EthSigning, handler, self); + } }, Api::Personal => { handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility).to_delegate()); @@ -278,8 +279,14 @@ impl Dependencies for FullDependencies { self.dapps_port, ).to_delegate()); - add_signing_methods!(EthSigning, handler, self); - add_signing_methods!(ParitySigning, handler, self); + if !for_generic_pubsub { + let mut rpc = MetaIoHandler::default(); + self.extend_api(&mut rpc, apis, true); + handler.extend_with(PubSubClient::new(rpc, self.remote.clone()).to_delegate()); + + add_signing_methods!(EthSigning, handler, self); + add_signing_methods!(ParitySigning, handler, self); + } }, Api::ParityAccounts => { handler.extend_with(ParityAccountsClient::new(&self.secret_store).to_delegate()); @@ -308,6 +315,20 @@ impl Dependencies for FullDependencies { } } +impl Dependencies for FullDependencies { + type Notifier = ClientNotifier; + + fn activity_notifier(&self) -> ClientNotifier { + ClientNotifier { + client: self.client.clone(), + } + } + + fn extend_with_set(&self, handler: &mut MetaIoHandler>, apis: &[Api]) { + self.extend_api(handler, apis, false) + } +} + /// Light client notifier. Doesn't do anything yet, but might in the future. pub struct LightClientNotifier; diff --git a/parity/run.rs b/parity/run.rs index 9d90ba006..083cbe5ce 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -631,6 +631,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R false => None, }, fetch: fetch.clone(), + remote: event_loop.remote(), }); let dependencies = rpc::Dependencies { diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 0067bdeaa..b1e2d64aa 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -17,6 +17,7 @@ serde = "0.9" serde_derive = "0.9" serde_json = "0.9" time = "0.1" +tokio-timer = "0.1" transient-hashmap = "0.4" cid = "0.2.1" multihash = "0.5" @@ -29,6 +30,7 @@ jsonrpc-minihttp-server = { git = "https://github.com/paritytech/jsonrpc.git", b jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } +jsonrpc-pubsub = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } ethcore-io = { path = "../util/io" } ethcore-ipc = { path = "../ipc/rpc" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 291c5bcd8..4540d6b33 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -26,6 +26,7 @@ extern crate semver; extern crate serde; extern crate serde_json; extern crate time; +extern crate tokio_timer; extern crate transient_hashmap; extern crate cid; extern crate multihash; @@ -34,8 +35,9 @@ extern crate rand; extern crate jsonrpc_core; extern crate jsonrpc_http_server as http; -extern crate jsonrpc_minihttp_server as minihttp; extern crate jsonrpc_ipc_server as ipc; +extern crate jsonrpc_minihttp_server as minihttp; +extern crate jsonrpc_pubsub; extern crate ethash; extern crate ethcore; @@ -76,6 +78,7 @@ pub extern crate jsonrpc_ws_server as ws; mod metadata; pub mod v1; +pub use jsonrpc_pubsub::Session as PubSubSession; pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext}; pub use http::{ hyper, diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 0a4265616..1e26de852 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -33,6 +33,7 @@ mod poll_filter; mod requests; mod signer; mod signing_queue; +mod subscription_manager; pub use self::dispatch::{Dispatcher, FullDispatcher}; pub use self::network_settings::NetworkSettings; @@ -46,3 +47,4 @@ pub use self::signing_queue::{ QUEUE_LIMIT as SIGNING_QUEUE_LIMIT, }; pub use self::signer::SignerService; +pub use self::subscription_manager::GenericPollManager; diff --git a/rpc/src/v1/helpers/subscription_manager.rs b/rpc/src/v1/helpers/subscription_manager.rs new file mode 100644 index 000000000..4b82fdcc4 --- /dev/null +++ b/rpc/src/v1/helpers/subscription_manager.rs @@ -0,0 +1,175 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Generic poll manager for Pub-Sub. + +use std::sync::Arc; +use std::collections::HashMap; +use util::Mutex; + +use jsonrpc_core::futures::future::{self, Either}; +use jsonrpc_core::futures::sync::mpsc; +use jsonrpc_core::futures::{Sink, Future, BoxFuture}; +use jsonrpc_core::{self as core, MetaIoHandler}; + +use v1::metadata::Metadata; + +#[derive(Debug)] +struct Subscription { + metadata: Metadata, + method: String, + params: core::Params, + sink: mpsc::Sender>, + last_result: Arc>>, +} + +/// A struct managing all subscriptions. +/// TODO [ToDr] Depending on the method decide on poll interval. +/// For most of the methods it will be enough to poll on new block instead of time-interval. +pub struct GenericPollManager> { + next_id: usize, + poll_subscriptions: HashMap, + rpc: MetaIoHandler, +} + +impl> GenericPollManager { + /// Creates new poll manager + pub fn new(rpc: MetaIoHandler) -> Self { + GenericPollManager { + next_id: 1, + poll_subscriptions: Default::default(), + rpc: rpc, + } + } + + /// Subscribes to update from polling given method. + pub fn subscribe(&mut self, metadata: Metadata, method: String, params: core::Params) + -> (usize, mpsc::Receiver>) + { + let id = self.next_id; + self.next_id += 1; + + let (sink, stream) = mpsc::channel(1); + + let subscription = Subscription { + metadata: metadata, + method: method, + params: params, + sink: sink, + last_result: Default::default(), + }; + + debug!(target: "pubsub", "Adding subscription id={:?}, {:?}", id, subscription); + self.poll_subscriptions.insert(id, subscription); + (id, stream) + } + + pub fn unsubscribe(&mut self, id: usize) -> bool { + debug!(target: "pubsub", "Removing subscription: {:?}", id); + self.poll_subscriptions.remove(&id).is_some() + } + + pub fn tick(&self) -> BoxFuture<(), ()> { + let mut futures = Vec::new(); + // poll all subscriptions + for (id, subscription) in self.poll_subscriptions.iter() { + let call = core::MethodCall { + jsonrpc: Some(core::Version::V2), + id: core::Id::Num(*id as u64), + method: subscription.method.clone(), + params: Some(subscription.params.clone()), + }; + trace!(target: "pubsub", "Polling method: {:?}", call); + let result = self.rpc.handle_call(call.into(), subscription.metadata.clone()); + + let last_result = subscription.last_result.clone(); + let sender = subscription.sink.clone(); + + let result = result.and_then(move |response| { + let mut last_result = last_result.lock(); + if *last_result != response && response.is_some() { + let output = response.expect("Existence proved by the condition."); + debug!(target: "pubsub", "Got new response, sending: {:?}", output); + *last_result = Some(output.clone()); + + let send = match output { + core::Output::Success(core::Success { result, .. }) => Ok(result), + core::Output::Failure(core::Failure { error, .. }) => Err(error), + }; + Either::A(sender.send(send).map(|_| ()).map_err(|_| ())) + } else { + trace!(target: "pubsub", "Response was not changed: {:?}", response); + Either::B(future::ok(())) + } + }); + + futures.push(result) + } + + // return a future represeting all the polls + future::join_all(futures).map(|_| ()).boxed() + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{self, AtomicBool}; + + use jsonrpc_core::{MetaIoHandler, NoopMiddleware, Value, Params}; + use jsonrpc_core::futures::{Future, Stream}; + use http::tokio_core::reactor; + + use super::GenericPollManager; + + fn poll_manager() -> GenericPollManager { + let mut io = MetaIoHandler::default(); + let called = AtomicBool::new(false); + io.add_method("hello", move |_| { + if !called.load(atomic::Ordering::SeqCst) { + called.store(true, atomic::Ordering::SeqCst); + Ok(Value::String("hello".into())) + } else { + Ok(Value::String("world".into())) + } + }); + GenericPollManager::new(io) + } + + #[test] + fn should_poll_subscribed_method() { + // given + let mut el = reactor::Core::new().unwrap(); + let mut poll_manager = poll_manager(); + let (id, rx) = poll_manager.subscribe(Default::default(), "hello".into(), Params::None); + assert_eq!(id, 1); + + // then + poll_manager.tick().wait().unwrap(); + let (res, rx) = el.run(rx.into_future()).unwrap(); + assert_eq!(res, Some(Ok(Value::String("hello".into())))); + + // retrieve second item + poll_manager.tick().wait().unwrap(); + let (res, rx) = el.run(rx.into_future()).unwrap(); + assert_eq!(res, Some(Ok(Value::String("world".into())))); + + // and no more notifications + poll_manager.tick().wait().unwrap(); + // we need to unsubscribe otherwise the future will never finish. + poll_manager.unsubscribe(1); + assert_eq!(el.run(rx.into_future()).unwrap().0, None); + } +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index a128e7104..a8691b32b 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -23,6 +23,7 @@ mod parity; mod parity_accounts; mod parity_set; mod personal; +mod pubsub; mod signer; mod signing; mod signing_unsafe; @@ -33,7 +34,6 @@ mod web3; pub mod light; -pub use self::web3::Web3Client; pub use self::eth::{EthClient, EthClientOptions}; pub use self::eth_filter::EthFilterClient; pub use self::net::NetClient; @@ -41,9 +41,11 @@ pub use self::parity::ParityClient; pub use self::parity_accounts::ParityAccountsClient; pub use self::parity_set::ParitySetClient; pub use self::personal::PersonalClient; +pub use self::pubsub::PubSubClient; pub use self::signer::SignerClient; pub use self::signing::SigningQueueClient; pub use self::signing_unsafe::SigningUnsafeClient; pub use self::traces::TracesClient; +pub use self::web3::Web3Client; pub use self::rpc::RpcClient; pub use self::secretstore::SecretStoreClient; diff --git a/rpc/src/v1/impls/pubsub.rs b/rpc/src/v1/impls/pubsub.rs new file mode 100644 index 000000000..8badefdb8 --- /dev/null +++ b/rpc/src/v1/impls/pubsub.rs @@ -0,0 +1,100 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Parity-specific PUB-SUB rpc implementation. + +use std::sync::Arc; +use std::time::Duration; +use util::RwLock; + +use futures::{self, BoxFuture, Future, Stream, Sink}; +use jsonrpc_core::{self as core, Error, MetaIoHandler}; +use jsonrpc_macros::pubsub::Subscriber; +use jsonrpc_pubsub::SubscriptionId; +use tokio_timer; + +use parity_reactor::Remote; +use v1::helpers::GenericPollManager; +use v1::metadata::Metadata; +use v1::traits::PubSub; + +/// Parity PubSub implementation. +pub struct PubSubClient> { + poll_manager: Arc>>, + remote: Remote, +} + +impl> PubSubClient { + /// Creates new `PubSubClient`. + pub fn new(rpc: MetaIoHandler, remote: Remote) -> Self { + let poll_manager = Arc::new(RwLock::new(GenericPollManager::new(rpc))); + let pm2 = poll_manager.clone(); + + let timer = tokio_timer::wheel() + .tick_duration(Duration::from_millis(500)) + .build(); + + // Start ticking + let interval = timer.interval(Duration::from_millis(1000)); + remote.spawn(interval + .map_err(|e| warn!("Polling timer error: {:?}", e)) + .for_each(move |_| pm2.read().tick()) + ); + + PubSubClient { + poll_manager: poll_manager, + remote: remote, + } + } +} + +impl> PubSub for PubSubClient { + type Metadata = Metadata; + + fn parity_subscribe(&self, mut meta: Metadata, subscriber: Subscriber, method: String, params: core::Params) { + // Make sure to get rid of PubSub session otherwise it will never be dropped. + meta.session = None; + + let mut poll_manager = self.poll_manager.write(); + let (id, receiver) = poll_manager.subscribe(meta, method, params); + match subscriber.assign_id(SubscriptionId::Number(id as u64)) { + Ok(sink) => { + self.remote.spawn(receiver.map(|res| match res { + Ok(val) => val, + Err(error) => { + warn!(target: "pubsub", "Subscription error: {:?}", error); + core::Value::Null + }, + }).forward(sink.sink_map_err(|e| { + warn!("Cannot send notification: {:?}", e); + })).map(|_| ())); + }, + Err(_) => { + poll_manager.unsubscribe(id); + }, + } + } + + fn parity_unsubscribe(&self, id: SubscriptionId) -> BoxFuture { + let res = if let SubscriptionId::Number(id) = id { + self.poll_manager.write().unsubscribe(id as usize) + } else { + false + }; + + futures::future::ok(res).boxed() + } +} diff --git a/rpc/src/v1/metadata.rs b/rpc/src/v1/metadata.rs index 26c79d976..74567d510 100644 --- a/rpc/src/v1/metadata.rs +++ b/rpc/src/v1/metadata.rs @@ -14,20 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Arc; + use jsonrpc_core; +use jsonrpc_pubsub::{Session, PubSubMetadata}; use v1::types::{DappId, Origin}; /// RPC methods metadata. -#[derive(Clone, Default, Debug, PartialEq)] +#[derive(Clone, Default, Debug)] pub struct Metadata { /// Request origin pub origin: Origin, + /// Request PubSub Session + pub session: Option>, } impl Metadata { - /// Get + /// Returns dapp id if this request is coming from a Dapp or default `DappId` otherwise. pub fn dapp_id(&self) -> DappId { + // TODO [ToDr] Extract dapp info from Ws connections. match self.origin { Origin::Dapps(ref dapp_id) => dapp_id.clone(), _ => DappId::default(), @@ -36,4 +42,8 @@ impl Metadata { } impl jsonrpc_core::Metadata for Metadata {} - +impl PubSubMetadata for Metadata { + fn session(&self) -> Option> { + self.session.clone() + } +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index e591cbd43..59aef84b3 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -58,7 +58,7 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc, SecretStore}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; pub use self::metadata::Metadata; diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index fed358574..e5a459633 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -24,6 +24,7 @@ mod parity; mod parity_accounts; mod parity_set; mod personal; +mod pubsub; mod rpc; mod secretstore; mod signer; diff --git a/rpc/src/v1/tests/mocked/pubsub.rs b/rpc/src/v1/tests/mocked/pubsub.rs new file mode 100644 index 000000000..b7c8963e5 --- /dev/null +++ b/rpc/src/v1/tests/mocked/pubsub.rs @@ -0,0 +1,76 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::sync::{atomic, Arc}; + +use jsonrpc_core::{self as core, MetaIoHandler}; +use jsonrpc_core::futures::{self, Stream, Future}; +use jsonrpc_pubsub::Session; + +use parity_reactor::EventLoop; +use v1::{PubSub, PubSubClient, Metadata}; + +fn rpc() -> MetaIoHandler { + let mut io = MetaIoHandler::default(); + let called = atomic::AtomicBool::new(false); + io.add_method("hello", move |_| { + if !called.load(atomic::Ordering::SeqCst) { + called.store(true, atomic::Ordering::SeqCst); + Ok(core::Value::String("hello".into())) + } else { + Ok(core::Value::String("world".into())) + } + }); + io +} + +#[test] +fn should_subscribe_to_a_method() { + // given + let el = EventLoop::spawn(); + let rpc = rpc(); + let pubsub = PubSubClient::new(rpc, el.remote()).to_delegate(); + + let mut io = MetaIoHandler::default(); + io.extend_with(pubsub); + + let mut metadata = Metadata::default(); + let (sender, receiver) = futures::sync::mpsc::channel(8); + metadata.session = Some(Arc::new(Session::new(sender))); + + // Subscribe + let request = r#"{"jsonrpc": "2.0", "method": "parity_subscribe", "params": ["hello", []], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; + assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned())); + + // Check notifications + let (res, receiver) = receiver.into_future().wait().unwrap(); + let response = r#"{"jsonrpc":"2.0","method":"parity_subscription","params":{"result":"hello","subscription":1}}"#; + assert_eq!(res, Some(response.into())); + + let (res, receiver) = receiver.into_future().wait().unwrap(); + let response = r#"{"jsonrpc":"2.0","method":"parity_subscription","params":{"result":"world","subscription":1}}"#; + assert_eq!(res, Some(response.into())); + + // And unsubscribe + let request = r#"{"jsonrpc": "2.0", "method": "parity_unsubscribe", "params": [1], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + assert_eq!(io.handle_request_sync(request, metadata), Some(response.to_owned())); + + let (res, _receiver) = receiver.into_future().wait().unwrap(); + assert_eq!(res, None); +} + diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 3f4125341..9cef58ee7 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -25,6 +25,7 @@ pub mod parity_accounts; pub mod parity_set; pub mod parity_signing; pub mod personal; +pub mod pubsub; pub mod signer; pub mod traces; pub mod rpc; @@ -39,6 +40,7 @@ pub use self::parity_accounts::ParityAccounts; pub use self::parity_set::ParitySet; pub use self::parity_signing::ParitySigning; pub use self::personal::Personal; +pub use self::pubsub::PubSub; pub use self::signer::Signer; pub use self::traces::Traces; pub use self::rpc::Rpc; diff --git a/rpc/src/v1/traits/pubsub.rs b/rpc/src/v1/traits/pubsub.rs new file mode 100644 index 000000000..a4672403a --- /dev/null +++ b/rpc/src/v1/traits/pubsub.rs @@ -0,0 +1,39 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Parity-specific PUB-SUB rpc interface. + +use jsonrpc_core::{Error, Value, Params}; +use jsonrpc_pubsub::SubscriptionId; +use jsonrpc_macros::pubsub::Subscriber; +use futures::BoxFuture; + +build_rpc_trait! { + /// Parity-specific PUB-SUB rpc interface. + pub trait PubSub { + type Metadata; + + #[pubsub(name = "parity_subscription")] { + /// Subscribe to changes of any RPC method in Parity. + #[rpc(name = "parity_subscribe")] + fn parity_subscribe(&self, Self::Metadata, Subscriber, String, Params); + + /// Unsubscribe from existing Parity subscription. + #[rpc(name = "parity_unsubscribe")] + fn parity_unsubscribe(&self, SubscriptionId) -> BoxFuture; + } + } +} From 0bd4d5bb0c1e65eff90d2a641f70808f00eb80e9 Mon Sep 17 00:00:00 2001 From: YaNing Zhang Date: Sun, 7 May 2017 20:33:52 +0800 Subject: [PATCH 8/8] Typo (#5547) --- ethcore/src/state/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 11ca9154f..e710559df 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -238,7 +238,7 @@ pub fn check_proof( /// Reverting a checkpoint with `revert_to_checkpoint` involves copying /// original values from the latest checkpoint back into `cache`. The code /// takes care not to overwrite cached storage while doing that. -/// checkpoint can be discateded with `discard_checkpoint`. All of the orignal +/// checkpoint can be discarded with `discard_checkpoint`. All of the orignal /// backed-up values are moved into a parent checkpoint (if any). /// pub struct State {