Trivial journal for private transactions (#10056)
* Journal for private txs added * Tests after adding logging to private tx fixed * Logs getter and tests added * Time and amount limit for logs added * RPC method for log retrieving added * Correct path name and time validation implemented * References for parameters added, redundant cloning reworked * References for parameters added, redundant cloning reworked * Work with json moved to the separate struct * Serialization test added * Fixed build after the merge with head * Documentation for methods fixed, redundant field removed * Fixed error usages * Timestamp trait implemented for std struct * Commented code removed * Remove timestamp source, rework serialization test * u64 replaced with SystemTime * Path made mandatory for logging * Source of monotonic time added * into_system_time method renamed * Initialize time source by max from current system time and max creation time from already saved logs * Redundant conversions removed, code a little bit reworked according to review comments * One more redundant conversion removed, rpc call simplified
This commit is contained in:
parent
87699f8de0
commit
5a581c1c90
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1050,6 +1050,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)",
|
||||
|
@ -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"
|
||||
|
@ -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<<VerifiedPrivateTransaction as VerifiedTransaction>::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<TxPoolError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerdeError> for Error {
|
||||
fn from(err: SerdeError) -> Self {
|
||||
Error::Json(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EthcoreError> for Error {
|
||||
fn from(err: EthcoreError) -> Self {
|
||||
Error::Ethcore(err).into()
|
||||
|
@ -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<Address>,
|
||||
/// Account used for signing public transactions created from private transactions
|
||||
pub signer_account: Option<Address>,
|
||||
/// Path to private tx logs
|
||||
pub logs_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -177,6 +188,7 @@ pub struct Provider {
|
||||
accounts: Arc<Signer>,
|
||||
channel: IoChannel<ClientIoMessage>,
|
||||
keys_provider: Arc<KeyProvider>,
|
||||
logging: Option<Logging>,
|
||||
}
|
||||
|
||||
#[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<Signature> = 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<bool, Error> {
|
||||
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<TransactionLog, Error> {
|
||||
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<Vec<Address>, Error> {
|
||||
let (data, decoder) = private_contract::functions::get_validators::call();
|
||||
|
408
ethcore/private-tx/src/log.rs
Normal file
408
ethcore/private-tx/src/log.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<SystemTime>,
|
||||
}
|
||||
|
||||
#[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<ValidatorLog>,
|
||||
/// Timestamp of the resulting public tx deployment
|
||||
pub deployment_timestamp: Option<SystemTime>,
|
||||
/// Hash of the resulting public tx
|
||||
pub public_tx_hash: Option<H256>,
|
||||
}
|
||||
|
||||
#[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<Vec<TransactionLog>, Error>;
|
||||
|
||||
/// Write all logs to the source
|
||||
fn flush_logs(&self, logs: &HashMap<H256, TransactionLog>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Logs serializer to the json file
|
||||
pub struct FileLogsSerializer {
|
||||
logs_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl FileLogsSerializer {
|
||||
pub fn with_path<P: Into<PathBuf>>(logs_dir: P) -> Self {
|
||||
FileLogsSerializer {
|
||||
logs_dir: logs_dir.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file(&self, to_create: bool) -> Result<File, Error> {
|
||||
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<Vec<TransactionLog>, 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<H256, TransactionLog>) -> 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<HashMap<H256, TransactionLog>>,
|
||||
logs_serializer: Arc<LogsSerializer>,
|
||||
mono_time: MonoTime,
|
||||
}
|
||||
|
||||
impl Logging {
|
||||
/// Creates the logging object
|
||||
pub fn new(logs_serializer: Arc<LogsSerializer>) -> 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<TransactionLog> {
|
||||
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<SystemTime, Error> {
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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<Vec<TransactionLog>, 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<H256, TransactionLog>) -> 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::<Vec<&&TransactionLog>>())?;
|
||||
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<TransactionLog> = serde_json::from_str(&serializer.log()).unwrap();
|
||||
assert_eq!(deserialized_logs, should_be_final);
|
||||
}
|
||||
}
|
@ -224,7 +224,7 @@ impl SigningStore {
|
||||
&mut self,
|
||||
private_hash: H256,
|
||||
transaction: SignedTransaction,
|
||||
validators: Vec<Address>,
|
||||
validators: &Vec<Address>,
|
||||
state: Bytes,
|
||||
contract_nonce: U256,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -914,9 +914,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 {
|
||||
@ -1458,7 +1460,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(),
|
||||
|
@ -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<PrivateTransactionLog, Error> {
|
||||
self.unwrap_manager()?
|
||||
.private_log(tx_hash)
|
||||
.map_err(errors::private_message)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -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<H256, Error>;
|
||||
|
||||
/// Retrieve log information about private transaction
|
||||
#[rpc(name = "private_log")]
|
||||
fn private_log(&self, H256) -> Result<PrivateTransactionLog, Error>;
|
||||
}
|
||||
|
@ -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.
|
||||
|
93
rpc/src/v1/types/private_log.rs
Normal file
93
rpc/src/v1/types/private_log.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<EthStatus> 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<u64>,
|
||||
}
|
||||
|
||||
impl From<EthValidatorLog> 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<ValidatorLog>,
|
||||
/// Timestamp of the resulting public tx deployment
|
||||
pub deployment_timestamp: Option<u64>,
|
||||
/// Hash of the resulting public tx
|
||||
pub public_tx_hash: Option<H256>,
|
||||
}
|
||||
|
||||
impl From<EthTransactionLog> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user