From 8dc7fcbe07cf20de00738770d73927f7a0b0a7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 15 Nov 2016 11:20:54 +0100 Subject: [PATCH 01/21] Don't clear propagated transactions --- sync/src/chain.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2f810e754..65fca4069 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1850,7 +1850,7 @@ impl ChainSync { /// propagates new transactions to all peers pub fn propagate_new_transactions(&mut self, io: &mut SyncIo) -> usize { - // Early out of nobody to send to. + // Early out if nobody to send to. if self.peers.is_empty() { return 0; } @@ -1948,9 +1948,6 @@ impl ChainSync { trace!(target: "sync", "Bad blocks in the queue, restarting"); self.restart(io); } - for peer_info in self.peers.values_mut() { - peer_info.last_sent_transactions.clear(); - } } } From 4febd0eb934f09267d5d89f040a39d160d997804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 10:45:55 +0100 Subject: [PATCH 02/21] Maintaining the statistics for propagation of pending transactions --- ethcore/src/client/client.rs | 4 +- ethcore/src/miner/mod.rs | 12 ++-- sync/src/chain.rs | 86 ++++++++++++++++---------- sync/src/lib.rs | 1 + sync/src/transactions_stats.rs | 109 +++++++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 40 deletions(-) create mode 100644 sync/src/transactions_stats.rs diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ec59e01cf..151d7e10e 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -262,7 +262,7 @@ impl Client { } } - /// Register an action to be done if a mode change happens. + /// Register an action to be done if a mode change happens. pub fn on_mode_change(&self, f: F) where F: 'static + FnMut(&Mode) + Send { *self.on_mode_change.lock() = Some(Box::new(f)); } @@ -890,7 +890,7 @@ impl BlockChainClient for Client { trace!(target: "mode", "Making callback..."); f(&*mode) }, - _ => {} + _ => {} } } match new_mode { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index da93dc0b7..2eb09cdf1 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -41,16 +41,16 @@ //! } //! ``` -mod miner; -mod external; -mod transaction_queue; mod banning_queue; -mod work_notify; +mod external; +mod miner; mod price_info; +mod transaction_queue; +mod work_notify; -pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; +pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; +pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; pub use client::TransactionImportResult; use std::collections::BTreeMap; diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 65fca4069..6f2605b27 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -102,6 +102,7 @@ use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as Do use snapshot::{Snapshot, ChunkType}; use rand::{thread_rng, Rng}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; +use transactions_stats::TransactionsStats; known_heap_size!(0, PeerInfo); @@ -257,7 +258,7 @@ enum ForkConfirmation { Unconfirmed, /// Peers chain is too short to confirm the fork. TooShort, - /// Fork is confurmed. + /// Fork is confirmed. Confirmed, } @@ -338,6 +339,8 @@ pub struct ChainSync { handshaking_peers: HashMap, /// Sync start timestamp. Measured when first peer is connected sync_start_time: Option, + /// Transactions propagation statistics + transactions_stats: TransactionsStats, } type RlpResponseResult = Result, PacketDecodeError>; @@ -360,6 +363,7 @@ impl ChainSync { fork_block: config.fork_block, snapshot: Snapshot::new(), sync_start_time: None, + transactions_stats: TransactionsStats::default(), }; sync.update_targets(chain); sync @@ -1867,38 +1871,52 @@ impl ChainSync { packet.out() }; + // Clear old transactions from stats + self.transactions_stats.retain(&all_transactions_hashes); + // sqrt(x)/x scaled to max u32 let fraction = (self.peers.len() as f64).powf(-0.5).mul(u32::max_value() as f64).round() as u32; let small = self.peers.len() < MIN_PEERS_PROPAGATION; - let lucky_peers = self.peers.iter_mut() - .filter(|_| small || ::rand::random::() < fraction) - .take(MAX_PEERS_PROPAGATION) - .filter_map(|(peer_id, mut peer_info)| { - // Send all transactions - if peer_info.last_sent_transactions.is_empty() { - peer_info.last_sent_transactions = all_transactions_hashes.clone(); - return Some((*peer_id, all_transactions_rlp.clone())); - } - - // Get hashes of all transactions to send to this peer - let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::>(); - if to_send.is_empty() { - return None; - } - - // Construct RLP - let mut packet = RlpStream::new_list(to_send.len()); - for tx in &transactions { - if to_send.contains(&tx.hash()) { - packet.append(tx); + let lucky_peers = { + let stats = &mut self.transactions_stats; + self.peers.iter_mut() + .filter(|_| small || ::rand::random::() < fraction) + .take(MAX_PEERS_PROPAGATION) + .filter_map(|(peer_id, mut peer_info)| { + // Send all transactions + if peer_info.last_sent_transactions.is_empty() { + // update stats + for hash in &all_transactions_hashes { + let id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(*hash, id); + } + peer_info.last_sent_transactions = all_transactions_hashes.clone(); + return Some((*peer_id, all_transactions_rlp.clone())); } - } - peer_info.last_sent_transactions = all_transactions_hashes.clone(); - Some((*peer_id, packet.out())) - }) - .collect::>(); + // Get hashes of all transactions to send to this peer + let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::>(); + if to_send.is_empty() { + return None; + } + + // Construct RLP + let mut packet = RlpStream::new_list(to_send.len()); + for tx in &transactions { + if to_send.contains(&tx.hash()) { + packet.append(tx); + // update stats + let peer_id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(tx.hash(), peer_id); + } + } + + peer_info.last_sent_transactions = all_transactions_hashes.clone(); + Some((*peer_id, packet.out())) + }) + .collect::>() + }; // Send RLPs let sent = lucky_peers.len(); @@ -2273,18 +2291,23 @@ mod tests { let peer_count = sync.propagate_new_transactions(&mut io); // Try to propagate same transactions for the second time let peer_count2 = sync.propagate_new_transactions(&mut io); + // Even after new block transactions should not be propagated twice + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + // Try to propagate same transactions for the third time + let peer_count3 = sync.propagate_new_transactions(&mut io); // 1 message should be send assert_eq!(1, io.queue.len()); // 1 peer should be updated but only once assert_eq!(1, peer_count); assert_eq!(0, peer_count2); + assert_eq!(0, peer_count3); // TRANSACTIONS_PACKET assert_eq!(0x02, io.queue[0].packet_id); } #[test] - fn propagates_transactions_again_after_new_block() { + fn propagates_new_transactions_after_new_block() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); @@ -2293,15 +2316,14 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); let peer_count = sync.propagate_new_transactions(&mut io); + io.chain.insert_transaction_to_queue(); + // New block import should trigger propagation. sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); - // Try to propagate same transactions for the second time - let peer_count2 = sync.propagate_new_transactions(&mut io); // 2 message should be send assert_eq!(2, io.queue.len()); - // 1 peer should be updated twice + // 1 peer should receive the message assert_eq!(1, peer_count); - assert_eq!(1, peer_count2); // TRANSACTIONS_PACKET assert_eq!(0x02, io.queue[0].packet_id); assert_eq!(0x02, io.queue[1].packet_id); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 532c05711..25350d410 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -51,6 +51,7 @@ mod blocks; mod block_sync; mod sync_io; mod snapshot; +mod transactions_stats; #[cfg(test)] mod tests; diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs new file mode 100644 index 000000000..ed0a2aedd --- /dev/null +++ b/sync/src/transactions_stats.rs @@ -0,0 +1,109 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Transaction Stats + +use std::collections::{HashSet, HashMap}; +use util::{H256, H512}; +use util::hash::H256FastMap; + +type NodeId = H512; +type BlockNumber = u64; + +#[derive(Debug, Default, PartialEq)] +pub struct Stats { + first_seen: BlockNumber, + propagated_to: HashMap, +} + +#[derive(Debug, Default)] +pub struct TransactionsStats { + pending_transactions: H256FastMap, +} + +impl TransactionsStats { + /// Increases number of propagations to given `enodeid`. + pub fn propagated(&mut self, hash: H256, enode_id: Option) { + let enode_id = enode_id.unwrap_or_default(); + let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::default()); + let mut count = stats.propagated_to.entry(enode_id).or_insert(0); + *count = count.saturating_add(1); + } + + /// Returns propagation stats for given hash or `None` if hash is not known. + pub fn stats(&self, hash: &H256) -> Option<&Stats> { + self.pending_transactions.get(hash) + } + + /// Retains only transactions present in given `HashSet`. + pub fn retain(&mut self, hashes: &HashSet) { + let to_remove = self.pending_transactions.keys() + .filter(|hash| !hashes.contains(hash)) + .cloned() + .collect::>(); + + for hash in to_remove { + self.pending_transactions.remove(&hash); + } + } +} + +#[cfg(test)] +mod tests { + + use std::collections::{HashMap, HashSet}; + use super::{Stats, TransactionsStats}; + + #[test] + fn should_keep_track_of_propagations() { + // given + let mut stats = TransactionsStats::default(); + let hash = 5.into(); + let enodeid1 = 2.into(); + let enodeid2 = 5.into(); + + // when + stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid2)); + + // then + let stats = stats.stats(&hash); + assert_eq!(stats, Some(&Stats { + first_seen: 0, + propagated_to: hash_map![ + enodeid1 => 2, + enodeid2 => 1 + ] + })); + } + + #[test] + fn should_remove_hash_from_tracking() { + // given + let mut stats = TransactionsStats::default(); + let hash = 5.into(); + let enodeid1 = 5.into(); + stats.propagated(hash, Some(enodeid1)); + + // when + stats.retain(&HashSet::new()); + + // then + let stats = stats.stats(&hash); + assert_eq!(stats, None); + } +} From 78b5c743f65bdb8c8b57bcde7fd781bdfebe84ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 13:37:21 +0100 Subject: [PATCH 03/21] Stats RPC --- ipc/rpc/src/binary.rs | 3 +- rpc/src/v1/impls/parity.rs | 12 +++++- rpc/src/v1/tests/helpers/sync_provider.rs | 26 ++++++++++-- rpc/src/v1/tests/mocked/parity.rs | 12 ++++++ rpc/src/v1/traits/parity.rs | 6 ++- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/sync.rs | 46 ++++++++++++++++++-- sync/build.rs | 1 + sync/src/api.rs | 22 +++++++++- sync/src/chain.rs | 29 +++++++++++-- sync/src/lib.rs | 2 +- sync/src/transactions_stats.rs | 51 +++++++++++++++++------ 12 files changed, 180 insertions(+), 32 deletions(-) diff --git a/ipc/rpc/src/binary.rs b/ipc/rpc/src/binary.rs index 3908992d1..e974626d0 100644 --- a/ipc/rpc/src/binary.rs +++ b/ipc/rpc/src/binary.rs @@ -16,7 +16,7 @@ //! Binary representation of types -use util::{U256, U512, H256, H2048, Address}; +use util::{U256, U512, H256, H512, H2048, Address}; use std::mem; use std::collections::{VecDeque, BTreeMap}; use std::ops::Range; @@ -800,6 +800,7 @@ binary_fixed_size!(bool); binary_fixed_size!(U256); binary_fixed_size!(U512); binary_fixed_size!(H256); +binary_fixed_size!(H512); binary_fixed_size!(H2048); binary_fixed_size!(Address); binary_fixed_size!(BinHandshake); diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 1b8ee9695..a952d54ec 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -34,7 +34,7 @@ use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; use v1::traits::Parity; -use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram}; +use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::DEFAULT_MAC; @@ -259,6 +259,16 @@ impl Parity for ParityClient where Ok(take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::>()) } + fn pending_transactions_stats(&self) -> Result, Error> { + try!(self.active()); + + let stats = take_weak!(self.sync).transactions_stats(); + Ok(stats.into_iter() + .map(|(hash, stats)| (hash.into(), stats.into())) + .collect() + ) + } + fn signer_port(&self) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index e0f811fc0..24be33417 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -16,8 +16,9 @@ //! Test implementation of SyncProvider. -use util::{RwLock}; -use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo}; +use std::collections::BTreeMap; +use util::{H256, RwLock}; +use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo, TransactionStats}; /// TestSyncProvider config. pub struct Config { @@ -74,7 +75,7 @@ impl SyncProvider for TestSyncProvider { PeerInfo { id: Some("node1".to_owned()), client_version: "Parity/1".to_owned(), - capabilities: vec!["eth/62".to_owned(), "eth/63".to_owned()], + capabilities: vec!["eth/62".to_owned(), "eth/63".to_owned()], remote_address: "127.0.0.1:7777".to_owned(), local_address: "127.0.0.1:8888".to_owned(), eth_version: 62, @@ -84,7 +85,7 @@ impl SyncProvider for TestSyncProvider { PeerInfo { id: None, client_version: "Parity/2".to_owned(), - capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()], + capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()], remote_address: "Handshake".to_owned(), local_address: "127.0.0.1:3333".to_owned(), eth_version: 64, @@ -97,5 +98,22 @@ impl SyncProvider for TestSyncProvider { fn enode(&self) -> Option { None } + + fn transactions_stats(&self) -> BTreeMap { + map![ + 1.into() => TransactionStats { + first_seen: 10, + propagated_to: map![ + 128.into() => 16 + ] + }, + 5.into() => TransactionStats { + first_seen: 16, + propagated_to: map![ + 16.into() => 1 + ] + } + ] + } } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index b5c8187c7..8c06d4c7d 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -355,3 +355,15 @@ fn rpc_parity_next_nonce() { assert_eq!(io1.handle_request_sync(&request), Some(response1.to_owned())); assert_eq!(io2.handle_request_sync(&request), Some(response2.to_owned())); } + +#[test] +fn rpc_parity_transactions_stats() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_pendingTransactionsStats", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x0000000000000000000000000000000000000000000000000000000000000001":{"firstSeen":10,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080":16}},"0x0000000000000000000000000000000000000000000000000000000000000005":{"firstSeen":16,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010":1}}},"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index f8c219a89..946da8149 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -19,7 +19,7 @@ use jsonrpc_core::Error; use std::collections::BTreeMap; use v1::helpers::auto_args::Wrap; -use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram}; +use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; build_rpc_trait! { /// Parity-specific rpc interface. @@ -115,6 +115,10 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactions")] fn pending_transactions(&self) -> Result, Error>; + /// Returns propagation statistics on transactions pending in the queue. + #[rpc(name = "parity_pendingTransactionsStats")] + fn pending_transactions_stats(&self) -> Result, Error>; + /// Returns current Trusted Signer port or an error if signer is disabled. #[rpc(name = "parity_signerPort")] fn signer_port(&self) -> Result; diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index b1e4ae2c9..29e50aae5 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -43,7 +43,7 @@ pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; pub use self::log::Log; -pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo}; +pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo, TransactionStats}; pub use self::transaction::Transaction; pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; diff --git a/rpc/src/v1/types/sync.rs b/rpc/src/v1/types/sync.rs index a0f61e799..6f8938be9 100644 --- a/rpc/src/v1/types/sync.rs +++ b/rpc/src/v1/types/sync.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethsync::PeerInfo as SyncPeerInfo; +use std::collections::BTreeMap; +use ethsync::{PeerInfo as SyncPeerInfo, TransactionStats as SyncTransactionStats}; use serde::{Serialize, Serializer}; -use v1::types::U256; +use v1::types::{U256, H512}; /// Sync info #[derive(Default, Debug, Serialize, PartialEq)] @@ -117,8 +118,19 @@ impl Serialize for SyncStatus { } } +/// Propagation statistics for pending transaction. +#[derive(Default, Debug, Serialize)] +pub struct TransactionStats { + /// Block no this transaction was first seen. + #[serde(rename="firstSeen")] + pub first_seen: u64, + /// Peers this transaction was propagated to with count. + #[serde(rename="propagatedTo")] + pub propagated_to: BTreeMap, +} + impl From for PeerInfo { - fn from(p: SyncPeerInfo) -> PeerInfo { + fn from(p: SyncPeerInfo) -> Self { PeerInfo { id: p.id, name: p.client_version, @@ -138,10 +150,23 @@ impl From for PeerInfo { } } +impl From for TransactionStats { + fn from(s: SyncTransactionStats) -> Self { + TransactionStats { + first_seen: s.first_seen, + propagated_to: s.propagated_to + .into_iter() + .map(|(id, count)| (id.into(), count)) + .collect() + } + } +} + #[cfg(test)] mod tests { use serde_json; - use super::{SyncInfo, SyncStatus, Peers}; + use std::collections::BTreeMap; + use super::{SyncInfo, SyncStatus, Peers, TransactionStats}; #[test] fn test_serialize_sync_info() { @@ -176,4 +201,17 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!(serialized, r#"{"startingBlock":"0x0","currentBlock":"0x0","highestBlock":"0x0","warpChunksAmount":null,"warpChunksProcessed":null,"blockGap":["0x1","0x5"]}"#) } + + #[test] + fn test_serialize_transaction_stats() { + let stats = TransactionStats { + first_seen: 100, + propagated_to: map![ + 10.into() => 50 + ] + }; + + let serialized = serde_json::to_string(&stats).unwrap(); + assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50}}"#) + } } diff --git a/sync/build.rs b/sync/build.rs index c465d5e34..db881e328 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -18,4 +18,5 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/transactions_stats.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/sync/src/api.rs b/sync/src/api.rs index d9dbbd263..3191483e4 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -15,13 +15,13 @@ // along with Parity. If not, see . use std::sync::Arc; -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap}; use std::io; use util::Bytes; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId, ProtocolId, NetworkConfiguration as BasicNetworkConfiguration, NonReservedPeerMode, NetworkError, AllowIP as NetworkAllowIP}; -use util::{U256, H256}; +use util::{U256, H256, H512}; use io::{TimerToken}; use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::snapshot::SnapshotService; @@ -76,6 +76,16 @@ pub trait SyncProvider: Send + Sync { /// Get the enode if available. fn enode(&self) -> Option; + + /// Returns propagation count for pending transactions. + fn transactions_stats(&self) -> BTreeMap; +} + +/// Transaction stats +#[derive(Debug, Binary)] +pub struct TransactionStats { + pub first_seen: u64, + pub propagated_to: BTreeMap, } /// Peer connection information @@ -150,6 +160,14 @@ impl SyncProvider for EthSync { fn enode(&self) -> Option { self.network.external_url() } + + fn transactions_stats(&self) -> BTreeMap { + let sync = self.handler.sync.read(); + sync.transactions_stats() + .iter() + .map(|(hash, stats)| (*hash, stats.into())) + .collect() + } } struct SyncProtocolHandler { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 6f2605b27..d255949b9 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -102,7 +102,7 @@ use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as Do use snapshot::{Snapshot, ChunkType}; use rand::{thread_rng, Rng}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; -use transactions_stats::TransactionsStats; +use transactions_stats::{TransactionsStats, Stats as TransactionStats}; known_heap_size!(0, PeerInfo); @@ -412,6 +412,11 @@ impl ChainSync { .collect() } + /// Returns transactions propagation statistics + pub fn transactions_stats(&self) -> &H256FastMap { + self.transactions_stats.stats() + } + /// Abort all sync activity pub fn abort(&mut self, io: &mut SyncIo) { self.reset_and_continue(io); @@ -1877,6 +1882,7 @@ impl ChainSync { // sqrt(x)/x scaled to max u32 let fraction = (self.peers.len() as f64).powf(-0.5).mul(u32::max_value() as f64).round() as u32; let small = self.peers.len() < MIN_PEERS_PROPAGATION; + let block_number = io.chain().chain_info().best_block_number; let lucky_peers = { let stats = &mut self.transactions_stats; @@ -1889,7 +1895,7 @@ impl ChainSync { // update stats for hash in &all_transactions_hashes { let id = io.peer_session_info(*peer_id).and_then(|info| info.id); - stats.propagated(*hash, id); + stats.propagated(*hash, id, block_number); } peer_info.last_sent_transactions = all_transactions_hashes.clone(); return Some((*peer_id, all_transactions_rlp.clone())); @@ -1907,8 +1913,8 @@ impl ChainSync { if to_send.contains(&tx.hash()) { packet.append(tx); // update stats - let peer_id = io.peer_session_info(*peer_id).and_then(|info| info.id); - stats.propagated(tx.hash(), peer_id); + let id = io.peer_session_info(*peer_id).and_then(|info| info.id); + stats.propagated(tx.hash(), id, block_number); } } @@ -2362,6 +2368,21 @@ mod tests { assert_eq!(0x02, io.queue[1].packet_id); } + #[test] + fn should_maintain_transations_propagation_stats() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + client.insert_transaction_to_queue(); + let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); + let mut queue = VecDeque::new(); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + sync.propagate_new_transactions(&mut io); + + let stats = sync.transactions_stats(); + assert_eq!(stats.len(), 1, "Should maintain stats for single transaction.") + } + #[test] fn handles_peer_new_block_malformed() { let mut client = TestBlockChainClient::new(); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 25350d410..2061e4e3a 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -62,7 +62,7 @@ mod api { } pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNetwork, SyncConfig, - ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP}; + ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP, TransactionStats}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs index ed0a2aedd..8c5eb6dda 100644 --- a/sync/src/transactions_stats.rs +++ b/sync/src/transactions_stats.rs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Transaction Stats - +use api::TransactionStats; use std::collections::{HashSet, HashMap}; use util::{H256, H512}; use util::hash::H256FastMap; @@ -23,12 +22,33 @@ use util::hash::H256FastMap; type NodeId = H512; type BlockNumber = u64; -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Stats { first_seen: BlockNumber, propagated_to: HashMap, } +impl Stats { + pub fn new(number: BlockNumber) -> Self { + Stats { + first_seen: number, + propagated_to: Default::default(), + } + } +} + +impl<'a> From<&'a Stats> for TransactionStats { + fn from(other: &'a Stats) -> Self { + TransactionStats { + first_seen: other.first_seen, + propagated_to: other.propagated_to + .iter() + .map(|(hash, size)| (*hash, *size)) + .collect(), + } + } +} + #[derive(Debug, Default)] pub struct TransactionsStats { pending_transactions: H256FastMap, @@ -36,18 +56,23 @@ pub struct TransactionsStats { impl TransactionsStats { /// Increases number of propagations to given `enodeid`. - pub fn propagated(&mut self, hash: H256, enode_id: Option) { + pub fn propagated(&mut self, hash: H256, enode_id: Option, current_block_num: BlockNumber) { let enode_id = enode_id.unwrap_or_default(); - let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::default()); + let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::new(current_block_num)); let mut count = stats.propagated_to.entry(enode_id).or_insert(0); *count = count.saturating_add(1); } /// Returns propagation stats for given hash or `None` if hash is not known. - pub fn stats(&self, hash: &H256) -> Option<&Stats> { + #[cfg(test)] + pub fn get(&self, hash: &H256) -> Option<&Stats> { self.pending_transactions.get(hash) } + pub fn stats(&self) -> &H256FastMap { + &self.pending_transactions + } + /// Retains only transactions present in given `HashSet`. pub fn retain(&mut self, hashes: &HashSet) { let to_remove = self.pending_transactions.keys() @@ -76,14 +101,14 @@ mod tests { let enodeid2 = 5.into(); // when - stats.propagated(hash, Some(enodeid1)); - stats.propagated(hash, Some(enodeid1)); - stats.propagated(hash, Some(enodeid2)); + stats.propagated(hash, Some(enodeid1), 5); + stats.propagated(hash, Some(enodeid1), 10); + stats.propagated(hash, Some(enodeid2), 15); // then - let stats = stats.stats(&hash); + let stats = stats.get(&hash); assert_eq!(stats, Some(&Stats { - first_seen: 0, + first_seen: 5, propagated_to: hash_map![ enodeid1 => 2, enodeid2 => 1 @@ -97,13 +122,13 @@ mod tests { let mut stats = TransactionsStats::default(); let hash = 5.into(); let enodeid1 = 5.into(); - stats.propagated(hash, Some(enodeid1)); + stats.propagated(hash, Some(enodeid1), 10); // when stats.retain(&HashSet::new()); // then - let stats = stats.stats(&hash); + let stats = stats.get(&hash); assert_eq!(stats, None); } } From 66e327dfcbad838a725b819a09c32d25c041567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 15:58:14 +0100 Subject: [PATCH 04/21] Keep track of local transactions --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/src/lib.rs | 1 + ethcore/src/miner/local_transactions.rs | 193 ++++++++++++++++++++++++ ethcore/src/miner/mod.rs | 1 + ethcore/src/miner/transaction_queue.rs | 132 +++++++++++++--- 6 files changed, 309 insertions(+), 20 deletions(-) create mode 100644 ethcore/src/miner/local_transactions.rs diff --git a/Cargo.lock b/Cargo.lock index a93d2d551..94e754997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,7 @@ dependencies = [ "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 667b40ace..48fce064a 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -27,6 +27,7 @@ time = "0.1" rand = "0.3" byteorder = "0.5" transient-hashmap = "0.1" +linked-hash-map = "0.3.0" evmjit = { path = "../evmjit", optional = true } clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index bf3e59171..ca343b1a7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -102,6 +102,7 @@ extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; extern crate transient_hashmap; +extern crate linked_hash_map; #[macro_use] extern crate log; diff --git a/ethcore/src/miner/local_transactions.rs b/ethcore/src/miner/local_transactions.rs new file mode 100644 index 000000000..c9c869c33 --- /dev/null +++ b/ethcore/src/miner/local_transactions.rs @@ -0,0 +1,193 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Local Transactions List. + +use linked_hash_map::LinkedHashMap; +use transaction::SignedTransaction; +use error::TransactionError; +use util::{U256, H256}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Status { + /// The transaction is currently in the transaction queue. + Pending, + /// The transaction is in future part of the queue. + Future, + /// Transaction is already mined. + Mined(SignedTransaction), + /// Transaction is dropped because of limit + Dropped(SignedTransaction), + /// Replaced because of higher gas price of another transaction. + Replaced(SignedTransaction, U256, H256), + /// Transaction was never accepted to the queue. + Rejected(SignedTransaction, TransactionError), + /// Transaction is invalid. + Invalid(SignedTransaction), +} + +impl Status { + fn is_current(&self) -> bool { + *self == Status::Pending || *self == Status::Future + } +} + +/// Keeps track of local transactions that are in the queue or were mined/dropped recently. +#[derive(Debug)] +pub struct LocalTransactionsList { + max_old: usize, + transactions: LinkedHashMap, +} + +impl Default for LocalTransactionsList { + fn default() -> Self { + Self::new(10) + } +} + +impl LocalTransactionsList { + pub fn new(max_old: usize) -> Self { + LocalTransactionsList { + max_old: max_old, + transactions: Default::default(), + } + } + + pub fn mark_pending(&mut self, hash: H256) { + self.clear_old(); + self.transactions.insert(hash, Status::Pending); + } + + pub fn mark_future(&mut self, hash: H256) { + self.transactions.insert(hash, Status::Future); + self.clear_old(); + } + + pub fn mark_rejected(&mut self, tx: SignedTransaction, err: TransactionError) { + self.transactions.insert(tx.hash(), Status::Rejected(tx, err)); + self.clear_old(); + } + + pub fn mark_replaced(&mut self, tx: SignedTransaction, gas_price: U256, hash: H256) { + self.transactions.insert(tx.hash(), Status::Replaced(tx, gas_price, hash)); + self.clear_old(); + } + + pub fn mark_invalid(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Invalid(tx)); + self.clear_old(); + } + + pub fn mark_dropped(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Dropped(tx)); + self.clear_old(); + } + + pub fn mark_mined(&mut self, tx: SignedTransaction) { + self.transactions.insert(tx.hash(), Status::Mined(tx)); + self.clear_old(); + } + + pub fn contains(&self, hash: &H256) -> bool { + self.transactions.contains_key(hash) + } + + pub fn all_transactions(&self) -> &LinkedHashMap { + &self.transactions + } + + fn clear_old(&mut self) { + let number_of_old = self.transactions + .values() + .filter(|status| !status.is_current()) + .count(); + + if self.max_old >= number_of_old { + return; + } + + let to_remove = self.transactions + .iter() + .filter(|&(_, status)| !status.is_current()) + .map(|(hash, _)| *hash) + .take(number_of_old - self.max_old) + .collect::>(); + + for hash in to_remove { + self.transactions.remove(&hash); + } + } +} + +#[cfg(test)] +mod tests { + use util::U256; + use ethkey::{Random, Generator}; + use transaction::{Action, Transaction, SignedTransaction}; + use super::{LocalTransactionsList, Status}; + + #[test] + fn should_add_transaction_as_pending() { + // given + let mut list = LocalTransactionsList::default(); + + // when + list.mark_pending(10.into()); + list.mark_future(20.into()); + + // then + assert!(list.contains(&10.into()), "Should contain the transaction."); + assert!(list.contains(&20.into()), "Should contain the transaction."); + let statuses = list.all_transactions().values().cloned().collect::>(); + assert_eq!(statuses, vec![Status::Pending, Status::Future]); + } + + #[test] + fn should_clear_old_transactions() { + // given + let mut list = LocalTransactionsList::new(1); + let tx1 = new_tx(10.into()); + let tx1_hash = tx1.hash(); + let tx2 = new_tx(50.into()); + let tx2_hash = tx2.hash(); + + list.mark_pending(10.into()); + list.mark_invalid(tx1); + list.mark_dropped(tx2); + assert!(list.contains(&tx2_hash)); + assert!(!list.contains(&tx1_hash)); + assert!(list.contains(&10.into())); + + // when + list.mark_future(15.into()); + + // then + assert!(list.contains(&10.into())); + assert!(list.contains(&15.into())); + } + + fn new_tx(nonce: U256) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: Action::Create, + value: U256::from(100), + data: Default::default(), + gas: U256::from(10), + gas_price: U256::from(1245), + nonce: nonce + }.sign(keypair.secret(), None) + } +} diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 2eb09cdf1..29d731e9e 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -43,6 +43,7 @@ mod banning_queue; mod external; +mod local_transactions; mod miner; mod price_info; mod transaction_queue; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index cc10bbe98..9f688adf5 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -91,6 +91,7 @@ use util::table::Table; use transaction::*; use error::{Error, TransactionError}; use client::TransactionImportResult; +use miner::local_transactions::LocalTransactionsList; /// Transaction origin #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -125,6 +126,12 @@ impl Ord for TransactionOrigin { } } +impl TransactionOrigin { + fn is_local(&self) -> bool { + *self == TransactionOrigin::Local + } +} + #[derive(Clone, Debug)] /// Light structure used to identify transaction and its order struct TransactionOrder { @@ -352,7 +359,7 @@ impl TransactionSet { /// /// It drops transactions from this set but also removes associated `VerifiedTransaction`. /// Returns addresses and lowest nonces of transactions removed because of limit. - fn enforce_limit(&mut self, by_hash: &mut HashMap) -> Option> { + fn enforce_limit(&mut self, by_hash: &mut HashMap, local: &mut LocalTransactionsList) -> Option> { let mut count = 0; let mut gas: U256 = 0.into(); let to_drop : Vec<(Address, U256)> = { @@ -379,9 +386,13 @@ impl TransactionSet { .expect("Transaction has just been found in `by_priority`; so it is in `by_address` also."); trace!(target: "txqueue", "Dropped out of limit transaction: {:?}", order.hash); - by_hash.remove(&order.hash) + let order = by_hash.remove(&order.hash) .expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed"); + if order.origin.is_local() { + local.mark_dropped(order.transaction); + } + let min = removed.get(&sender).map_or(nonce, |val| cmp::min(*val, nonce)); removed.insert(sender, min); removed @@ -488,6 +499,8 @@ pub struct TransactionQueue { by_hash: HashMap, /// Last nonce of transaction in current (to quickly check next expected transaction) last_nonces: HashMap, + /// List of local transactions and their statuses. + local_transactions: LocalTransactionsList, } impl Default for TransactionQueue { @@ -529,6 +542,7 @@ impl TransactionQueue { future: future, by_hash: HashMap::new(), last_nonces: HashMap::new(), + local_transactions: LocalTransactionsList::default(), } } @@ -537,8 +551,8 @@ impl TransactionQueue { self.current.set_limit(limit); self.future.set_limit(limit); // And ensure the limits - self.current.enforce_limit(&mut self.by_hash); - self.future.enforce_limit(&mut self.by_hash); + self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Returns current limit of transactions in the queue. @@ -578,7 +592,7 @@ impl TransactionQueue { pub fn set_total_gas_limit(&mut self, gas_limit: U256) { self.future.gas_limit = gas_limit; self.current.gas_limit = gas_limit; - self.future.enforce_limit(&mut self.by_hash); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Set the new limit for the amount of gas any individual transaction may have. @@ -609,6 +623,42 @@ impl TransactionQueue { F: Fn(&Address) -> AccountDetails, G: Fn(&SignedTransaction) -> U256, { + if origin == TransactionOrigin::Local { + let hash = tx.hash(); + let cloned_tx = tx.clone(); + + let result = self.add_internal(tx, origin, fetch_account, gas_estimator); + match result { + Ok(TransactionImportResult::Current) => { + self.local_transactions.mark_pending(hash); + }, + Ok(TransactionImportResult::Future) => { + self.local_transactions.mark_future(hash); + }, + Err(Error::Transaction(ref err)) => { + self.local_transactions.mark_rejected(cloned_tx, err.clone()); + }, + Err(_) => { + self.local_transactions.mark_invalid(cloned_tx); + }, + } + result + } else { + self.add_internal(tx, origin, fetch_account, gas_estimator) + } + } + + /// Adds signed transaction to the queue. + fn add_internal( + &mut self, + tx: SignedTransaction, + origin: TransactionOrigin, + fetch_account: &F, + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "txqueue", @@ -647,7 +697,6 @@ impl TransactionQueue { self.gas_limit, self.tx_gas_limit ); - return Err(Error::Transaction(TransactionError::GasLimitExceeded { limit: self.gas_limit, got: tx.gas, @@ -766,6 +815,11 @@ impl TransactionQueue { trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash()); + // Mark in locals + if self.local_transactions.contains(transaction_hash) { + self.local_transactions.mark_invalid(transaction.transaction.clone()); + } + // Remove from future let order = self.future.drop(&sender, &nonce); if order.is_some() { @@ -821,15 +875,21 @@ impl TransactionQueue { qed"); if k >= current_nonce { let order = order.update_height(k, current_nonce); + if order.origin.is_local() { + self.local_transactions.mark_future(order.hash); + } if let Some(old) = self.future.insert(*sender, k, order.clone()) { - Self::replace_orders(*sender, k, old, order, &mut self.future, &mut self.by_hash); + Self::replace_orders(*sender, k, old, order, &mut self.future, &mut self.by_hash, &mut self.local_transactions); } } else { trace!(target: "txqueue", "Removing old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce); - self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); + let tx = self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); + if tx.origin.is_local() { + self.local_transactions.mark_mined(tx.transaction); + } } } - self.future.enforce_limit(&mut self.by_hash); + self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); } /// Returns top transactions from the queue ordered by priority. @@ -897,8 +957,11 @@ impl TransactionQueue { self.future.by_gas_price.remove(&order.gas_price, &order.hash); // Put to current let order = order.update_height(current_nonce, first_nonce); + if order.origin.is_local() { + self.local_transactions.mark_pending(order.hash); + } if let Some(old) = self.current.insert(address, current_nonce, order.clone()) { - Self::replace_orders(address, current_nonce, old, order, &mut self.current, &mut self.by_hash); + Self::replace_orders(address, current_nonce, old, order, &mut self.current, &mut self.by_hash, &mut self.local_transactions); } update_last_nonce_to = Some(current_nonce); current_nonce = current_nonce + U256::one(); @@ -957,9 +1020,11 @@ impl TransactionQueue { if nonce > next_nonce { // We have a gap - put to future. // Insert transaction (or replace old one with lower gas price) - try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash))); + try!(check_too_cheap( + Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash, &mut self.local_transactions) + )); // Enforce limit in Future - let removed = self.future.enforce_limit(&mut self.by_hash); + let removed = self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); // Return an error if this transaction was not imported because of limit. try!(check_if_removed(&address, &nonce, removed)); @@ -973,13 +1038,15 @@ impl TransactionQueue { self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); // Replace transaction if any - try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash))); + try!(check_too_cheap( + Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash, &mut self.local_transactions) + )); // Keep track of highest nonce stored in current let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n)); self.last_nonces.insert(address, new_max); // Also enforce the limit - let removed = self.current.enforce_limit(&mut self.by_hash); + let removed = self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); // If some transaction were removed because of limit we need to update last_nonces also. self.update_last_nonces(&removed); // Trigger error if the transaction we are importing was removed. @@ -1010,7 +1077,14 @@ impl TransactionQueue { /// /// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher /// gas_price) - fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, min_gas_price: (U256, PrioritizationStrategy), set: &mut TransactionSet, by_hash: &mut HashMap) -> bool { + fn replace_transaction( + tx: VerifiedTransaction, + base_nonce: U256, + min_gas_price: (U256, PrioritizationStrategy), + set: &mut TransactionSet, + by_hash: &mut HashMap, + local: &mut LocalTransactionsList, + ) -> bool { let order = TransactionOrder::for_transaction(&tx, base_nonce, min_gas_price.0, min_gas_price.1); let hash = tx.hash(); let address = tx.sender(); @@ -1021,14 +1095,24 @@ impl TransactionQueue { if let Some(old) = set.insert(address, nonce, order.clone()) { - Self::replace_orders(address, nonce, old, order, set, by_hash) + Self::replace_orders(address, nonce, old, order, set, by_hash, local) } else { true } } - fn replace_orders(address: Address, nonce: U256, old: TransactionOrder, order: TransactionOrder, set: &mut TransactionSet, by_hash: &mut HashMap) -> bool { + fn replace_orders( + address: Address, + nonce: U256, + old: TransactionOrder, + order: TransactionOrder, + set: &mut TransactionSet, + by_hash: &mut HashMap, + local: &mut LocalTransactionsList, + ) -> bool { // There was already transaction in queue. Let's check which one should stay + let old_hash = old.hash; + let new_hash = order.hash; let old_fee = old.gas_price; let new_fee = order.gas_price; if old_fee.cmp(&new_fee) == Ordering::Greater { @@ -1036,12 +1120,18 @@ impl TransactionQueue { // Put back old transaction since it has greater priority (higher gas_price) set.insert(address, nonce, old); // and remove new one - by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); + let order = by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); + if order.origin.is_local() { + local.mark_replaced(order.transaction, old_fee, old_hash); + } false } else { trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash); // Make sure we remove old transaction entirely - by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); + let old = by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); + if old.origin.is_local() { + local.mark_replaced(old.transaction, new_fee, new_hash); + } true } } @@ -1078,6 +1168,7 @@ mod test { use error::{Error, TransactionError}; use super::*; use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; + use miner::local_transactions::LocalTransactionsList; use client::TransactionImportResult; fn unwrap_tx_err(err: Result) -> TransactionError { @@ -1208,6 +1299,7 @@ mod test { #[test] fn should_create_transaction_set() { // given + let mut local = LocalTransactionsList::default(); let mut set = TransactionSet { by_priority: BTreeSet::new(), by_address: Table::new(), @@ -1235,7 +1327,7 @@ mod test { assert_eq!(set.by_address.len(), 2); // when - set.enforce_limit(&mut by_hash); + set.enforce_limit(&mut by_hash, &mut local); // then assert_eq!(by_hash.len(), 1); From 2cd2b103273fa6a360750299b2f2d1f39e27b91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 16 Nov 2016 17:54:54 +0100 Subject: [PATCH 05/21] Local transactions RPC --- ethcore/src/miner/local_transactions.rs | 3 + ethcore/src/miner/miner.rs | 12 +- ethcore/src/miner/mod.rs | 4 + ethcore/src/miner/transaction_queue.rs | 8 +- rpc/src/v1/helpers/errors.rs | 64 ++++++----- rpc/src/v1/impls/parity.rs | 17 ++- rpc/src/v1/tests/helpers/miner_service.rs | 9 +- rpc/src/v1/tests/mocked/parity.rs | 16 ++- rpc/src/v1/traits/parity.rs | 10 +- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/transaction.rs | 133 +++++++++++++++++++++- 11 files changed, 240 insertions(+), 38 deletions(-) diff --git a/ethcore/src/miner/local_transactions.rs b/ethcore/src/miner/local_transactions.rs index c9c869c33..c8afcc0d5 100644 --- a/ethcore/src/miner/local_transactions.rs +++ b/ethcore/src/miner/local_transactions.rs @@ -21,6 +21,9 @@ use transaction::SignedTransaction; use error::TransactionError; use util::{U256, H256}; +/// Status of local transaction. +/// Can indicate that the transaction is currently part of the queue (`Pending/Future`) +/// or gives a reason why the transaction was removed. #[derive(Debug, PartialEq, Clone)] pub enum Status { /// The transaction is currently in the transaction queue. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index af4677cf3..f86ecedc4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,6 +23,7 @@ use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; +use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; use error::*; @@ -33,10 +34,11 @@ use engines::Engine; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; -use client::TransactionImportResult; use miner::price_info::PriceInfo; +use miner::local_transactions::{Status as LocalTransactionStatus}; use header::BlockNumber; + /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] pub enum PendingSet { @@ -845,6 +847,14 @@ impl MinerService for Miner { queue.top_transactions() } + fn local_transactions(&self) -> BTreeMap { + let queue = self.transaction_queue.lock(); + queue.local_transactions() + .iter() + .map(|(hash, status)| (*hash, status.clone())) + .collect() + } + fn pending_transactions(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); match self.options.pending_set { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 29d731e9e..1fb2244fd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -52,6 +52,7 @@ mod work_notify; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; +pub use self::local_transactions::{Status as LocalTransactionStatus}; pub use client::TransactionImportResult; use std::collections::BTreeMap; @@ -146,6 +147,9 @@ pub trait MinerService : Send + Sync { /// Get a list of all pending transactions. fn pending_transactions(&self, best_block: BlockNumber) -> Vec; + /// Get a list of local transactions with statuses. + fn local_transactions(&self) -> BTreeMap; + /// Get a list of all pending receipts. fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 9f688adf5..bb7dea1eb 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -86,12 +86,13 @@ use std::ops::Deref; use std::cmp::Ordering; use std::cmp; use std::collections::{HashSet, HashMap, BTreeSet, BTreeMap}; +use linked_hash_map::LinkedHashMap; use util::{Address, H256, Uint, U256}; use util::table::Table; use transaction::*; use error::{Error, TransactionError}; use client::TransactionImportResult; -use miner::local_transactions::LocalTransactionsList; +use miner::local_transactions::{LocalTransactionsList, Status as LocalTransactionStatus}; /// Transaction origin #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -901,6 +902,11 @@ impl TransactionQueue { .collect() } + /// Returns local transactions (some of them might not be part of the queue anymore). + pub fn local_transactions(&self) -> &LinkedHashMap { + self.local_transactions.all_transactions() + } + #[cfg(test)] fn future_transactions(&self) -> Vec { self.future.by_priority diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index d36feca4b..673987084 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -22,7 +22,7 @@ macro_rules! rpc_unimplemented { use std::fmt; use rlp::DecoderError; -use ethcore::error::{Error as EthcoreError, CallError}; +use ethcore::error::{Error as EthcoreError, CallError, TransactionError}; use ethcore::account_provider::{Error as AccountError}; use fetch::FetchError; use jsonrpc_core::{Error, ErrorCode, Value}; @@ -227,40 +227,44 @@ pub fn from_password_error(error: AccountError) -> Error { } } -pub fn from_transaction_error(error: EthcoreError) -> Error { +pub fn transaction_message(error: TransactionError) -> String { use ethcore::error::TransactionError::*; + match error { + AlreadyImported => "Transaction with the same hash was already imported.".into(), + Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), + TooCheapToReplace => { + "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() + }, + LimitReached => { + "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() + }, + InsufficientGas { minimal, got } => { + format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) + }, + InsufficientGasPrice { minimal, got } => { + format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) + }, + InsufficientBalance { balance, cost } => { + format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) + }, + GasLimitExceeded { limit, got } => { + format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) + }, + InvalidNetworkId => "Invalid network id.".into(), + InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), + SenderBanned => "Sender is banned in local queue.".into(), + RecipientBanned => "Recipient is banned in local queue.".into(), + CodeBanned => "Code is banned in local queue.".into(), + } +} + +pub fn from_transaction_error(error: EthcoreError) -> Error { + if let EthcoreError::Transaction(e) = error { - let msg = match e { - AlreadyImported => "Transaction with the same hash was already imported.".into(), - Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), - TooCheapToReplace => { - "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() - }, - LimitReached => { - "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() - }, - InsufficientGas { minimal, got } => { - format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) - }, - InsufficientGasPrice { minimal, got } => { - format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) - }, - InsufficientBalance { balance, cost } => { - format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) - }, - GasLimitExceeded { limit, got } => { - format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) - }, - InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), - SenderBanned => "Sender is banned in local queue.".into(), - RecipientBanned => "Recipient is banned in local queue.".into(), - CodeBanned => "Code is banned in local queue.".into(), - e => format!("{}", e).into(), - }; Error { code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), - message: msg, + message: transaction_message(e), data: None, } } else { diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index a952d54ec..1fdcbdef8 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -34,7 +34,11 @@ use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; use v1::traits::Parity; -use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; +use v1::types::{ + Bytes, U256, H160, H256, H512, + Peers, Transaction, RpcSettings, Histogram, + TransactionStats, LocalTransactionStatus, +}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::DEFAULT_MAC; @@ -269,6 +273,17 @@ impl Parity for ParityClient where ) } + fn local_transactions(&self) -> Result, Error> { + try!(self.active()); + + let transactions = take_weak!(self.miner).local_transactions(); + Ok(transactions + .into_iter() + .map(|(hash, status)| (hash.into(), status.into())) + .collect() + ) + } + fn signer_port(&self) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index af158d564..ad55faa7b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -24,7 +24,7 @@ use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; -use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult}; +use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; /// Test miner service. pub struct TestMinerService { @@ -34,6 +34,8 @@ pub struct TestMinerService { pub latest_closed_block: Mutex>, /// Pre-existed pending transactions pub pending_transactions: Mutex>, + /// Pre-existed local transactions + pub local_transactions: Mutex>, /// Pre-existed pending receipts pub pending_receipts: Mutex>, /// Last nonces. @@ -53,6 +55,7 @@ impl Default for TestMinerService { imported_transactions: Mutex::new(Vec::new()), latest_closed_block: Mutex::new(None), pending_transactions: Mutex::new(HashMap::new()), + local_transactions: Mutex::new(BTreeMap::new()), pending_receipts: Mutex::new(BTreeMap::new()), last_nonces: RwLock::new(HashMap::new()), min_gas_price: RwLock::new(U256::from(20_000_000)), @@ -195,6 +198,10 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().values().cloned().collect() } + fn local_transactions(&self) -> BTreeMap { + self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() + } + fn pending_transactions(&self, _best_block: BlockNumber) -> Vec { self.pending_transactions.lock().values().cloned().collect() } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 8c06d4c7d..5226e2f96 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -18,8 +18,9 @@ use std::sync::Arc; use util::log::RotatingLogger; use util::Address; use ethsync::ManageNetwork; -use ethcore::client::{TestBlockChainClient}; use ethcore::account_provider::AccountProvider; +use ethcore::client::{TestBlockChainClient}; +use ethcore::miner::LocalTransactionStatus; use ethstore::ethkey::{Generator, Random}; use jsonrpc_core::IoHandler; @@ -367,3 +368,16 @@ fn rpc_parity_transactions_stats() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } +#[test] +fn rpc_parity_local_transactions() { + let deps = Dependencies::new(); + let io = deps.default_client(); + deps.miner.local_transactions.lock().insert(10.into(), LocalTransactionStatus::Pending); + deps.miner.local_transactions.lock().insert(15.into(), LocalTransactionStatus::Future); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_localTransactions", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x000000000000000000000000000000000000000000000000000000000000000a":{"status":"pending"},"0x000000000000000000000000000000000000000000000000000000000000000f":{"status":"future"}},"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 946da8149..b4df594e8 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -19,7 +19,11 @@ use jsonrpc_core::Error; use std::collections::BTreeMap; use v1::helpers::auto_args::Wrap; -use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram, TransactionStats}; +use v1::types::{ + H160, H256, H512, U256, Bytes, + Peers, Transaction, RpcSettings, Histogram, + TransactionStats, LocalTransactionStatus, +}; build_rpc_trait! { /// Parity-specific rpc interface. @@ -119,6 +123,10 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactionsStats")] fn pending_transactions_stats(&self) -> Result, Error>; + /// Returns a list of current and past local transactions with status details. + #[rpc(name = "parity_localTransactions")] + fn local_transactions(&self) -> Result, Error>; + /// Returns current Trusted Signer port or an error if signer is disabled. #[rpc(name = "parity_signerPort")] fn signer_port(&self) -> Result; diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 29e50aae5..4ee61387e 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -44,7 +44,7 @@ pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; pub use self::log::Log; pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo, TransactionStats}; -pub use self::transaction::Transaction; +pub use self::transaction::{Transaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; pub use self::rpc_settings::RpcSettings; diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 0982a5ef9..7379a3b19 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -14,8 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use serde::{Serialize, Serializer}; +use ethcore::miner; use ethcore::contract_address; use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; +use v1::helpers::errors; use v1::types::{Bytes, H160, H256, U256, H512}; /// Transaction @@ -62,6 +65,74 @@ pub struct Transaction { pub s: H256, } +/// Local Transaction Status +#[derive(Debug)] +pub enum LocalTransactionStatus { + /// Transaction is pending + Pending, + /// Transaction is in future part of the queue + Future, + /// Transaction is already mined. + Mined(Transaction), + /// Transaction was dropped because of limit. + Dropped(Transaction), + /// Transaction was replaced by transaction with higher gas price. + Replaced(Transaction, U256, H256), + /// Transaction never got into the queue. + Rejected(Transaction, String), + /// Transaction is invalid. + Invalid(Transaction), +} + +impl Serialize for LocalTransactionStatus { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + use self::LocalTransactionStatus::*; + + let elems = match *self { + Pending | Future => 1, + Mined(..) | Dropped(..) | Invalid(..) => 2, + Rejected(..) => 3, + Replaced(..) => 4, + }; + + let status = "status"; + let transaction = "transaction"; + + let mut state = try!(serializer.serialize_struct("LocalTransactionStatus", elems)); + match *self { + Pending => try!(serializer.serialize_struct_elt(&mut state, status, "pending")), + Future => try!(serializer.serialize_struct_elt(&mut state, status, "future")), + Mined(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "mined")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Dropped(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "dropped")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Invalid(ref tx) => { + try!(serializer.serialize_struct_elt(&mut state, status, "invalid")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + }, + Rejected(ref tx, ref reason) => { + try!(serializer.serialize_struct_elt(&mut state, status, "rejected")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + try!(serializer.serialize_struct_elt(&mut state, "error", reason)); + }, + Replaced(ref tx, ref gas_price, ref hash) => { + try!(serializer.serialize_struct_elt(&mut state, status, "replaced")); + try!(serializer.serialize_struct_elt(&mut state, transaction, tx)); + try!(serializer.serialize_struct_elt(&mut state, "hash", hash)); + try!(serializer.serialize_struct_elt(&mut state, "gasPrice", gas_price)); + }, + } + serializer.serialize_struct_end(state) + } +} + + impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { let signature = t.signature(); @@ -124,9 +195,24 @@ impl From for Transaction { } } +impl From for LocalTransactionStatus { + fn from(s: miner::LocalTransactionStatus) -> Self { + use ethcore::miner::LocalTransactionStatus::*; + match s { + Pending => LocalTransactionStatus::Pending, + Future => LocalTransactionStatus::Future, + Mined(tx) => LocalTransactionStatus::Mined(tx.into()), + Dropped(tx) => LocalTransactionStatus::Dropped(tx.into()), + Rejected(tx, err) => LocalTransactionStatus::Rejected(tx.into(), errors::transaction_message(err)), + Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(tx.into(), gas_price.into(), hash.into()), + Invalid(tx) => LocalTransactionStatus::Invalid(tx.into()), + } + } +} + #[cfg(test)] mod tests { - use super::Transaction; + use super::{Transaction, LocalTransactionStatus}; use serde_json; #[test] @@ -135,5 +221,50 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"v":0,"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#); } + + #[test] + fn test_local_transaction_status_serialize() { + let tx_ser = serde_json::to_string(&Transaction::default()).unwrap(); + let status1 = LocalTransactionStatus::Pending; + let status2 = LocalTransactionStatus::Future; + let status3 = LocalTransactionStatus::Mined(Transaction::default()); + let status4 = LocalTransactionStatus::Dropped(Transaction::default()); + let status5 = LocalTransactionStatus::Invalid(Transaction::default()); + let status6 = LocalTransactionStatus::Rejected(Transaction::default(), "Just because".into()); + let status7 = LocalTransactionStatus::Replaced(Transaction::default(), 5.into(), 10.into()); + + assert_eq!( + serde_json::to_string(&status1).unwrap(), + r#"{"status":"pending"}"# + ); + assert_eq!( + serde_json::to_string(&status2).unwrap(), + r#"{"status":"future"}"# + ); + assert_eq!( + serde_json::to_string(&status3).unwrap(), + r#"{"status":"mined","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status4).unwrap(), + r#"{"status":"dropped","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status5).unwrap(), + r#"{"status":"invalid","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + ); + assert_eq!( + serde_json::to_string(&status6).unwrap(), + r#"{"status":"rejected","transaction":"#.to_owned() + + &format!("{}", tx_ser) + + r#","error":"Just because"}"# + ); + assert_eq!( + serde_json::to_string(&status7).unwrap(), + r#"{"status":"replaced","transaction":"#.to_owned() + + &format!("{}", tx_ser) + + r#","hash":"0x000000000000000000000000000000000000000000000000000000000000000a","gasPrice":"0x5"}"# + ); + } } From ff27bbcb4fea40a4e38014902f7914774655106e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 10:15:11 +0100 Subject: [PATCH 06/21] Simple GUI for local transactions --- js/src/api/rpc/parity/parity.js | 30 +- js/src/dapps/localtx.html | 17 + js/src/dapps/localtx.js | 33 ++ .../dapps/localtx/Application/application.js | 197 ++++++++++ .../localtx/Application/application.spec.js | 32 ++ js/src/dapps/localtx/Application/index.js | 17 + js/src/dapps/localtx/Transaction/index.js | 17 + .../dapps/localtx/Transaction/transaction.css | 31 ++ .../dapps/localtx/Transaction/transaction.js | 359 ++++++++++++++++++ .../localtx/Transaction/transaction.spec.js | 37 ++ js/src/dapps/localtx/parity.js | 21 + js/src/jsonrpc/interfaces/parity.js | 45 ++- js/src/views/Dapps/builtin.json | 10 + js/webpack.config.js | 1 + 14 files changed, 834 insertions(+), 13 deletions(-) create mode 100644 js/src/dapps/localtx.html create mode 100644 js/src/dapps/localtx.js create mode 100644 js/src/dapps/localtx/Application/application.js create mode 100644 js/src/dapps/localtx/Application/application.spec.js create mode 100644 js/src/dapps/localtx/Application/index.js create mode 100644 js/src/dapps/localtx/Transaction/index.js create mode 100644 js/src/dapps/localtx/Transaction/transaction.css create mode 100644 js/src/dapps/localtx/Transaction/transaction.js create mode 100644 js/src/dapps/localtx/Transaction/transaction.spec.js create mode 100644 js/src/dapps/localtx/parity.js diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index a33828b80..2c90d091b 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input'; -import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output'; +import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output'; export default class Parity { constructor (transport) { @@ -117,16 +117,27 @@ export default class Parity { .execute('parity_hashContent', url); } + importGethAccounts (accounts) { + return this._transport + .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) + .then((accounts) => (accounts || []).map(outAddress)); + } + listGethAccounts () { return this._transport .execute('parity_listGethAccounts') .then((accounts) => (accounts || []).map(outAddress)); } - importGethAccounts (accounts) { + localTransactions () { return this._transport - .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) - .then((accounts) => (accounts || []).map(outAddress)); + .execute('parity_localTransactions') + .then(transactions => { + Object.values(transactions) + .filter(tx => tx.transaction) + .map(tx => tx.transaction = outTransaction(tx.transaction)); + return transactions; + }); } minGasPrice () { @@ -192,6 +203,17 @@ export default class Parity { .execute('parity_nodeName'); } + pendingTransactions () { + return this._transport + .execute('parity_pendingTransactions') + .then(data => data.map(outTransaction)); + } + + pendingTransactionsStats () { + return this._transport + .execute('parity_pendingTransactionsStats'); + } + phraseToAddress (phrase) { return this._transport .execute('parity_phraseToAddress', phrase) diff --git a/js/src/dapps/localtx.html b/js/src/dapps/localtx.html new file mode 100644 index 000000000..d1e6fed05 --- /dev/null +++ b/js/src/dapps/localtx.html @@ -0,0 +1,17 @@ + + + + + + + + Local transactions Viewer + + +
+ + + + + + diff --git a/js/src/dapps/localtx.js b/js/src/dapps/localtx.js new file mode 100644 index 000000000..98561f33f --- /dev/null +++ b/js/src/dapps/localtx.js @@ -0,0 +1,33 @@ +// Copyright 2015, 2016 Ethcore (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 . + +import ReactDOM from 'react-dom'; +import React from 'react'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); + +import Application from './localtx/Application'; + +import '../../assets/fonts/Roboto/font.css'; +import '../../assets/fonts/RobotoMono/font.css'; +import './style.css'; +import './localtx.html'; + +ReactDOM.render( + , + document.querySelector('#container') +); diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js new file mode 100644 index 000000000..cbe7eebaa --- /dev/null +++ b/js/src/dapps/localtx/Application/application.js @@ -0,0 +1,197 @@ +// Copyright 2015, 2016 Ethcore (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 . + +import BigNumber from 'bignumber.js'; +import React, { Component } from 'react'; + +import { api } from '../parity'; + +import { Transaction, LocalTransaction } from '../Transaction'; + +export default class Application extends Component { + state = { + loading: true, + transactions: [], + localTransactions: {} + } + + componentDidMount () { + const poll = () => this.fetchTransactionData().then(poll); + this._timeout = setTimeout(poll, 2000); + } + + componentWillUnmount () { + clearTimeout(this._timeout); + } + + fetchTransactionData () { + return Promise.all([ + api.parity.pendingTransactions(), + api.parity.pendingTransactionsStats(), + api.parity.localTransactions(), + ]).then(([pending, stats, local]) => { + // Combine results together + const transactions = pending.map(tx => { + return { + transaction: tx, + stats: stats[tx.hash], + isLocal: !!local[tx.hash] + }; + }); + + // Add transaction data to locals + transactions + .filter(tx => tx.isLocal) + .map(data => { + const tx = data.transaction; + local[tx.hash].transaction = tx; + local[tx.hash].stats = data.stats; + }); + + // Convert local transactions to array + const localTransactions = Object.keys(local).map(hash => { + const data = local[hash]; + data.txHash = hash; + return data; + }); + + // Sort local transactions by nonce (move future to the end) + localTransactions.sort((a, b) => { + a = a.transaction || {}; + b = b.transaction || {}; + + if (a.from && b.from && a.from !== b.from) { + return a.from < b.from; + } + + if (!a.nonce || !b.nonce) { + return !a.nonce ? 1 : -1; + } + + return new BigNumber(a.nonce).comparedTo(new BigNumber(b.nonce)); + }); + + this.setState({ + loading: false, + transactions, + localTransactions + }); + }); + } + + render () { + const { loading } = this.state; + + if (loading) { + return ( +
Loading...
+ ); + } + + return ( +
+

Your past local transactions

+ { this.renderLocals() } +

Transactions in the queue

+ { this.renderQueueSummary() } + { this.renderQueue() } +
+ ); + } + + renderQueueSummary () { + const { transactions } = this.state; + if (!transactions.length) { + return null; + } + + const count = transactions.length; + const locals = transactions.filter(tx => tx.isLocal).length; + const fee = transactions + .map(tx => tx.transaction) + .map(tx => tx.gasPrice.mul(tx.gas)) + .reduce((sum, fee) => sum.add(fee), new BigNumber(0)); + + return ( +

+ Count: { locals ? `${count} (${locals})` : count } +   + Total Fee: { api.util.fromWei(fee).toFixed(3) } ETH +

+ ); + } + + renderQueue () { + const { transactions } = this.state; + if (!transactions.length) { + return ( +

The queue seems is empty.

+ ); + } + + return ( + + + { Transaction.renderHeader() } + + + { + transactions.map((tx, idx) => ( + + )) + } + +
+ ); + } + + renderLocals () { + const { localTransactions } = this.state; + if (!localTransactions.length) { + return ( +

You haven't sent any transactions yet.

+ ); + } + + return ( + + + { LocalTransaction.renderHeader() } + + + { + localTransactions.map(tx => ( + + )) + } + +
+ ); + } +} diff --git a/js/src/dapps/localtx/Application/application.spec.js b/js/src/dapps/localtx/Application/application.spec.js new file mode 100644 index 000000000..2044b4e14 --- /dev/null +++ b/js/src/dapps/localtx/Application/application.spec.js @@ -0,0 +1,32 @@ +// Copyright 2015, 2016 Ethcore (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 . + +import React from 'react'; +import { shallow } from 'enzyme'; + +import '../../../environment/tests'; + +import Application from './application'; + +describe('localtx/Application', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow(); + + expect(rendered).to.be.defined; + }); + }); +}); diff --git a/js/src/dapps/localtx/Application/index.js b/js/src/dapps/localtx/Application/index.js new file mode 100644 index 000000000..236578226 --- /dev/null +++ b/js/src/dapps/localtx/Application/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 . + +export default from './application'; diff --git a/js/src/dapps/localtx/Transaction/index.js b/js/src/dapps/localtx/Transaction/index.js new file mode 100644 index 000000000..56854f412 --- /dev/null +++ b/js/src/dapps/localtx/Transaction/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 . + +export { Transaction, LocalTransaction } from './transaction'; diff --git a/js/src/dapps/localtx/Transaction/transaction.css b/js/src/dapps/localtx/Transaction/transaction.css new file mode 100644 index 000000000..c49d9479f --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.css @@ -0,0 +1,31 @@ +.from { + white-space: nowrap; + + img { + vertical-align: middle; + } +} + +.transaction { + td { + padding: 7px 15px; + } + + td:first-child { + padding: 7px 0; + } + + &.local { + background: #8bc34a; + } +} + +.nowrap { + white-space: nowrap; +} + +.edit { + label, input { + display: block; + } +} diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js new file mode 100644 index 000000000..e3195a59d --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -0,0 +1,359 @@ +// Copyright 2015, 2016 Ethcore (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 . + +import React, { Component, PropTypes } from 'react'; +import classnames from 'classnames'; + +import { api } from '../parity'; + +import styles from './transaction.css'; + +import IdentityIcon from '../../githubhint/IdentityIcon'; + +class BaseTransaction extends Component { + + shortHash (hash) { + return `${hash.substr(0, 6)}..${hash.substr(hash.length - 4)}`; + } + + renderHash (hash) { + return ( + + { this.shortHash(hash) } + + ); + } + + renderFrom (transaction) { + if (!transaction) { + return '-'; + } + + return ( +
+ + 0x{ transaction.nonce.toString(16) } +
+ ); + } + + renderGasPrice (transaction) { + if (!transaction) { + return '-'; + } + + return ( + + { api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon + + ); + } + + renderGas (transaction) { + if (!transaction) { + return '-'; + } + + return ( + + { transaction.gas.div(10**6).toFormat(3) } MGas + + ); + } + + renderPropagation (stats) { + const noOfPeers = Object.keys(stats.propagatedTo).length; + const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + + return ( + + { noOfPropagations } ({ noOfPeers } peers) + + ); + } +} + +export class Transaction extends BaseTransaction { + + static propTypes = { + idx: PropTypes.number.isRequired, + transaction: PropTypes.object.isRequired, + isLocal: PropTypes.bool, + stats: PropTypes.object + }; + + static defaultProps = { + isLocal: false, + stats: { + firstSeen: 0, + propagatedTo: {} + } + }; + + static renderHeader () { + return ( + + + + Transaction + + + From + + + Gas Price + + + Gas + + + First seen + + + # Propagated + + + + + ); + } + + render () { + const { isLocal, stats, transaction, idx } = this.props; + + const clazz = classnames(styles.transaction, { + [styles.local]: isLocal + }); + const noOfPeers = Object.keys(stats.propagatedTo).length; + const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + + return ( + + + { idx }. + + + { this.renderHash(transaction.hash) } + + + { this.renderFrom(transaction) } + + + { this.renderGasPrice(transaction) } + + + { this.renderGas(transaction) } + + + { stats.firstSeen } + + + { this.renderPropagation(stats) } + + + ); + } +} + +export class LocalTransaction extends BaseTransaction { + + static propTypes = { + hash: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + transaction: PropTypes.object, + isLocal: PropTypes.bool, + stats: PropTypes.object, + details: PropTypes.object + }; + + static defaultProps = { + stats: { + propagatedTo: {} + } + }; + + static renderHeader () { + return ( + + + + Transaction + + + From + + + Gas Price / Gas + + + Propagated + + + Status + + + ); + } + + state = { + isSending: false, + isResubmitting: false, + gasPrice: null, + gas: null + }; + + toggleResubmit = () => { + const { transaction } = this.props; + const { isResubmitting, gasPrice } = this.state; + + this.setState({ + isResubmitting: !isResubmitting, + }); + + if (gasPrice === null) { + this.setState({ + gasPrice: `0x${ transaction.gasPrice.toString(16) }`, + gas: `0x${ transaction.gas.toString(16) }` + }); + } + }; + + sendTransaction = () => { + const { transaction } = this.props; + const { gasPrice, gas } = this.state; + + const newTransaction = { + from: transaction.from, + to: transaction.to, + nonce: transaction.nonce, + value: transaction.value, + data: transaction.data, + gasPrice, gas + }; + + this.setState({ + isResubmitting: false, + isSending: true + }); + + const closeSending = () => this.setState({ + isSending: false + }); + + api.eth.sendTransaction(newTransaction) + .then(closeSending) + .catch(closeSending); + }; + + render () { + if (this.state.isResubmitting) { + return this.renderResubmit(); + } + + const { stats, transaction, hash, status } = this.props; + const { isSending } = this.state; + + const resubmit = isSending ? ( + 'sending...' + ) : ( + + resubmit + + ); + + return ( + + + { !transaction ? null : resubmit } + + + { this.renderHash(hash) } + + + { this.renderFrom(transaction) } + + + { this.renderGasPrice(transaction) } +
+ { this.renderGas(transaction) } + + + { status === 'pending' ? this.renderPropagation(stats) : '-' } + + + { this.renderStatus() } + + + ); + } + + renderStatus () { + const { details } = this.props; + + let state = { + 'pending': () => `In queue: Pending`, + 'future': () => `In queue: Future`, + 'mined': () => `Mined`, + 'dropped': () => `Dropped because of queue limit`, + 'invalid': () => `Transaction is invalid`, + 'rejected': () => `Rejected: ${ details.error }`, + 'replaced': () => `Replaced by ${ this.shortHash(details.hash) }`, + }[this.props.status]; + + return state ? state() : 'unknown'; + } + + renderResubmit () { + const { transaction } = this.props; + const { gasPrice, gas } = this.state; + + return ( + + + + cancel + + + + { this.renderHash(transaction.hash) } + + + { this.renderFrom(transaction) } + + + this.setState({ gasPrice: el.target.value }) } + /> + this.setState({ gas: el.target.value }) } + /> + + + + Send + + + + ); + } + +} diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js new file mode 100644 index 000000000..1fe78124c --- /dev/null +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -0,0 +1,37 @@ +// Copyright 2015, 2016 Ethcore (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 . + +import React from 'react'; +import { shallow } from 'enzyme'; + +import '../../../environment/tests'; + +import Transaction from './transaction'; + +describe('localtx/Transaction', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow( + + ); + + expect(rendered).to.be.defined; + }); + }); +}); diff --git a/js/src/dapps/localtx/parity.js b/js/src/dapps/localtx/parity.js new file mode 100644 index 000000000..acee4dee0 --- /dev/null +++ b/js/src/dapps/localtx/parity.js @@ -0,0 +1,21 @@ +// Copyright 2015, 2016 Ethcore (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 . + +const api = window.parent.secureApi; + +export { + api +}; diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 5dd313e00..a5717f502 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -224,15 +224,6 @@ export default { } }, - listGethAccounts: { - desc: 'Returns a list of the accounts available from Geth', - params: [], - returns: { - type: Array, - desc: '20 Bytes addresses owned by the client.' - } - }, - importGethAccounts: { desc: 'Imports a list of accounts from geth', params: [ @@ -247,6 +238,24 @@ export default { } }, + listGethAccounts: { + desc: 'Returns a list of the accounts available from Geth', + params: [], + returns: { + type: Array, + desc: '20 Bytes addresses owned by the client.' + } + }, + + localTransactions: { + desc: 'Returns an object of current and past local transactions.', + params: [], + returns: { + type: Object, + desc: 'Mapping of `tx hash` into status object.' + } + }, + minGasPrice: { desc: 'Returns currently set minimal gas price', params: [], @@ -379,6 +388,24 @@ export default { } }, + pendingTransactions: { + desc: 'Returns a list of transactions currently in the queue.', + params: [], + returns: { + type: Array, + desc: 'Transactions ordered by priority' + } + }, + + pendingTransactionsStats: { + desc: 'Returns propagation stats for transactions in the queue', + params: [], + returns: { + type: Object, + desc: 'mapping of `tx hash` into `stats`' + } + }, + phraseToAddress: { desc: 'Converts a secret phrase into the corresponting address', params: [ diff --git a/js/src/views/Dapps/builtin.json b/js/src/views/Dapps/builtin.json index 827c52db0..71f3cd6ed 100644 --- a/js/src/views/Dapps/builtin.json +++ b/js/src/views/Dapps/builtin.json @@ -39,5 +39,15 @@ "author": "Parity Team ", "version": "1.0.0", "secure": true + }, + { + "id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d", + "url": "localtx", + "name": "TxQueue Viewer", + "description": "Have a peak on internals of transaction queue of your node.", + "author": "Parity Team ", + "version": "1.0.0", + "secure": true } + ] diff --git a/js/webpack.config.js b/js/webpack.config.js index 4413299fa..7d445262e 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -40,6 +40,7 @@ module.exports = { 'githubhint': ['./dapps/githubhint.js'], 'registry': ['./dapps/registry.js'], 'signaturereg': ['./dapps/signaturereg.js'], + 'localtx': ['./dapps/localtx.js'], 'tokenreg': ['./dapps/tokenreg.js'], // app 'index': ['./index.js'] From 74bf2c75f0c11e7009ca095a71a28d706d5f38bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 11:01:21 +0100 Subject: [PATCH 07/21] Transaction queue improvements --- ethcore/src/miner/miner.rs | 27 +++-- ethcore/src/miner/transaction_queue.rs | 102 +++++++++++++++++- .../dapps/localtx/Application/application.css | 19 ++++ .../dapps/localtx/Application/application.js | 22 ++-- .../dapps/localtx/Transaction/transaction.js | 33 ++++-- 5 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 js/src/dapps/localtx/Application/application.css diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b770aa40c..df55e7367 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -564,7 +564,7 @@ impl Miner { prepare_new } - fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> + fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, default_origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> Vec> { let fetch_account = |a: &Address| AccountDetails { @@ -572,15 +572,28 @@ impl Miner { balance: chain.latest_balance(a), }; + let accounts = self.accounts.as_ref() + .and_then(|provider| provider.accounts().ok()) + .map(|accounts| accounts.into_iter().collect::>()); + let schedule = chain.latest_schedule(); let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); transactions.into_iter() - .map(|tx| match origin { - TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - transaction_queue.add(tx, origin, &fetch_account, &gas_required) - }, - TransactionOrigin::External => { - transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) + .map(|tx| { + let origin = accounts.as_ref().and_then(|accounts| { + tx.sender().ok().and_then(|sender| match accounts.contains(&sender) { + true => Some(TransactionOrigin::Local), + false => None, + }) + }).unwrap_or(default_origin); + + match origin { + TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { + transaction_queue.add(tx, origin, &fetch_account, &gas_required) + }, + TransactionOrigin::External => { + transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) + } } }) .collect() diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index bb7dea1eb..31c27982a 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -250,6 +250,7 @@ impl Ord for TransactionOrder { } /// Verified transaction (with sender) +#[derive(Debug)] struct VerifiedTransaction { /// Transaction transaction: SignedTransaction, @@ -637,7 +638,11 @@ impl TransactionQueue { self.local_transactions.mark_future(hash); }, Err(Error::Transaction(ref err)) => { - self.local_transactions.mark_rejected(cloned_tx, err.clone()); + // Sometimes transactions are re-imported, so + // don't overwrite transactions if they are already on the list + if !self.local_transactions.contains(&cloned_tx.hash()) { + self.local_transactions.mark_rejected(cloned_tx, err.clone()); + } }, Err(_) => { self.local_transactions.mark_invalid(cloned_tx); @@ -772,6 +777,12 @@ impl TransactionQueue { None => return, Some(t) => t, }; + + // Never penalize local transactions + if transaction.origin.is_local() { + return; + } + let sender = transaction.sender(); // Penalize all transactions from this sender @@ -843,6 +854,33 @@ impl TransactionQueue { } } + /// Marks all transactions from particular sender as local transactions + fn mark_transactions_local(&mut self, sender: &Address) { + fn mark_local(sender: &Address, set: &mut TransactionSet, mut mark: F) { + // Mark all transactions from this sender as local + let nonces_from_sender = set.by_address.row(sender) + .map(|row_map| { + row_map.iter().filter_map(|(nonce, order)| if order.origin.is_local() { + None + } else { + Some(*nonce) + }).collect::>() + }) + .unwrap_or_else(Vec::new); + + for k in nonces_from_sender { + let mut order = set.drop(sender, &k).expect("transaction known to be in self.current/self.future; qed"); + order.origin = TransactionOrigin::Local; + mark(order.hash); + set.insert(*sender, k, order); + } + } + + let local = &mut self.local_transactions; + mark_local(sender, &mut self.current, |hash| local.mark_pending(hash)); + mark_local(sender, &mut self.future, |hash| local.mark_future(hash)); + } + /// Update height of all transactions in future transactions set. fn update_future(&mut self, sender: &Address, current_nonce: U256) { // We need to drain all transactions for current sender from future and reinsert them with updated height @@ -1022,6 +1060,10 @@ impl TransactionQueue { .cloned() .map_or(state_nonce, |n| n + U256::one()); + if tx.origin.is_local() { + self.mark_transactions_local(&address); + } + // Future transaction if nonce > next_nonce { // We have a gap - put to future. @@ -1099,6 +1141,7 @@ impl TransactionQueue { let old_hash = by_hash.insert(hash, tx); assert!(old_hash.is_none(), "Each hash has to be inserted exactly once."); + trace!(target: "txqueue", "Inserting: {:?}", order); if let Some(old) = set.insert(address, nonce, order.clone()) { Self::replace_orders(address, nonce, old, order, set, by_hash, local) @@ -1726,6 +1769,31 @@ mod test { assert_eq!(top.len(), 2); } + #[test] + fn when_importing_local_should_mark_others_from_the_same_sender_as_local() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + // the second one has same nonce but higher `gas_price` + let (_, tx0) = new_similar_tx_pair(); + + txq.add(tx0.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + // the one with higher gas price is first + assert_eq!(txq.top_transactions()[0], tx0); + assert_eq!(txq.top_transactions()[1], tx1); + + // when + // insert second as local + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + + // then + // the order should be updated + assert_eq!(txq.top_transactions()[0], tx1); + assert_eq!(txq.top_transactions()[1], tx0); + assert_eq!(txq.top_transactions()[2], tx2); + } + #[test] fn should_prioritize_reimported_transactions_within_same_nonce_height() { // given @@ -1793,6 +1861,38 @@ mod test { assert_eq!(top.len(), 4); } + #[test] + fn should_not_penalize_local_transactions() { + // given + let mut txq = TransactionQueue::default(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + + // when + txq.penalize(&tx1.hash()); + + // then (order is the same) + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + } #[test] fn should_penalize_transactions_from_sender() { diff --git a/js/src/dapps/localtx/Application/application.css b/js/src/dapps/localtx/Application/application.css new file mode 100644 index 000000000..4b5f0bc31 --- /dev/null +++ b/js/src/dapps/localtx/Application/application.css @@ -0,0 +1,19 @@ +.container { + padding: 1rem 2rem; + text-align: center; + + h1 { + margin-top: 3rem; + margin-bottom: 1rem; + } + + table { + text-align: left; + margin: auto; + max-width: 90vw; + + th { + text-align: center; + } + } +} diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index cbe7eebaa..a6e6cd166 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -19,17 +19,20 @@ import React, { Component } from 'react'; import { api } from '../parity'; +import styles from './application.css'; + import { Transaction, LocalTransaction } from '../Transaction'; export default class Application extends Component { state = { loading: true, transactions: [], - localTransactions: {} + localTransactions: {}, + blockNumber: 0 } componentDidMount () { - const poll = () => this.fetchTransactionData().then(poll); + const poll = () => this.fetchTransactionData().then(poll).catch(poll); this._timeout = setTimeout(poll, 2000); } @@ -42,7 +45,8 @@ export default class Application extends Component { api.parity.pendingTransactions(), api.parity.pendingTransactionsStats(), api.parity.localTransactions(), - ]).then(([pending, stats, local]) => { + api.eth.blockNumber() + ]).then(([pending, stats, local, blockNumber]) => { // Combine results together const transactions = pending.map(tx => { return { @@ -87,7 +91,8 @@ export default class Application extends Component { this.setState({ loading: false, transactions, - localTransactions + localTransactions, + blockNumber }); }); } @@ -97,13 +102,13 @@ export default class Application extends Component { if (loading) { return ( -
Loading...
+
Loading...
); } return ( -
-

Your past local transactions

+
+

Your local transactions

{ this.renderLocals() }

Transactions in the queue

{ this.renderQueueSummary() } @@ -135,7 +140,7 @@ export default class Application extends Component { } renderQueue () { - const { transactions } = this.state; + const { blockNumber, transactions } = this.state; if (!transactions.length) { return (

The queue seems is empty.

@@ -156,6 +161,7 @@ export default class Application extends Component { isLocal={ tx.isLocal } transaction={ tx.transaction } stats={ tx.stats } + blockNumber={ blockNumber } /> )) } diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index e3195a59d..0263a8430 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import classnames from 'classnames'; @@ -26,7 +27,7 @@ import IdentityIcon from '../../githubhint/IdentityIcon'; class BaseTransaction extends Component { shortHash (hash) { - return `${hash.substr(0, 6)}..${hash.substr(hash.length - 4)}`; + return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`; } renderHash (hash) { @@ -93,6 +94,7 @@ export class Transaction extends BaseTransaction { static propTypes = { idx: PropTypes.number.isRequired, transaction: PropTypes.object.isRequired, + blockNumber: PropTypes.object.isRequired, isLocal: PropTypes.bool, stats: PropTypes.object }; @@ -122,7 +124,7 @@ export class Transaction extends BaseTransaction { Gas - First seen + First propagation # Propagated @@ -141,6 +143,7 @@ export class Transaction extends BaseTransaction { }); const noOfPeers = Object.keys(stats.propagatedTo).length; const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); + const blockNo = new BigNumber(stats.firstSeen); return ( @@ -159,8 +162,8 @@ export class Transaction extends BaseTransaction { { this.renderGas(transaction) } - - { stats.firstSeen } + + { this.renderTime(stats.firstSeen) } { this.renderPropagation(stats) } @@ -168,6 +171,16 @@ export class Transaction extends BaseTransaction { ); } + + renderTime (firstSeen) { + const { blockNumber } = this.props; + if (!firstSeen) { + return 'never'; + } + + const timeInMinutes = blockNumber.sub(firstSeen).mul(14).div(60).toFormat(1); + return `${timeInMinutes} minutes ago`; + } } export class LocalTransaction extends BaseTransaction { @@ -200,9 +213,6 @@ export class LocalTransaction extends BaseTransaction { Gas Price / Gas - - Propagated - Status @@ -252,7 +262,9 @@ export class LocalTransaction extends BaseTransaction { }); const closeSending = () => this.setState({ - isSending: false + isSending: false, + gasPrice: null, + gas: null }); api.eth.sendTransaction(newTransaction) @@ -292,11 +304,10 @@ export class LocalTransaction extends BaseTransaction {
{ this.renderGas(transaction) } - - { status === 'pending' ? this.renderPropagation(stats) : '-' } - { this.renderStatus() } +
+ { status === 'pending' ? this.renderPropagation(stats) : null } ); From fc4b51fe683b5b37fb7610b0d825e787a22dca5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 13:42:18 +0100 Subject: [PATCH 08/21] Prioritizing local transactions regardless of nonce --- ethcore/src/miner/transaction_queue.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 31c27982a..00fe47243 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -209,17 +209,16 @@ impl Ord for TransactionOrder { return self.penalties.cmp(&b.penalties); } - // First check nonce_height - if self.nonce_height != b.nonce_height { - return self.nonce_height.cmp(&b.nonce_height); - } - // Local transactions should always have priority - // NOTE nonce has to be checked first, cause otherwise the order might be wrong. if self.origin != b.origin { return self.origin.cmp(&b.origin); } + // Check nonce_height + if self.nonce_height != b.nonce_height { + return self.nonce_height.cmp(&b.nonce_height); + } + match self.strategy { PrioritizationStrategy::GasAndGasPrice => { if self.gas != b.gas { @@ -1790,8 +1789,8 @@ mod test { // then // the order should be updated assert_eq!(txq.top_transactions()[0], tx1); - assert_eq!(txq.top_transactions()[1], tx0); - assert_eq!(txq.top_transactions()[2], tx2); + assert_eq!(txq.top_transactions()[1], tx2); + assert_eq!(txq.top_transactions()[2], tx0); } #[test] @@ -2141,8 +2140,8 @@ mod test { let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2)); txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit + txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); From 5c62e38a7c0063c3393ad9282c49b5ccb53642d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 13:48:25 +0100 Subject: [PATCH 09/21] Cleanup --- ethcore/src/client/client.rs | 9 +++------ ethcore/src/miner/miner.rs | 1 - ethcore/src/miner/transaction_queue.rs | 3 +-- js/src/views/Dapps/builtin.json | 1 - rpc/src/v1/types/transaction.rs | 1 - sync/build.rs | 1 - 6 files changed, 4 insertions(+), 12 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bf318aeda..0e0b292f9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -891,12 +891,9 @@ impl BlockChainClient for Client { let mut mode = self.mode.lock(); *mode = new_mode.clone().into(); trace!(target: "mode", "Mode now {:?}", &*mode); - match *self.on_mode_change.lock() { - Some(ref mut f) => { - trace!(target: "mode", "Making callback..."); - f(&*mode) - }, - _ => {} + if let Some(ref mut f) = *self.on_mode_change.lock() { + trace!(target: "mode", "Making callback..."); + f(&*mode) } } match new_mode { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index df55e7367..5530ac462 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -38,7 +38,6 @@ use miner::price_info::PriceInfo; use miner::local_transactions::{Status as LocalTransactionStatus}; use header::BlockNumber; - /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] pub enum PendingSet { diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 00fe47243..bfbd3fade 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -2137,12 +2137,11 @@ mod test { let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2)); + let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); - txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 4); diff --git a/js/src/views/Dapps/builtin.json b/js/src/views/Dapps/builtin.json index 71f3cd6ed..a2959bb18 100644 --- a/js/src/views/Dapps/builtin.json +++ b/js/src/views/Dapps/builtin.json @@ -49,5 +49,4 @@ "version": "1.0.0", "secure": true } - ] diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 7379a3b19..45f1cdd5e 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -132,7 +132,6 @@ impl Serialize for LocalTransactionStatus { } } - impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { let signature = t.signature(); diff --git a/sync/build.rs b/sync/build.rs index db881e328..c465d5e34 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -18,5 +18,4 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); - ethcore_ipc_codegen::derive_ipc_cond("src/transactions_stats.rs", cfg!(feature="ipc")).unwrap(); } From be6eb79296809b6907dc0ae25b731f58bd4c6055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:00:53 +0100 Subject: [PATCH 10/21] Fixing JS lints --- js/src/api/rpc/parity/parity.js | 4 +- .../dapps/localtx/Application/application.js | 2 +- .../dapps/localtx/Transaction/transaction.js | 46 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 2c90d091b..d14cc6554 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -135,7 +135,9 @@ export default class Parity { .then(transactions => { Object.values(transactions) .filter(tx => tx.transaction) - .map(tx => tx.transaction = outTransaction(tx.transaction)); + .map(tx => { + tx.transaction = outTransaction(tx.transaction); + }); return transactions; }); } diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index a6e6cd166..89b0b73b4 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -157,7 +157,7 @@ export default class Application extends Component { transactions.map((tx, idx) => ( + { api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon ); @@ -72,7 +72,7 @@ class BaseTransaction extends Component { return ( - { transaction.gas.div(10**6).toFormat(3) } MGas + { transaction.gas.div(10 ** 6).toFormat(3) } MGas ); } @@ -137,13 +137,11 @@ export class Transaction extends BaseTransaction { render () { const { isLocal, stats, transaction, idx } = this.props; + const blockNo = new BigNumber(stats.firstSeen); const clazz = classnames(styles.transaction, { [styles.local]: isLocal }); - const noOfPeers = Object.keys(stats.propagatedTo).length; - const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0); - const blockNo = new BigNumber(stats.firstSeen); return ( @@ -232,17 +230,29 @@ export class LocalTransaction extends BaseTransaction { const { isResubmitting, gasPrice } = this.state; this.setState({ - isResubmitting: !isResubmitting, + isResubmitting: !isResubmitting }); if (gasPrice === null) { this.setState({ - gasPrice: `0x${ transaction.gasPrice.toString(16) }`, - gas: `0x${ transaction.gas.toString(16) }` + gasPrice: `0x${transaction.gasPrice.toString(16)}`, + gas: `0x${transaction.gas.toString(16)}` }); } }; + setGasPrice = el => { + this.setState({ + gasPrice: el.target.value + }); + }; + + setGas = el => { + this.setState({ + gas: el.target.value + }); + }; + sendTransaction = () => { const { transaction } = this.props; const { gasPrice, gas } = this.state; @@ -317,18 +327,20 @@ export class LocalTransaction extends BaseTransaction { const { details } = this.props; let state = { - 'pending': () => `In queue: Pending`, - 'future': () => `In queue: Future`, - 'mined': () => `Mined`, - 'dropped': () => `Dropped because of queue limit`, - 'invalid': () => `Transaction is invalid`, - 'rejected': () => `Rejected: ${ details.error }`, - 'replaced': () => `Replaced by ${ this.shortHash(details.hash) }`, + 'pending': () => 'In queue: Pending', + 'future': () => 'In queue: Future', + 'mined': () => 'Mined', + 'dropped': () => 'Dropped because of queue limit', + 'invalid': () => 'Transaction is invalid', + 'rejected': () => `Rejected: ${details.error}`, + 'replaced': () => `Replaced by ${this.shortHash(details.hash)}` }[this.props.status]; return state ? state() : 'unknown'; } + // TODO [ToDr] Gas Price / Gas selection is not needed + // when signer supports gasPrice/gas tunning. renderResubmit () { const { transaction } = this.props; const { gasPrice, gas } = this.state; @@ -350,12 +362,12 @@ export class LocalTransaction extends BaseTransaction { this.setState({ gasPrice: el.target.value }) } + onChange={ this.setGasPrice } /> this.setState({ gas: el.target.value }) } + onChange={ this.setGas } /> From 45c7a285856609d5845f1e99311c48d2f269eb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:13:59 +0100 Subject: [PATCH 11/21] Fixing JS tests --- .../localtx/Transaction/transaction.spec.js | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 1fe78124c..5a9e1a8e9 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -19,15 +19,38 @@ import { shallow } from 'enzyme'; import '../../../environment/tests'; -import Transaction from './transaction'; +import BigNumber from 'bignumber.js'; +import { Transaction, LocalTransaction } from './transaction'; describe('localtx/Transaction', () => { describe('rendering', () => { it('renders without crashing', () => { + const transaction = { + hash: '0x1234567890', + nonce: 15, + gasPrice: new BigNumber(10), + gas: new BigNumber(10) + }; const rendered = shallow( + ); + + expect(rendered).to.be.defined; + }); + }); +}); + +describe('localtx/LocalTransaction', () => { + describe('rendering', () => { + it('renders without crashing', () => { + const rendered = shallow( + ); From afef5b5316dab3445aaefd1991e95a8042a3da15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 17 Nov 2016 14:30:53 +0100 Subject: [PATCH 12/21] Mocking API for tests --- js/src/dapps/localtx/Transaction/transaction.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 5a9e1a8e9..5e9c39147 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -18,6 +18,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import '../../../environment/tests'; +import EthApi from '../../../api'; + +// Mock API for tests +import * as Api from '../parity'; +Api.api = { + util: EthApi.prototype.util +}; import BigNumber from 'bignumber.js'; import { Transaction, LocalTransaction } from './transaction'; From 306ee068f54acf8c375fb9d11f7f61f63336571a Mon Sep 17 00:00:00 2001 From: sandakersmann Date: Sat, 19 Nov 2016 19:22:26 +0100 Subject: [PATCH 13/21] Add simple one-line installer to README.md Add simple one-line installer for Mac and Ubuntu to README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index fc5cd9762..65c374413 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,14 @@ $ cargo build --release This will produce an executable in the `./target/release` subdirectory. +---- + +## Simple one-line installer for Mac and Ubuntu + +```bash +bash <(curl https://get.parity.io -Lk) +``` + ## Start Parity ### Manually To start Parity manually, just run From ab4941b5ee5a8345c9966ee954fda58bbf6f3d09 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 13:28:04 +0000 Subject: [PATCH 14/21] [ci skip] js-precompiled 20161120-132620 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dafac3d7a..78078fdce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#427319583ccde288ba26728c14384392ddbba93d" +source = "git+https://github.com/ethcore/js-precompiled.git#27560245f25577aa64bb2496a8d77b47c3326a10" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 9a45fbf18..f73599b28 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.54", + "version": "0.2.55", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 9832be395f73e89338a087f9b974be0d132cddbc Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 13:35:00 +0000 Subject: [PATCH 15/21] [ci skip] js-precompiled 20161120-133314 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78078fdce..4a209e009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#27560245f25577aa64bb2496a8d77b47c3326a10" +source = "git+https://github.com/ethcore/js-precompiled.git#20e50a56709fbe3e39d0fea6cf31f48bd1da4bff" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index f73599b28..d4ee6fe8e 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.55", + "version": "0.2.56", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From efd1d9bd0e9ad10184e713b2fb191256c61d1a92 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Sun, 20 Nov 2016 16:38:45 +0100 Subject: [PATCH 16/21] Ropsten network (#3539) * Ropsten network * Sorted premine * Comas * Removed trailing coma --- ethcore/res/ethereum/ropsten.json | 304 ++++++++++++++++++++++++++++++ ethcore/src/ethereum/mod.rs | 3 + parity/params.rs | 4 + 3 files changed, 311 insertions(+) create mode 100644 ethcore/res/ethereum/ropsten.json diff --git a/ethcore/res/ethereum/ropsten.json b/ethcore/res/ethereum/ropsten.json new file mode 100644 index 000000000..c92676161 --- /dev/null +++ b/ethcore/res/ethereum/ropsten.json @@ -0,0 +1,304 @@ +{ + "name": "Ropsten", + "engine": { + "Ethash": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", + "homesteadTransition": 0, + "eip150Transition": 0, + "eip155Transition": 10, + "eip160Transition": 10, + "eip161abcTransition": 10, + "eip161dTransition": 10 + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x3" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x100000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x3535353535353535353535353535353535353535353535353535353535353535", + "gasLimit": "0x1000000" + }, + "nodes": [ + "enode://a22f0977ce02653bf95e38730106356342df48b5222e2c2a1a6f9ef34769bf593bae9ca0a888cf60839edd52efc1b6e393c63a57d76f4c4fe14e641f1f9e637e@128.199.55.137:30303", + "enode://012239fccf3ff1d92b036983a430cb6705c6528c96c0354413f8854802138e5135c084ab36e7c54efb621c46728df8c3a6f4c1db9bb48a1330efe3f82f2dd7a6@52.169.94.142:30303" + ], + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "0", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "0", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "0", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000000": { "balance": "1" }, + "0000000000000000000000000000000000000005": { "balance": "1" }, + "0000000000000000000000000000000000000006": { "balance": "1" }, + "0000000000000000000000000000000000000007": { "balance": "1" }, + "0000000000000000000000000000000000000008": { "balance": "1" }, + "0000000000000000000000000000000000000009": { "balance": "1" }, + "000000000000000000000000000000000000000a": { "balance": "0" }, + "000000000000000000000000000000000000000b": { "balance": "0" }, + "000000000000000000000000000000000000000c": { "balance": "0" }, + "000000000000000000000000000000000000000d": { "balance": "0" }, + "000000000000000000000000000000000000000e": { "balance": "0" }, + "000000000000000000000000000000000000000f": { "balance": "0" }, + "0000000000000000000000000000000000000010": { "balance": "0" }, + "0000000000000000000000000000000000000011": { "balance": "0" }, + "0000000000000000000000000000000000000012": { "balance": "0" }, + "0000000000000000000000000000000000000013": { "balance": "0" }, + "0000000000000000000000000000000000000014": { "balance": "0" }, + "0000000000000000000000000000000000000015": { "balance": "0" }, + "0000000000000000000000000000000000000016": { "balance": "0" }, + "0000000000000000000000000000000000000017": { "balance": "0" }, + "0000000000000000000000000000000000000018": { "balance": "0" }, + "0000000000000000000000000000000000000019": { "balance": "0" }, + "000000000000000000000000000000000000001a": { "balance": "0" }, + "000000000000000000000000000000000000001b": { "balance": "0" }, + "000000000000000000000000000000000000001c": { "balance": "0" }, + "000000000000000000000000000000000000001d": { "balance": "0" }, + "000000000000000000000000000000000000001e": { "balance": "0" }, + "000000000000000000000000000000000000001f": { "balance": "0" }, + "0000000000000000000000000000000000000020": { "balance": "0" }, + "0000000000000000000000000000000000000021": { "balance": "0" }, + "0000000000000000000000000000000000000022": { "balance": "0" }, + "0000000000000000000000000000000000000023": { "balance": "0" }, + "0000000000000000000000000000000000000024": { "balance": "0" }, + "0000000000000000000000000000000000000025": { "balance": "0" }, + "0000000000000000000000000000000000000026": { "balance": "0" }, + "0000000000000000000000000000000000000027": { "balance": "0" }, + "0000000000000000000000000000000000000028": { "balance": "0" }, + "0000000000000000000000000000000000000029": { "balance": "0" }, + "000000000000000000000000000000000000002a": { "balance": "0" }, + "000000000000000000000000000000000000002b": { "balance": "0" }, + "000000000000000000000000000000000000002c": { "balance": "0" }, + "000000000000000000000000000000000000002d": { "balance": "0" }, + "000000000000000000000000000000000000002e": { "balance": "0" }, + "000000000000000000000000000000000000002f": { "balance": "0" }, + "0000000000000000000000000000000000000030": { "balance": "0" }, + "0000000000000000000000000000000000000031": { "balance": "0" }, + "0000000000000000000000000000000000000032": { "balance": "0" }, + "0000000000000000000000000000000000000033": { "balance": "0" }, + "0000000000000000000000000000000000000034": { "balance": "0" }, + "0000000000000000000000000000000000000035": { "balance": "0" }, + "0000000000000000000000000000000000000036": { "balance": "0" }, + "0000000000000000000000000000000000000037": { "balance": "0" }, + "0000000000000000000000000000000000000038": { "balance": "0" }, + "0000000000000000000000000000000000000039": { "balance": "0" }, + "000000000000000000000000000000000000003a": { "balance": "0" }, + "000000000000000000000000000000000000003b": { "balance": "0" }, + "000000000000000000000000000000000000003c": { "balance": "0" }, + "000000000000000000000000000000000000003d": { "balance": "0" }, + "000000000000000000000000000000000000003e": { "balance": "0" }, + "000000000000000000000000000000000000003f": { "balance": "0" }, + "0000000000000000000000000000000000000040": { "balance": "0" }, + "0000000000000000000000000000000000000041": { "balance": "0" }, + "0000000000000000000000000000000000000042": { "balance": "0" }, + "0000000000000000000000000000000000000043": { "balance": "0" }, + "0000000000000000000000000000000000000044": { "balance": "0" }, + "0000000000000000000000000000000000000045": { "balance": "0" }, + "0000000000000000000000000000000000000046": { "balance": "0" }, + "0000000000000000000000000000000000000047": { "balance": "0" }, + "0000000000000000000000000000000000000048": { "balance": "0" }, + "0000000000000000000000000000000000000049": { "balance": "0" }, + "000000000000000000000000000000000000004a": { "balance": "0" }, + "000000000000000000000000000000000000004b": { "balance": "0" }, + "000000000000000000000000000000000000004c": { "balance": "0" }, + "000000000000000000000000000000000000004d": { "balance": "0" }, + "000000000000000000000000000000000000004e": { "balance": "0" }, + "000000000000000000000000000000000000004f": { "balance": "0" }, + "0000000000000000000000000000000000000050": { "balance": "0" }, + "0000000000000000000000000000000000000051": { "balance": "0" }, + "0000000000000000000000000000000000000052": { "balance": "0" }, + "0000000000000000000000000000000000000053": { "balance": "0" }, + "0000000000000000000000000000000000000054": { "balance": "0" }, + "0000000000000000000000000000000000000055": { "balance": "0" }, + "0000000000000000000000000000000000000056": { "balance": "0" }, + "0000000000000000000000000000000000000057": { "balance": "0" }, + "0000000000000000000000000000000000000058": { "balance": "0" }, + "0000000000000000000000000000000000000059": { "balance": "0" }, + "000000000000000000000000000000000000005a": { "balance": "0" }, + "000000000000000000000000000000000000005b": { "balance": "0" }, + "000000000000000000000000000000000000005c": { "balance": "0" }, + "000000000000000000000000000000000000005d": { "balance": "0" }, + "000000000000000000000000000000000000005e": { "balance": "0" }, + "000000000000000000000000000000000000005f": { "balance": "0" }, + "0000000000000000000000000000000000000060": { "balance": "0" }, + "0000000000000000000000000000000000000061": { "balance": "0" }, + "0000000000000000000000000000000000000062": { "balance": "0" }, + "0000000000000000000000000000000000000063": { "balance": "0" }, + "0000000000000000000000000000000000000064": { "balance": "0" }, + "0000000000000000000000000000000000000065": { "balance": "0" }, + "0000000000000000000000000000000000000066": { "balance": "0" }, + "0000000000000000000000000000000000000067": { "balance": "0" }, + "0000000000000000000000000000000000000068": { "balance": "0" }, + "0000000000000000000000000000000000000069": { "balance": "0" }, + "000000000000000000000000000000000000006a": { "balance": "0" }, + "000000000000000000000000000000000000006b": { "balance": "0" }, + "000000000000000000000000000000000000006c": { "balance": "0" }, + "000000000000000000000000000000000000006d": { "balance": "0" }, + "000000000000000000000000000000000000006e": { "balance": "0" }, + "000000000000000000000000000000000000006f": { "balance": "0" }, + "0000000000000000000000000000000000000070": { "balance": "0" }, + "0000000000000000000000000000000000000071": { "balance": "0" }, + "0000000000000000000000000000000000000072": { "balance": "0" }, + "0000000000000000000000000000000000000073": { "balance": "0" }, + "0000000000000000000000000000000000000074": { "balance": "0" }, + "0000000000000000000000000000000000000075": { "balance": "0" }, + "0000000000000000000000000000000000000076": { "balance": "0" }, + "0000000000000000000000000000000000000077": { "balance": "0" }, + "0000000000000000000000000000000000000078": { "balance": "0" }, + "0000000000000000000000000000000000000079": { "balance": "0" }, + "000000000000000000000000000000000000007a": { "balance": "0" }, + "000000000000000000000000000000000000007b": { "balance": "0" }, + "000000000000000000000000000000000000007c": { "balance": "0" }, + "000000000000000000000000000000000000007d": { "balance": "0" }, + "000000000000000000000000000000000000007e": { "balance": "0" }, + "000000000000000000000000000000000000007f": { "balance": "0" }, + "0000000000000000000000000000000000000080": { "balance": "0" }, + "0000000000000000000000000000000000000081": { "balance": "0" }, + "0000000000000000000000000000000000000082": { "balance": "0" }, + "0000000000000000000000000000000000000083": { "balance": "0" }, + "0000000000000000000000000000000000000084": { "balance": "0" }, + "0000000000000000000000000000000000000085": { "balance": "0" }, + "0000000000000000000000000000000000000086": { "balance": "0" }, + "0000000000000000000000000000000000000087": { "balance": "0" }, + "0000000000000000000000000000000000000088": { "balance": "0" }, + "0000000000000000000000000000000000000089": { "balance": "0" }, + "000000000000000000000000000000000000008a": { "balance": "0" }, + "000000000000000000000000000000000000008b": { "balance": "0" }, + "000000000000000000000000000000000000008c": { "balance": "0" }, + "000000000000000000000000000000000000008d": { "balance": "0" }, + "000000000000000000000000000000000000008e": { "balance": "0" }, + "000000000000000000000000000000000000008f": { "balance": "0" }, + "0000000000000000000000000000000000000090": { "balance": "0" }, + "0000000000000000000000000000000000000091": { "balance": "0" }, + "0000000000000000000000000000000000000092": { "balance": "0" }, + "0000000000000000000000000000000000000093": { "balance": "0" }, + "0000000000000000000000000000000000000094": { "balance": "0" }, + "0000000000000000000000000000000000000095": { "balance": "0" }, + "0000000000000000000000000000000000000096": { "balance": "0" }, + "0000000000000000000000000000000000000097": { "balance": "0" }, + "0000000000000000000000000000000000000098": { "balance": "0" }, + "0000000000000000000000000000000000000099": { "balance": "0" }, + "000000000000000000000000000000000000009a": { "balance": "0" }, + "000000000000000000000000000000000000009b": { "balance": "0" }, + "000000000000000000000000000000000000009c": { "balance": "0" }, + "000000000000000000000000000000000000009d": { "balance": "0" }, + "000000000000000000000000000000000000009e": { "balance": "0" }, + "000000000000000000000000000000000000009f": { "balance": "0" }, + "00000000000000000000000000000000000000a0": { "balance": "0" }, + "00000000000000000000000000000000000000a1": { "balance": "0" }, + "00000000000000000000000000000000000000a2": { "balance": "0" }, + "00000000000000000000000000000000000000a3": { "balance": "0" }, + "00000000000000000000000000000000000000a4": { "balance": "0" }, + "00000000000000000000000000000000000000a5": { "balance": "0" }, + "00000000000000000000000000000000000000a6": { "balance": "0" }, + "00000000000000000000000000000000000000a7": { "balance": "0" }, + "00000000000000000000000000000000000000a8": { "balance": "0" }, + "00000000000000000000000000000000000000a9": { "balance": "0" }, + "00000000000000000000000000000000000000aa": { "balance": "0" }, + "00000000000000000000000000000000000000ab": { "balance": "0" }, + "00000000000000000000000000000000000000ac": { "balance": "0" }, + "00000000000000000000000000000000000000ad": { "balance": "0" }, + "00000000000000000000000000000000000000ae": { "balance": "0" }, + "00000000000000000000000000000000000000af": { "balance": "0" }, + "00000000000000000000000000000000000000b0": { "balance": "0" }, + "00000000000000000000000000000000000000b1": { "balance": "0" }, + "00000000000000000000000000000000000000b2": { "balance": "0" }, + "00000000000000000000000000000000000000b3": { "balance": "0" }, + "00000000000000000000000000000000000000b4": { "balance": "0" }, + "00000000000000000000000000000000000000b5": { "balance": "0" }, + "00000000000000000000000000000000000000b6": { "balance": "0" }, + "00000000000000000000000000000000000000b7": { "balance": "0" }, + "00000000000000000000000000000000000000b8": { "balance": "0" }, + "00000000000000000000000000000000000000b9": { "balance": "0" }, + "00000000000000000000000000000000000000ba": { "balance": "0" }, + "00000000000000000000000000000000000000bb": { "balance": "0" }, + "00000000000000000000000000000000000000bc": { "balance": "0" }, + "00000000000000000000000000000000000000bd": { "balance": "0" }, + "00000000000000000000000000000000000000be": { "balance": "0" }, + "00000000000000000000000000000000000000bf": { "balance": "0" }, + "00000000000000000000000000000000000000c0": { "balance": "0" }, + "00000000000000000000000000000000000000c1": { "balance": "0" }, + "00000000000000000000000000000000000000c2": { "balance": "0" }, + "00000000000000000000000000000000000000c3": { "balance": "0" }, + "00000000000000000000000000000000000000c4": { "balance": "0" }, + "00000000000000000000000000000000000000c5": { "balance": "0" }, + "00000000000000000000000000000000000000c6": { "balance": "0" }, + "00000000000000000000000000000000000000c7": { "balance": "0" }, + "00000000000000000000000000000000000000c8": { "balance": "0" }, + "00000000000000000000000000000000000000c9": { "balance": "0" }, + "00000000000000000000000000000000000000ca": { "balance": "0" }, + "00000000000000000000000000000000000000cb": { "balance": "0" }, + "00000000000000000000000000000000000000cc": { "balance": "0" }, + "00000000000000000000000000000000000000cd": { "balance": "0" }, + "00000000000000000000000000000000000000ce": { "balance": "0" }, + "00000000000000000000000000000000000000cf": { "balance": "0" }, + "00000000000000000000000000000000000000d0": { "balance": "0" }, + "00000000000000000000000000000000000000d1": { "balance": "0" }, + "00000000000000000000000000000000000000d2": { "balance": "0" }, + "00000000000000000000000000000000000000d3": { "balance": "0" }, + "00000000000000000000000000000000000000d4": { "balance": "0" }, + "00000000000000000000000000000000000000d5": { "balance": "0" }, + "00000000000000000000000000000000000000d6": { "balance": "0" }, + "00000000000000000000000000000000000000d7": { "balance": "0" }, + "00000000000000000000000000000000000000d8": { "balance": "0" }, + "00000000000000000000000000000000000000d9": { "balance": "0" }, + "00000000000000000000000000000000000000da": { "balance": "0" }, + "00000000000000000000000000000000000000db": { "balance": "0" }, + "00000000000000000000000000000000000000dc": { "balance": "0" }, + "00000000000000000000000000000000000000dd": { "balance": "0" }, + "00000000000000000000000000000000000000de": { "balance": "0" }, + "00000000000000000000000000000000000000df": { "balance": "0" }, + "00000000000000000000000000000000000000e0": { "balance": "0" }, + "00000000000000000000000000000000000000e1": { "balance": "0" }, + "00000000000000000000000000000000000000e2": { "balance": "0" }, + "00000000000000000000000000000000000000e3": { "balance": "0" }, + "00000000000000000000000000000000000000e4": { "balance": "0" }, + "00000000000000000000000000000000000000e5": { "balance": "0" }, + "00000000000000000000000000000000000000e6": { "balance": "0" }, + "00000000000000000000000000000000000000e7": { "balance": "0" }, + "00000000000000000000000000000000000000e8": { "balance": "0" }, + "00000000000000000000000000000000000000e9": { "balance": "0" }, + "00000000000000000000000000000000000000ea": { "balance": "0" }, + "00000000000000000000000000000000000000eb": { "balance": "0" }, + "00000000000000000000000000000000000000ec": { "balance": "0" }, + "00000000000000000000000000000000000000ed": { "balance": "0" }, + "00000000000000000000000000000000000000ee": { "balance": "0" }, + "00000000000000000000000000000000000000ef": { "balance": "0" }, + "00000000000000000000000000000000000000f0": { "balance": "0" }, + "00000000000000000000000000000000000000f1": { "balance": "0" }, + "00000000000000000000000000000000000000f2": { "balance": "0" }, + "00000000000000000000000000000000000000f3": { "balance": "0" }, + "00000000000000000000000000000000000000f4": { "balance": "0" }, + "00000000000000000000000000000000000000f5": { "balance": "0" }, + "00000000000000000000000000000000000000f6": { "balance": "0" }, + "00000000000000000000000000000000000000f7": { "balance": "0" }, + "00000000000000000000000000000000000000f8": { "balance": "0" }, + "00000000000000000000000000000000000000f9": { "balance": "0" }, + "00000000000000000000000000000000000000fa": { "balance": "0" }, + "00000000000000000000000000000000000000fb": { "balance": "0" }, + "00000000000000000000000000000000000000fc": { "balance": "0" }, + "00000000000000000000000000000000000000fd": { "balance": "0" }, + "00000000000000000000000000000000000000fe": { "balance": "0" }, + "00000000000000000000000000000000000000ff": { "balance": "0" }, + "874b54a8bd152966d63f706bae1ffeb0411921e5": { "balance": "1000000000000000000000000000000" } + } +} diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 253a12372..e236924ad 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -63,6 +63,9 @@ pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/t /// Create a new Frontier main net chain spec without genesis accounts. pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } +/// Create a new Ropsten chain spec. +pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } + /// Create a new Morden chain spec. pub fn new_morden() -> Spec { load(include_bytes!("../../res/ethereum/morden.json")) } diff --git a/parity/params.rs b/parity/params.rs index 54e08da32..8af70b91d 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -28,6 +28,7 @@ use user_defaults::UserDefaults; pub enum SpecType { Mainnet, Testnet, + Ropsten, Olympic, Classic, Expanse, @@ -49,6 +50,7 @@ impl str::FromStr for SpecType { "frontier" | "homestead" | "mainnet" => SpecType::Mainnet, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, "morden" | "testnet" => SpecType::Testnet, + "ropsten" => SpecType::Ropsten, "olympic" => SpecType::Olympic, "expanse" => SpecType::Expanse, "dev" => SpecType::Dev, @@ -63,6 +65,7 @@ impl SpecType { match *self { SpecType::Mainnet => Ok(ethereum::new_frontier()), SpecType::Testnet => Ok(ethereum::new_morden()), + SpecType::Ropsten => Ok(ethereum::new_ropsten()), SpecType::Olympic => Ok(ethereum::new_olympic()), SpecType::Classic => Ok(ethereum::new_classic()), SpecType::Expanse => Ok(ethereum::new_expanse()), @@ -285,6 +288,7 @@ mod tests { assert_eq!(SpecType::Mainnet, "mainnet".parse().unwrap()); assert_eq!(SpecType::Testnet, "testnet".parse().unwrap()); assert_eq!(SpecType::Testnet, "morden".parse().unwrap()); + assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap()); assert_eq!(SpecType::Olympic, "olympic".parse().unwrap()); } From 92a34d9dd892cf53e2cc5b244e8c74fc31580711 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 20 Nov 2016 16:06:52 +0000 Subject: [PATCH 17/21] [ci skip] js-precompiled 20161120-160026 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a209e009..bffde3803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#20e50a56709fbe3e39d0fea6cf31f48bd1da4bff" +source = "git+https://github.com/ethcore/js-precompiled.git#2f78566b69c96e21ca1799cbf04d7a4d510c6240" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index d4ee6fe8e..d4c418f37 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.56", + "version": "0.2.57", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 845bc52e36003e2ad4553f72b6c547c7a105a89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 17:40:28 +0100 Subject: [PATCH 18/21] Moving contract resolver to separate crate --- Cargo.lock | 13 +++++++++- dapps/Cargo.toml | 4 +-- dapps/src/apps/fetcher.rs | 7 +++--- dapps/src/apps/mod.rs | 1 - dapps/src/lib.rs | 7 +++--- dapps/src/tests/helpers.rs | 2 +- ethcore/hash-fetch/Cargo.toml | 14 +++++++++++ .../hash-fetch/res}/registrar.json | 0 .../hash-fetch/res}/urlhint.json | 0 ethcore/hash-fetch/src/lib.rs | 25 +++++++++++++++++++ .../hash-fetch/src}/urlhint.rs | 4 +-- 11 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 ethcore/hash-fetch/Cargo.toml rename {dapps/src/apps => ethcore/hash-fetch/res}/registrar.json (100%) rename {dapps/src/apps => ethcore/hash-fetch/res}/urlhint.json (100%) create mode 100644 ethcore/hash-fetch/src/lib.rs rename {dapps/src/apps => ethcore/hash-fetch/src}/urlhint.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 4a209e009..a590d479c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,8 +334,8 @@ version = "1.5.0" dependencies = [ "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", + "ethcore-hash-fetch 1.5.0", "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", "fetch 0.1.0", @@ -366,6 +366,17 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-hash-fetch" +version = "1.5.0" +dependencies = [ + "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-util 1.5.0", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-io" version = "1.5.0" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index f6e9d102d..15e537820 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -20,20 +20,20 @@ url = "1.0" rustc-serialize = "0.3" serde = "0.8" serde_json = "0.8" -ethabi = "0.2.2" linked-hash-map = "0.3" parity-dapps-glue = "1.4" mime = "0.2" +mime_guess = "1.6.1" time = "0.1.35" serde_macros = { version = "0.8", optional = true } zip = { version = "0.1", default-features = false } ethcore-devtools = { path = "../devtools" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } +ethcore-hash-fetch = { path = "../ethcore/hash-fetch" } fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } -mime_guess = { version = "1.6.1" } clippy = { version = "0.0.96", optional = true} [build-dependencies] diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 2c1414201..e7f36d144 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -24,6 +24,7 @@ use std::io::{self, Read, Write}; use std::path::PathBuf; use std::sync::Arc; use rustc_serialize::hex::FromHex; +use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult}; use hyper; use hyper::status::StatusCode; @@ -37,7 +38,6 @@ use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator}; use endpoint::{Endpoint, EndpointPath, Handler}; use apps::cache::{ContentCache, ContentStatus}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; -use apps::urlhint::{URLHintContract, URLHint, URLHintResult}; /// Limit of cached dapps/content const MAX_CACHED_DAPPS: usize = 20; @@ -402,10 +402,11 @@ mod tests { use std::env; use std::sync::Arc; use util::Bytes; + use hash_fetch::urlhint::{URLHint, URLHintResult}; + + use apps::cache::ContentStatus; use endpoint::EndpointInfo; use page::LocalPageEndpoint; - use apps::cache::ContentStatus; - use apps::urlhint::{URLHint, URLHintResult}; use super::ContentFetcher; struct FakeResolver; diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 3cb0d8256..4c9270aa5 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -21,7 +21,6 @@ use parity_dapps::WebApp; mod cache; mod fs; -pub mod urlhint; pub mod fetcher; pub mod manifest; diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 2c9fa33d1..7c7ea0a86 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -51,13 +51,13 @@ extern crate serde; extern crate serde_json; extern crate zip; extern crate rand; -extern crate ethabi; extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate mime_guess; extern crate rustc_serialize; extern crate ethcore_rpc; extern crate ethcore_util as util; +extern crate ethcore_hash_fetch as hash_fetch; extern crate linked_hash_map; extern crate fetch; extern crate parity_dapps_glue as parity_dapps; @@ -84,12 +84,11 @@ mod url; #[cfg(test)] mod tests; -pub use self::apps::urlhint::ContractClient; - use std::sync::{Arc, Mutex}; use std::net::SocketAddr; use std::collections::HashMap; +use hash_fetch::urlhint::ContractClient; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use ethcore_rpc::Extendable; @@ -219,7 +218,7 @@ impl Server { ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(hash_fetch::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); let cors_domains = Self::cors_domains(signer_address.clone()); diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index f7c9e8ba6..66bf0f8eb 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -22,7 +22,7 @@ use env_logger::LogBuilder; use ServerBuilder; use Server; -use apps::urlhint::ContractClient; +use hash_fetch::urlhint::ContractClient; use util::{Bytes, Address, Mutex, ToPretty}; use devtools::http_client; diff --git a/ethcore/hash-fetch/Cargo.toml b/ethcore/hash-fetch/Cargo.toml new file mode 100644 index 000000000..f1a65bfc8 --- /dev/null +++ b/ethcore/hash-fetch/Cargo.toml @@ -0,0 +1,14 @@ +[package] +description = "Fetching hash-addressed content." +homepage = "https://ethcore.io" +license = "GPL-3.0" +name = "ethcore-hash-fetch" +version = "1.5.0" +authors = ["Ethcore "] + +[dependencies] +log = "0.3" +rustc-serialize = "0.3" +ethabi = "0.2.2" +mime_guess = "1.6.1" +ethcore-util = { path = "../../util" } diff --git a/dapps/src/apps/registrar.json b/ethcore/hash-fetch/res/registrar.json similarity index 100% rename from dapps/src/apps/registrar.json rename to ethcore/hash-fetch/res/registrar.json diff --git a/dapps/src/apps/urlhint.json b/ethcore/hash-fetch/res/urlhint.json similarity index 100% rename from dapps/src/apps/urlhint.json rename to ethcore/hash-fetch/res/urlhint.json diff --git a/ethcore/hash-fetch/src/lib.rs b/ethcore/hash-fetch/src/lib.rs new file mode 100644 index 000000000..ac613a558 --- /dev/null +++ b/ethcore/hash-fetch/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2015, 2016 Ethcore (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 . + +#[macro_use] +extern crate log; +extern crate rustc_serialize; +extern crate mime_guess; +extern crate ethabi; +extern crate ethcore_util as util; + +pub mod urlhint; + diff --git a/dapps/src/apps/urlhint.rs b/ethcore/hash-fetch/src/urlhint.rs similarity index 98% rename from dapps/src/apps/urlhint.rs rename to ethcore/hash-fetch/src/urlhint.rs index 27769d07a..b7f905144 100644 --- a/dapps/src/apps/urlhint.rs +++ b/ethcore/hash-fetch/src/urlhint.rs @@ -92,8 +92,8 @@ pub struct URLHintContract { impl URLHintContract { pub fn new(client: Arc) -> Self { - let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI"); - let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI"); + let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); + let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); URLHintContract { urlhint: Contract::new(urlhint), From cc8a9d410b235a17c59ebfc9067c56b53dac56b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 18:30:54 +0100 Subject: [PATCH 19/21] Adding fetch API to the crate --- Cargo.lock | 1 + ethcore/hash-fetch/Cargo.toml | 1 + ethcore/hash-fetch/src/client.rs | 114 ++++++++++++++++++++++++++++++ ethcore/hash-fetch/src/lib.rs | 8 +++ ethcore/hash-fetch/src/urlhint.rs | 48 ++++++++----- util/bigint/src/uint.rs | 1 + util/fetch/src/lib.rs | 2 +- 7 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 ethcore/hash-fetch/src/client.rs diff --git a/Cargo.lock b/Cargo.lock index a590d479c..85aac6e54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,6 +372,7 @@ version = "1.5.0" dependencies = [ "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-util 1.5.0", + "fetch 0.1.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (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/ethcore/hash-fetch/Cargo.toml b/ethcore/hash-fetch/Cargo.toml index f1a65bfc8..4fb724c08 100644 --- a/ethcore/hash-fetch/Cargo.toml +++ b/ethcore/hash-fetch/Cargo.toml @@ -11,4 +11,5 @@ log = "0.3" rustc-serialize = "0.3" ethabi = "0.2.2" mime_guess = "1.6.1" +fetch = { path = "../../util/fetch" } ethcore-util = { path = "../../util" } diff --git a/ethcore/hash-fetch/src/client.rs b/ethcore/hash-fetch/src/client.rs new file mode 100644 index 000000000..f5d19afa5 --- /dev/null +++ b/ethcore/hash-fetch/src/client.rs @@ -0,0 +1,114 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Hash-addressed content resolver & fetcher. + +use std::{io, fs}; +use std::sync::Arc; +use std::path::PathBuf; + +use util::{Mutex, H256, sha3}; +use fetch::{Fetch, FetchError, Client as FetchClient}; + +use urlhint::{ContractClient, URLHintContract, URLHint, URLHintResult}; + +/// API for fetching by hash. +pub trait HashFetch { + /// Fetch hash-addressed content. + /// Parameters: + /// 1. `hash` - content hash + /// 2. `on_done` - callback function invoked when the content is ready (or there was error during fetch) + /// + /// This function may fail immediately when fetch cannot be initialized or content cannot be resolved. + fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error>; +} + +/// Hash-fetching error. +#[derive(Debug)] +pub enum Error { + /// Hash could not be resolved to a valid content address. + NoResolution, + /// Downloaded content hash does not match. + HashMismatch { expected: H256, got: H256 }, + /// IO Error while validating hash. + IO(io::Error), + /// Error during fetch. + Fetch(FetchError), +} + +impl From for Error { + fn from(error: FetchError) -> Self { + Error::Fetch(error) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::IO(error) + } +} + +/// Default Hash-fetching client using on-chain contract to resolve hashes to URLs. +pub struct Client { + contract: URLHintContract, + fetch: Mutex, +} + +impl Client { + /// Creates new instance of the `Client` given on-chain contract client. + pub fn new(contract: Arc) -> Self { + Client { + contract: URLHintContract::new(contract), + fetch: Mutex::new(FetchClient::default()), + } + } +} + +impl HashFetch for Client { + fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error> { + debug!(target: "dapps", "Fetching: {:?}", hash); + + let url = try!( + self.contract.resolve(hash.to_vec()).map(|content| match content { + URLHintResult::Dapp(dapp) => { + dapp.url() + }, + URLHintResult::Content(content) => { + content.url + }, + }).ok_or_else(|| Error::NoResolution) + ); + + debug!(target: "dapps", "Resolved {:?} to {:?}. Fetching...", hash, url); + + self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| { + fn validate_hash(hash: H256, result: Result) -> Result { + let path = try!(result); + let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); + let content_hash = try!(sha3(&mut file_reader)); + + if content_hash != hash { + Err(Error::HashMismatch{ got: content_hash, expected: hash }) + } else { + Ok(path) + } + } + + debug!(target: "dapps", "Content fetched, validating hash ({:?})", hash); + on_done(validate_hash(hash, result)) + })).map_err(Into::into) + } +} diff --git a/ethcore/hash-fetch/src/lib.rs b/ethcore/hash-fetch/src/lib.rs index ac613a558..ffb74b260 100644 --- a/ethcore/hash-fetch/src/lib.rs +++ b/ethcore/hash-fetch/src/lib.rs @@ -14,12 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Hash-addressed content resolver & fetcher. + +#![warn(missing_docs)] + #[macro_use] extern crate log; extern crate rustc_serialize; extern crate mime_guess; extern crate ethabi; extern crate ethcore_util as util; +extern crate fetch; + +mod client; pub mod urlhint; +pub use client::{HashFetch, Client}; diff --git a/ethcore/hash-fetch/src/urlhint.rs b/ethcore/hash-fetch/src/urlhint.rs index b7f905144..9cbd13b1e 100644 --- a/ethcore/hash-fetch/src/urlhint.rs +++ b/ethcore/hash-fetch/src/urlhint.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! URLHint Contract + use std::fmt; use std::sync::Arc; use rustc_serialize::hex::ToHex; @@ -24,15 +26,30 @@ use util::{Address, Bytes, Hashable}; const COMMIT_LEN: usize = 20; +/// RAW Contract interface. +/// Should execute transaction using current blockchain state. +pub trait ContractClient: Send + Sync { + /// Get registrar address + fn registrar(&self) -> Result; + /// Call Contract + fn call(&self, address: Address, data: Bytes) -> Result; +} + +/// Github-hosted dapp. #[derive(Debug, PartialEq)] pub struct GithubApp { + /// Github Account pub account: String, + /// Github Repository pub repo: String, + /// Commit on Github pub commit: [u8;COMMIT_LEN], + /// Dapp owner address pub owner: Address, } impl GithubApp { + /// Returns URL of this Github-hosted dapp package. pub fn url(&self) -> String { // Since https fetcher doesn't support redirections we use direct link // format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex()) @@ -53,22 +70,17 @@ impl GithubApp { } } +/// Hash-Addressed Content #[derive(Debug, PartialEq)] pub struct Content { + /// URL of the content pub url: String, + /// MIME type of the content pub mime: String, + /// Content owner address pub owner: Address, } -/// RAW Contract interface. -/// Should execute transaction using current blockchain state. -pub trait ContractClient: Send + Sync { - /// Get registrar address - fn registrar(&self) -> Result; - /// Call Contract - fn call(&self, address: Address, data: Bytes) -> Result; -} - /// Result of resolving id to URL #[derive(Debug, PartialEq)] pub enum URLHintResult { @@ -84,6 +96,7 @@ pub trait URLHint { fn resolve(&self, id: Bytes) -> Option; } +/// `URLHintContract` API pub struct URLHintContract { urlhint: Contract, registrar: Contract, @@ -91,6 +104,7 @@ pub struct URLHintContract { } impl URLHintContract { + /// Creates new `URLHintContract` pub fn new(client: Arc) -> Self { let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); @@ -244,11 +258,6 @@ fn guess_mime_type(url: &str) -> Option { }) } -#[cfg(test)] -pub fn test_guess_mime_type(url: &str) -> Option { - guess_mime_type(url) -} - fn as_string(e: T) -> String { format!("{:?}", e) } @@ -260,6 +269,7 @@ mod tests { use rustc_serialize::hex::FromHex; use super::*; + use super::guess_mime_type; use util::{Bytes, Address, Mutex, ToPretty}; struct FakeRegistrar { @@ -390,10 +400,10 @@ mod tests { let url5 = "https://ethcore.io/parity.png"; - assert_eq!(test_guess_mime_type(url1), None); - assert_eq!(test_guess_mime_type(url2), Some("image/png".into())); - assert_eq!(test_guess_mime_type(url3), Some("image/png".into())); - assert_eq!(test_guess_mime_type(url4), Some("image/jpeg".into())); - assert_eq!(test_guess_mime_type(url5), Some("image/png".into())); + assert_eq!(guess_mime_type(url1), None); + assert_eq!(guess_mime_type(url2), Some("image/png".into())); + assert_eq!(guess_mime_type(url3), Some("image/png".into())); + assert_eq!(guess_mime_type(url4), Some("image/jpeg".into())); + assert_eq!(guess_mime_type(url5), Some("image/png".into())); } } diff --git a/util/bigint/src/uint.rs b/util/bigint/src/uint.rs index dab00537e..f4dd91140 100644 --- a/util/bigint/src/uint.rs +++ b/util/bigint/src/uint.rs @@ -683,6 +683,7 @@ macro_rules! construct_uint { bytes[i] = (arr[pos] >> ((rev % 8) * 8)) as u8; } } + #[inline] fn exp10(n: usize) -> Self { match n { diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 7ab38604b..8ec9e0ddd 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -26,4 +26,4 @@ extern crate rand; pub mod client; pub mod fetch_file; -pub use self::client::{Client, Fetch, FetchError, FetchResult}; \ No newline at end of file +pub use self::client::{Client, Fetch, FetchError, FetchResult}; From 94328e97845aef1ced5078ebd9ab80b156fb8643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 20 Nov 2016 22:40:23 +0100 Subject: [PATCH 20/21] Fixing main --- Cargo.lock | 1 + Cargo.toml | 1 + parity/dapps.rs | 2 +- parity/main.rs | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 85aac6e54..609d02dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "ethcore 1.5.0", "ethcore-dapps 1.5.0", "ethcore-devtools 1.4.0", + "ethcore-hash-fetch 1.5.0", "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", diff --git a/Cargo.toml b/Cargo.toml index fe72d67ec..a8d7ba794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ ethcore-ipc-nano = { path = "ipc/nano" } ethcore-ipc = { path = "ipc/rpc" } ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-logger = { path = "logger" } +ethcore-hash-fetch = { path = "ethcore/hash-fetch" } rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/parity/dapps.rs b/parity/dapps.rs index 80f2f7060..16ae4dd98 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -110,7 +110,7 @@ mod server { use rpc_apis; use ethcore_rpc::is_major_importing; - use ethcore_dapps::ContractClient; + use hash_fetch::urlhint::ContractClient; pub use ethcore_dapps::Server as WebappServer; diff --git a/parity/main.rs b/parity/main.rs index 0fc88d8e7..274d29de2 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -42,6 +42,7 @@ extern crate ethcore_ipc_nano as nanoipc; extern crate serde; extern crate serde_json; extern crate rlp; +extern crate ethcore_hash_fetch as hash_fetch; extern crate json_ipc_server as jsonipc; From 21b2b4ac273137839c5c7dcb4919f193d60a16c6 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 21 Nov 2016 09:37:54 +0000 Subject: [PATCH 21/21] [ci skip] js-precompiled 20161121-093611 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69a964d11..02e45b49e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#2f78566b69c96e21ca1799cbf04d7a4d510c6240" +source = "git+https://github.com/ethcore/js-precompiled.git#587684374a12bf715151dd987a552a3d61e42972" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index d4c418f37..7f4157cad 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.57", + "version": "0.2.58", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ",