diff --git a/Cargo.lock b/Cargo.lock index a56d1b663fc..41d14ba54ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,6 +1045,7 @@ dependencies = [ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "time-utils 0.1.0", "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "transaction-pool 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "trie-db 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/private-tx/Cargo.toml b/ethcore/private-tx/Cargo.toml index 2ce127a8b7a..cd484d53f7b 100644 --- a/ethcore/private-tx/Cargo.toml +++ b/ethcore/private-tx/Cargo.toml @@ -35,6 +35,7 @@ rustc-hex = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +time-utils = { path = "../../util/time-utils" } tiny-keccak = "1.4" transaction-pool = "2.0" url = "1" diff --git a/ethcore/private-tx/src/error.rs b/ethcore/private-tx/src/error.rs index eda08b2a567..b4fc4a3fad3 100644 --- a/ethcore/private-tx/src/error.rs +++ b/ethcore/private-tx/src/error.rs @@ -25,6 +25,7 @@ use ethkey::Error as KeyError; use ethkey::crypto::Error as CryptoError; use txpool::VerifiedTransaction; use private_transactions::VerifiedPrivateTransaction; +use serde_json::{Error as SerdeError}; type TxPoolError = txpool::Error<::Hash>; @@ -45,6 +46,9 @@ pub enum Error { /// Crypto error. #[display(fmt = "Crypto Error {}", _0)] Crypto(CryptoError), + /// Serialization error. + #[display(fmt = "Serialization Error {}", _0)] + Json(SerdeError), /// Encryption error. #[display(fmt = "Encryption error. ({})", _0)] Encrypt(String), @@ -99,6 +103,15 @@ pub enum Error { /// Key server URL is not set. #[display(fmt = "Key server URL is not set.")] KeyServerNotSet, + /// Transaction not found in logs. + #[display(fmt = "Private transaction not found in logs.")] + TxNotFoundInLog, + /// Path for logging not set. + #[display(fmt = "Path for logging not set.")] + LoggingPathNotSet, + /// Timestamp overflow error. + #[display(fmt = "Timestamp overflow error.")] + TimestampOverflow, /// VM execution error. #[display(fmt = "VM execution error {}", _0)] Execution(ExecutionError), @@ -123,6 +136,7 @@ impl error::Error for Error { Error::Decoder(e) => Some(e), Error::Trie(e) => Some(e), Error::TxPool(e) => Some(e), + Error::Json(e) => Some(e), Error::Crypto(e) => Some(e), Error::Execution(e) => Some(e), Error::Key(e) => Some(e), @@ -187,6 +201,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: SerdeError) -> Self { + Error::Json(err).into() + } +} + impl From for Error { fn from(err: EthcoreError) -> Self { Error::Ethcore(err).into() diff --git a/ethcore/private-tx/src/lib.rs b/ethcore/private-tx/src/lib.rs index d487b4d835b..aaf44d572b6 100644 --- a/ethcore/private-tx/src/lib.rs +++ b/ethcore/private-tx/src/lib.rs @@ -25,6 +25,7 @@ mod key_server_keys; mod private_transactions; mod messages; mod error; +mod log; extern crate common_types as types; extern crate ethabi; @@ -45,11 +46,15 @@ extern crate parking_lot; extern crate trie_db as trie; extern crate patricia_trie_ethereum as ethtrie; extern crate rlp; +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_json; extern crate rustc_hex; extern crate transaction_pool as txpool; extern crate url; #[macro_use] -extern crate log; +extern crate log as ethlog; #[macro_use] extern crate ethabi_derive; #[macro_use] @@ -58,6 +63,9 @@ extern crate derive_more; #[macro_use] extern crate rlp_derive; +#[cfg(not(time_checked_add))] +extern crate time_utils; + #[cfg(test)] extern crate rand; #[cfg(test)] @@ -68,6 +76,7 @@ pub use key_server_keys::{KeyProvider, SecretStoreKeys, StoringKeyProvider}; pub use private_transactions::{VerifiedPrivateTransaction, VerificationStore, PrivateTransactionSigningDesc, SigningStore}; pub use messages::{PrivateTransaction, SignedPrivateTransaction}; pub use error::Error; +pub use log::{Logging, TransactionLog, ValidatorLog, PrivateTxStatus, FileLogsSerializer}; use std::sync::{Arc, Weak}; use std::collections::{HashMap, HashSet, BTreeMap}; @@ -117,6 +126,8 @@ pub struct ProviderConfig { pub validator_accounts: Vec
, /// Account used for signing public transactions created from private transactions pub signer_account: Option
, + /// Path to private tx logs + pub logs_path: Option, } #[derive(Debug)] @@ -177,6 +188,7 @@ pub struct Provider { accounts: Arc, channel: IoChannel, keys_provider: Arc, + logging: Option, } #[derive(Debug)] @@ -211,6 +223,7 @@ impl Provider { accounts, channel, keys_provider, + logging: config.logs_path.map(|path| Logging::new(Arc::new(FileLogsSerializer::with_path(path)))), } } @@ -257,8 +270,11 @@ impl Provider { trace!(target: "privatetx", "Required validators: {:?}", contract_validators); let private_state_hash = self.calculate_state_hash(&private_state, contract_nonce); trace!(target: "privatetx", "Hashed effective private state for sender: {:?}", private_state_hash); - self.transactions_for_signing.write().add_transaction(private.hash(), signed_transaction, contract_validators, private_state, contract_nonce)?; + self.transactions_for_signing.write().add_transaction(private.hash(), signed_transaction, &contract_validators, private_state, contract_nonce)?; self.broadcast_private_transaction(private.hash(), private.rlp_bytes()); + if let Some(ref logging) = self.logging { + logging.private_tx_created(&tx_hash, &contract_validators); + } Ok(Receipt { hash: tx_hash, contract_address: contract, @@ -354,8 +370,9 @@ impl Provider { Some(desc) => desc, }; let last = self.last_required_signature(&desc, signed_tx.signature())?; + let original_tx_hash = desc.original_transaction.hash(); - if last { + if last.0 { let mut signatures = desc.received_signatures.clone(); signatures.push(signed_tx.signature()); let rsv: Vec = signatures.into_iter().map(|sign| sign.into_electrum().into()).collect(); @@ -373,8 +390,8 @@ impl Provider { trace!(target: "privatetx", "Last required signature received, public transaction created: {:?}", public_tx); // Sign and add it to the queue let chain_id = desc.original_transaction.chain_id(); - let hash = public_tx.hash(chain_id); - let signature = self.accounts.sign(signer_account, hash)?; + let public_tx_hash = public_tx.hash(chain_id); + let signature = self.accounts.sign(signer_account, public_tx_hash)?; let signed = SignedTransaction::new(public_tx.with_signature(signature, chain_id))?; match self.miner.import_own_transaction(&*self.client, signed.into()) { Ok(_) => trace!(target: "privatetx", "Public transaction added to queue"), @@ -392,6 +409,11 @@ impl Provider { Err(err) => warn!(target: "privatetx", "Failed to send private state changed notification, error: {:?}", err), } } + // Store logs + if let Some(ref logging) = self.logging { + logging.signature_added(&original_tx_hash, &last.1); + logging.tx_deployed(&original_tx_hash, &public_tx_hash); + } // Remove from store for signing if let Err(err) = self.transactions_for_signing.write().remove(&private_hash) { warn!(target: "privatetx", "Failed to remove transaction from signing store, error: {:?}", err); @@ -400,7 +422,12 @@ impl Provider { } else { // Add signature to the store match self.transactions_for_signing.write().add_signature(&private_hash, signed_tx.signature()) { - Ok(_) => trace!(target: "privatetx", "Signature stored for private transaction"), + Ok(_) => { + trace!(target: "privatetx", "Signature stored for private transaction"); + if let Some(ref logging) = self.logging { + logging.signature_added(&original_tx_hash, &last.1); + } + } Err(err) => { warn!(target: "privatetx", "Failed to add signature to signing store, error: {:?}", err); return Err(err); @@ -420,17 +447,14 @@ impl Provider { } } - fn last_required_signature(&self, desc: &PrivateTransactionSigningDesc, sign: Signature) -> Result { - if desc.received_signatures.contains(&sign) { - return Ok(false); - } + fn last_required_signature(&self, desc: &PrivateTransactionSigningDesc, sign: Signature) -> Result<(bool, Address), Error> { let state_hash = self.calculate_state_hash(&desc.state, desc.contract_nonce); match recover(&sign, &state_hash) { Ok(public) => { let sender = public_to_address(&public); match desc.validators.contains(&sender) { true => { - Ok(desc.received_signatures.len() + 1 == desc.validators.len()) + Ok((desc.received_signatures.len() + 1 == desc.validators.len(), sender)) } false => { warn!(target: "privatetx", "Sender's state doesn't correspond to validator's"); @@ -674,6 +698,14 @@ impl Provider { Ok(result.result) } + /// Retrieves log information about private transaction + pub fn private_log(&self, tx_hash: H256) -> Result { + match self.logging { + Some(ref logging) => logging.tx_log(&tx_hash).ok_or(Error::TxNotFoundInLog), + None => Err(Error::LoggingPathNotSet), + } + } + /// Returns private validators for a contract. pub fn get_validators(&self, block: BlockId, address: &Address) -> Result, Error> { let (data, decoder) = private_contract::functions::get_validators::call(); diff --git a/ethcore/private-tx/src/log.rs b/ethcore/private-tx/src/log.rs new file mode 100644 index 00000000000..d836dfa3654 --- /dev/null +++ b/ethcore/private-tx/src/log.rs @@ -0,0 +1,408 @@ +// Copyright 2015-2018 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 . + +//! Private transactions logs. + +use ethereum_types::{H256, Address}; +use std::collections::HashMap; +use std::fs::File; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::{SystemTime, Duration, Instant}; +use parking_lot::RwLock; +use serde::ser::{Serializer, SerializeSeq}; +use error::Error; + +#[cfg(not(time_checked_add))] +use time_utils::CheckedSystemTime; + +/// Maximum amount of stored private transaction logs. +const MAX_JOURNAL_LEN: usize = 1000; + +/// Maximum period for storing private transaction logs. +/// Logs older than 20 days will not be processed +const MAX_STORING_TIME: Duration = Duration::from_secs(60 * 60 * 24 * 20); + +/// Source of monotonic time for log timestamps +struct MonoTime { + start_time: SystemTime, + start_inst: Instant +} + +impl MonoTime { + fn new(start: SystemTime) -> Self { + Self { + start_time: start, + start_inst: Instant::now() + } + } + + fn elapsed(&self) -> Duration { + self.start_inst.elapsed() + } + + fn to_system_time(&self) -> SystemTime { + self.start_time + self.elapsed() + } +} + +impl Default for MonoTime { + fn default() -> Self { + MonoTime::new(SystemTime::now()) + } +} + +/// Current status of the private transaction +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +pub enum PrivateTxStatus { + /// Private tx was created but no validation received yet + Created, + /// Several validators (but not all) validated the transaction + Validating, + /// All validators has validated the private tx + /// Corresponding public tx was created and added into the pool + Deployed, +} + +/// Information about private tx validation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ValidatorLog { + /// Account of the validator + pub account: Address, + /// Validation timestamp, None if the transaction is not validated + pub validation_timestamp: Option, +} + +#[cfg(test)] +impl PartialEq for ValidatorLog { + fn eq(&self, other: &Self) -> bool { + self.account == other.account + } +} + +/// Information about the private transaction +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TransactionLog { + /// Original signed transaction hash (used as a source for private tx) + pub tx_hash: H256, + /// Current status of the private transaction + pub status: PrivateTxStatus, + /// Creation timestamp + pub creation_timestamp: SystemTime, + /// List of validations + pub validators: Vec, + /// Timestamp of the resulting public tx deployment + pub deployment_timestamp: Option, + /// Hash of the resulting public tx + pub public_tx_hash: Option, +} + +#[cfg(test)] +impl PartialEq for TransactionLog { + fn eq(&self, other: &Self) -> bool { + self.tx_hash == other.tx_hash && + self.status == other.status && + self.validators == other.validators && + self.public_tx_hash == other.public_tx_hash + } +} + +/// Wrapper other JSON serializer +pub trait LogsSerializer: Send + Sync + 'static { + /// Read logs from the source + fn read_logs(&self) -> Result, Error>; + + /// Write all logs to the source + fn flush_logs(&self, logs: &HashMap) -> Result<(), Error>; +} + +/// Logs serializer to the json file +pub struct FileLogsSerializer { + logs_dir: PathBuf, +} + +impl FileLogsSerializer { + pub fn with_path>(logs_dir: P) -> Self { + FileLogsSerializer { + logs_dir: logs_dir.into(), + } + } + + fn open_file(&self, to_create: bool) -> Result { + let file_path = self.logs_dir.with_file_name("private_tx.log"); + if to_create { + File::create(&file_path).map_err(From::from) + } else { + File::open(&file_path).map_err(From::from) + } + } +} + +impl LogsSerializer for FileLogsSerializer { + fn read_logs(&self) -> Result, Error> { + let log_file = self.open_file(false)?; + match serde_json::from_reader(log_file) { + Ok(logs) => Ok(logs), + Err(err) => { + error!(target: "privatetx", "Cannot deserialize logs from file: {}", err); + return Err(format!("Cannot deserialize logs from file: {:?}", err).into()); + } + } + } + + fn flush_logs(&self, logs: &HashMap) -> Result<(), Error> { + if logs.is_empty() { + // Do not create empty file + return Ok(()); + } + let log_file = self.open_file(true)?; + let mut json = serde_json::Serializer::new(log_file); + let mut json_array = json.serialize_seq(Some(logs.len()))?; + for v in logs.values() { + json_array.serialize_element(v)?; + } + json_array.end()?; + Ok(()) + } +} + +/// Private transactions logging +pub struct Logging { + logs: RwLock>, + logs_serializer: Arc, + mono_time: MonoTime, +} + +impl Logging { + /// Creates the logging object + pub fn new(logs_serializer: Arc) -> Self { + let mut logging = Logging { + logs: RwLock::new(HashMap::new()), + logs_serializer, + mono_time: MonoTime::default(), + }; + match logging.read_logs() { + // Initialize time source by max from current system time and max creation time from already saved logs + Ok(initial_time) => logging.mono_time = MonoTime::new(initial_time), + Err(err) => warn!(target: "privatetx", "Cannot read logs: {:?}", err), + } + logging + } + + /// Retrieves log for the corresponding tx hash + pub fn tx_log(&self, tx_hash: &H256) -> Option { + self.logs.read().get(&tx_hash).cloned() + } + + /// Logs the creation of the private transaction + pub fn private_tx_created(&self, tx_hash: &H256, validators: &[Address]) { + let mut validator_logs = Vec::new(); + for account in validators { + validator_logs.push(ValidatorLog { + account: *account, + validation_timestamp: None, + }); + } + let mut logs = self.logs.write(); + if logs.len() > MAX_JOURNAL_LEN { + // Remove the oldest log + if let Some(tx_hash) = logs.values() + .min_by(|x, y| x.creation_timestamp.cmp(&y.creation_timestamp)) + .map(|oldest| oldest.tx_hash) + { + logs.remove(&tx_hash); + } + } + logs.insert(*tx_hash, TransactionLog { + tx_hash: *tx_hash, + status: PrivateTxStatus::Created, + creation_timestamp: self.mono_time.to_system_time(), + validators: validator_logs, + deployment_timestamp: None, + public_tx_hash: None, + }); + } + + /// Logs the validation of the private transaction by one of its validators + pub fn signature_added(&self, tx_hash: &H256, validator: &Address) { + let mut logs = self.logs.write(); + if let Some(transaction_log) = logs.get_mut(&tx_hash) { + if let Some(ref mut validator_log) = transaction_log.validators.iter_mut().find(|log| log.account == *validator) { + transaction_log.status = PrivateTxStatus::Validating; + validator_log.validation_timestamp = Some(self.mono_time.to_system_time()); + } + } + } + + /// Logs the final deployment of the resulting public transaction + pub fn tx_deployed(&self, tx_hash: &H256, public_tx_hash: &H256) { + let mut logs = self.logs.write(); + if let Some(log) = logs.get_mut(&tx_hash) { + log.status = PrivateTxStatus::Deployed; + log.deployment_timestamp = Some(self.mono_time.to_system_time()); + log.public_tx_hash = Some(*public_tx_hash); + } + } + + fn read_logs(&self) -> Result { + let mut transaction_logs = self.logs_serializer.read_logs()?; + // Drop old logs + let earliest_possible = SystemTime::now().checked_sub(MAX_STORING_TIME).ok_or(Error::TimestampOverflow)?; + transaction_logs.retain(|tx_log| tx_log.creation_timestamp > earliest_possible); + // Sort logs by their creation time in order to find the most recent + transaction_logs.sort_by(|a, b| b.creation_timestamp.cmp(&a.creation_timestamp)); + let initial_timestamp = transaction_logs.first() + .map_or(SystemTime::now(), |l| std::cmp::max(SystemTime::now(), l.creation_timestamp)); + let mut logs = self.logs.write(); + for log in transaction_logs { + logs.insert(log.tx_hash, log); + } + Ok(initial_timestamp) + } + + fn flush_logs(&self) -> Result<(), Error> { + let logs = self.logs.read(); + self.logs_serializer.flush_logs(&logs) + } +} + +// Flush all logs on drop +impl Drop for Logging { + fn drop(&mut self) { + if let Err(err) = self.flush_logs() { + warn!(target: "privatetx", "Cannot write logs: {:?}", err); + } + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use error::Error; + use ethereum_types::H256; + use std::collections::{HashMap, BTreeMap}; + use std::sync::Arc; + use std::time::{SystemTime, Duration}; + use types::transaction::Transaction; + use parking_lot::RwLock; + use super::{TransactionLog, Logging, PrivateTxStatus, LogsSerializer, ValidatorLog}; + + #[cfg(not(time_checked_add))] + use time_utils::CheckedSystemTime; + + struct StringLogSerializer { + string_log: RwLock, + } + + impl StringLogSerializer { + fn new(source: String) -> Self { + StringLogSerializer { + string_log: RwLock::new(source), + } + } + + fn log(&self) -> String { + let log = self.string_log.read(); + log.clone() + } + } + + impl LogsSerializer for StringLogSerializer { + fn read_logs(&self) -> Result, Error> { + let source = self.string_log.read(); + if source.is_empty() { + return Ok(Vec::new()) + } + let logs = serde_json::from_str(&source).unwrap(); + Ok(logs) + } + + fn flush_logs(&self, logs: &HashMap) -> Result<(), Error> { + // Sort logs in order to have the same order + let sorted_logs: BTreeMap<&H256, &TransactionLog> = logs.iter().collect(); + *self.string_log.write() = serde_json::to_string(&sorted_logs.values().collect::>())?; + Ok(()) + } + } + + #[test] + fn private_log_format() { + let s = r#"{ + "tx_hash":"0x64f648ca7ae7f4138014f860ae56164d8d5732969b1cea54d8be9d144d8aa6f6", + "status":"Deployed", + "creation_timestamp":{"secs_since_epoch":1557220355,"nanos_since_epoch":196382053}, + "validators":[{ + "account":"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", + "validation_timestamp":{"secs_since_epoch":1557220355,"nanos_since_epoch":196382053} + }], + "deployment_timestamp":{"secs_since_epoch":1557220355,"nanos_since_epoch":196382053}, + "public_tx_hash":"0x69b9c691ede7993effbcc88911c309af1c82be67b04b3882dd446b808ae146da" + }"#; + + let _deserialized: TransactionLog = serde_json::from_str(s).unwrap(); + } + + #[test] + fn private_log_status() { + let logger = Logging::new(Arc::new(StringLogSerializer::new("".into()))); + let private_tx = Transaction::default(); + let hash = private_tx.hash(None); + logger.private_tx_created(&hash, &vec!["0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]); + logger.signature_added(&hash, &"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()); + logger.tx_deployed(&hash, &hash); + let tx_log = logger.tx_log(&hash).unwrap(); + assert_eq!(tx_log.status, PrivateTxStatus::Deployed); + } + + #[test] + fn serialization() { + let current_timestamp = SystemTime::now(); + let initial_validator_log = ValidatorLog { + account: "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into(), + validation_timestamp: Some(current_timestamp.checked_add(Duration::from_secs(1)).unwrap()), + }; + let initial_log = TransactionLog { + tx_hash: "0x64f648ca7ae7f4138014f860ae56164d8d5732969b1cea54d8be9d144d8aa6f6".into(), + status: PrivateTxStatus::Deployed, + creation_timestamp: current_timestamp, + validators: vec![initial_validator_log], + deployment_timestamp: Some(current_timestamp.checked_add(Duration::from_secs(2)).unwrap()), + public_tx_hash: Some("0x69b9c691ede7993effbcc88911c309af1c82be67b04b3882dd446b808ae146da".into()), + }; + let serializer = Arc::new(StringLogSerializer::new(serde_json::to_string(&vec![initial_log.clone()]).unwrap())); + let logger = Logging::new(serializer.clone()); + let hash: H256 = "0x63c715e88f7291e66069302f6fcbb4f28a19ef5d7cbd1832d0c01e221c0061c6".into(); + logger.private_tx_created(&hash, &vec!["0x7ffbe3512782069be388f41be4d8eb350672d3a5".into()]); + logger.signature_added(&hash, &"0x7ffbe3512782069be388f41be4d8eb350672d3a5".into()); + logger.tx_deployed(&hash, &"0xde2209a8635b9cab9eceb67928b217c70ab53f6498e5144492ec01e6f43547d7".into()); + drop(logger); + let added_validator_log = ValidatorLog { + account: "0x7ffbe3512782069be388f41be4d8eb350672d3a5".into(), + validation_timestamp: Some(current_timestamp.checked_add(Duration::from_secs(7)).unwrap()), + }; + let added_log = TransactionLog { + tx_hash: "0x63c715e88f7291e66069302f6fcbb4f28a19ef5d7cbd1832d0c01e221c0061c6".into(), + status: PrivateTxStatus::Deployed, + creation_timestamp: current_timestamp.checked_add(Duration::from_secs(6)).unwrap(), + validators: vec![added_validator_log], + deployment_timestamp: Some(current_timestamp.checked_add(Duration::from_secs(8)).unwrap()), + public_tx_hash: Some("0xde2209a8635b9cab9eceb67928b217c70ab53f6498e5144492ec01e6f43547d7".into()), + }; + let should_be_final = vec![added_log, initial_log]; + let deserialized_logs: Vec = serde_json::from_str(&serializer.log()).unwrap(); + assert_eq!(deserialized_logs, should_be_final); + } +} \ No newline at end of file diff --git a/ethcore/private-tx/src/private_transactions.rs b/ethcore/private-tx/src/private_transactions.rs index d0456657b06..dd263ee1868 100644 --- a/ethcore/private-tx/src/private_transactions.rs +++ b/ethcore/private-tx/src/private_transactions.rs @@ -224,7 +224,7 @@ impl SigningStore { &mut self, private_hash: H256, transaction: SignedTransaction, - validators: Vec
, + validators: &Vec
, state: Bytes, contract_nonce: U256, ) -> Result<(), Error> { diff --git a/ethcore/private-tx/tests/private_contract.rs b/ethcore/private-tx/tests/private_contract.rs index 6365b10eecd..5d760fe0706 100644 --- a/ethcore/private-tx/tests/private_contract.rs +++ b/ethcore/private-tx/tests/private_contract.rs @@ -59,6 +59,7 @@ fn private_contract() { let config = ProviderConfig{ validator_accounts: vec![key3.address(), key4.address()], signer_account: None, + logs_path: None, }; let io = ethcore_io::IoChannel::disconnected(); @@ -193,6 +194,7 @@ fn call_other_private_contract() { let config = ProviderConfig{ validator_accounts: vec![key3.address(), key4.address()], signer_account: None, + logs_path: None, }; let io = ethcore_io::IoChannel::disconnected(); diff --git a/ethcore/sync/src/tests/private.rs b/ethcore/sync/src/tests/private.rs index 24de14d936d..b56f0ecc218 100644 --- a/ethcore/sync/src/tests/private.rs +++ b/ethcore/sync/src/tests/private.rs @@ -69,11 +69,13 @@ fn send_private_transaction() { let validator_config = ProviderConfig{ validator_accounts: vec![s1.address()], signer_account: None, + logs_path: None, }; let signer_config = ProviderConfig{ validator_accounts: Vec::new(), signer_account: Some(s0.address()), + logs_path: None, }; let private_keys = Arc::new(StoringKeyProvider::default()); diff --git a/parity/configuration.rs b/parity/configuration.rs index 198a58c3f99..216a3a1cadf 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -913,9 +913,11 @@ impl Configuration { } fn private_provider_config(&self) -> Result<(ProviderConfig, EncryptorConfig, bool), String> { + let dirs = self.directories(); let provider_conf = ProviderConfig { validator_accounts: to_addresses(&self.args.arg_private_validators)?, signer_account: self.args.arg_private_signer.clone().and_then(|account| to_address(Some(account)).ok()), + logs_path: Some(dirs.base), }; let encryptor_conf = EncryptorConfig { @@ -1453,7 +1455,11 @@ mod tests { net_settings: Default::default(), ipfs_conf: Default::default(), secretstore_conf: Default::default(), - private_provider_conf: Default::default(), + private_provider_conf: ProviderConfig { + validator_accounts: Default::default(), + signer_account: Default::default(), + logs_path: Some(Directories::default().base), + }, private_encryptor_conf: Default::default(), private_tx_enabled: false, name: "".into(), diff --git a/rpc/src/v1/impls/private.rs b/rpc/src/v1/impls/private.rs index c3be3f91506..abd15500608 100644 --- a/rpc/src/v1/impls/private.rs +++ b/rpc/src/v1/impls/private.rs @@ -26,7 +26,8 @@ use types::transaction::SignedTransaction; use jsonrpc_core::{Error}; use v1::types::{Bytes, PrivateTransactionReceipt, TransactionRequest, - BlockNumber, PrivateTransactionReceiptAndTransaction, CallRequest, block_number_to_id}; + BlockNumber, PrivateTransactionReceiptAndTransaction, CallRequest, + block_number_to_id, PrivateTransactionLog}; use v1::traits::Private; use v1::metadata::Metadata; use v1::helpers::{errors, fake_sign}; @@ -119,4 +120,11 @@ impl Private for PrivateClient { let key = client.contract_key_id(&contract_address).map_err(errors::private_message)?; Ok(key) } + + fn private_log(&self, tx_hash: H256) -> Result { + self.unwrap_manager()? + .private_log(tx_hash) + .map_err(errors::private_message) + .map(Into::into) + } } diff --git a/rpc/src/v1/traits/private.rs b/rpc/src/v1/traits/private.rs index 732e3914bda..887e7126780 100644 --- a/rpc/src/v1/traits/private.rs +++ b/rpc/src/v1/traits/private.rs @@ -21,7 +21,7 @@ use jsonrpc_core::Error; use jsonrpc_derive::rpc; use v1::types::{Bytes, PrivateTransactionReceipt, BlockNumber, - PrivateTransactionReceiptAndTransaction, CallRequest}; + PrivateTransactionReceiptAndTransaction, CallRequest, PrivateTransactionLog}; /// Private transaction management RPC interface. #[rpc] @@ -44,4 +44,8 @@ pub trait Private { /// Retrieve the id of the key associated with the contract #[rpc(name = "private_contractKey")] fn private_contract_key(&self, H160) -> Result; + + /// Retrieve log information about private transaction + #[rpc(name = "private_log")] + fn private_log(&self, H256) -> Result; } diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index a41f49fab18..3518aea56ee 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -32,6 +32,8 @@ mod histogram; mod index; mod log; mod node_kind; +mod private_receipt; +mod private_log; mod provenance; mod receipt; mod rpc_settings; @@ -43,7 +45,6 @@ mod transaction; mod transaction_request; mod transaction_condition; mod work; -mod private_receipt; mod eip191; pub mod pubsub; @@ -65,6 +66,8 @@ pub use self::histogram::Histogram; pub use self::index::Index; pub use self::log::Log; pub use self::node_kind::{NodeKind, Availability, Capability}; +pub use self::private_receipt::{PrivateTransactionReceipt, PrivateTransactionReceiptAndTransaction}; +pub use self::private_log::PrivateTransactionLog; pub use self::provenance::Origin; pub use self::receipt::Receipt; pub use self::rpc_settings::RpcSettings; @@ -79,7 +82,6 @@ pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionSta pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; pub use self::work::Work; -pub use self::private_receipt::{PrivateTransactionReceipt, PrivateTransactionReceiptAndTransaction}; // TODO [ToDr] Refactor to a proper type Vec of enums? /// Expected tracing type. diff --git a/rpc/src/v1/types/private_log.rs b/rpc/src/v1/types/private_log.rs new file mode 100644 index 00000000000..2c90bcfa905 --- /dev/null +++ b/rpc/src/v1/types/private_log.rs @@ -0,0 +1,93 @@ +// Copyright 2015-2018 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::time::SystemTime; +use ethereum_types::{H160, H256}; +use ethcore_private_tx::{TransactionLog as EthTransactionLog, ValidatorLog as EthValidatorLog, PrivateTxStatus as EthStatus}; + +/// Current status of the private transaction +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum Status { + /// Private tx was created but no validation received yet + Created, + /// Several validators (but not all) validated the transaction + Validating, + /// All validators validated the private tx + /// Corresponding public tx was created and added into the pool + Deployed, +} + +impl From for Status { + fn from(c: EthStatus) -> Self { + match c { + EthStatus::Created => Status::Created, + EthStatus::Validating => Status::Validating, + EthStatus::Deployed => Status::Deployed, + } + } +} + +/// Information about private tx validation +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ValidatorLog { + /// Account of the validator + pub account: H160, + /// Validation timestamp, None, if the transaction is not validated yet + pub validation_timestamp: Option, +} + +impl From for ValidatorLog { + fn from(r: EthValidatorLog) -> Self { + ValidatorLog { + account: r.account, + validation_timestamp: r.validation_timestamp.map(|t| t.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs()), + } + } +} + +/// Information about the private transaction +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PrivateTransactionLog { + /// Original signed transaction hash (used as a source for private tx) + pub tx_hash: H256, + /// Current status of the private transaction + pub status: Status, + /// Creation timestamp + pub creation_timestamp: u64, + /// List of validations + pub validators: Vec, + /// Timestamp of the resulting public tx deployment + pub deployment_timestamp: Option, + /// Hash of the resulting public tx + pub public_tx_hash: Option, +} + +impl From for PrivateTransactionLog { + fn from(r: EthTransactionLog) -> Self { + PrivateTransactionLog { + tx_hash: r.tx_hash, + status: r.status.into(), + creation_timestamp: r.creation_timestamp.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(), + validators: r.validators.into_iter().map(Into::into).collect(), + deployment_timestamp: r.deployment_timestamp.map(|t| t.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs()), + public_tx_hash: r.public_tx_hash, + } + } +} +