diff --git a/Cargo.lock b/Cargo.lock index 98a64d795..0a2480ac1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ dependencies = [ "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)", "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", - "parity-dapps-builtins 0.5.1 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)", + "parity-dapps-builtins 0.5.2 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)", "parity-dapps-status 0.5.0 (git+https://github.com/ethcore/parity-dapps-status-rs.git)", "parity-dapps-wallet 0.6.1 (git+https://github.com/ethcore/parity-dapps-wallet-rs.git)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -910,8 +910,8 @@ dependencies = [ [[package]] name = "parity-dapps-builtins" -version = "0.5.1" -source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#7408838e8ca3b57c6b0cf5da2e31e0e275959955" +version = "0.5.2" +source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#01af2091d5d70dfe0aecbfd96308f0ae79fc61e6" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 0476e9919..6aaceb50a 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -23,7 +23,7 @@ ethcore-util = { path = "../util" } parity-dapps = { git = "https://github.com/ethcore/parity-dapps-rs.git", version = "0.3" } # List of apps parity-dapps-status = { git = "https://github.com/ethcore/parity-dapps-status-rs.git", version = "0.5.0" } -parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtins-rs.git", version = "0.5.0" } +parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtins-rs.git", version = "0.5.2" } parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.6.0", optional = true } parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.4.0", optional = true } parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.3.0", optional = true } diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index d0628436d..2edbf87ae 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -108,6 +108,12 @@ impl Account { self.code_cache = code; } + /// Reset this account's code to the given code. + pub fn reset_code(&mut self, code: Bytes) { + self.code_hash = None; + self.init_code(code); + } + /// Set (and cache) the contents of the trie's storage at `key` to `value`. pub fn set_storage(&mut self, key: H256, value: H256) { self.storage_overlay.borrow_mut().insert(key, (Filth::Dirty, value)); @@ -336,6 +342,21 @@ mod tests { assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb"); } + #[test] + fn reset_code() { + let mut a = Account::new_contract(69.into(), 0.into()); + let mut db = MemoryDB::new(); + let mut db = AccountDBMut::new(&mut db, &Address::new()); + a.init_code(vec![0x55, 0x44, 0xffu8]); + assert_eq!(a.code_hash(), SHA3_EMPTY); + a.commit_code(&mut db); + assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb"); + a.reset_code(vec![0x55]); + assert_eq!(a.code_hash(), SHA3_EMPTY); + a.commit_code(&mut db); + assert_eq!(a.code_hash().hex(), "37bf2238b11b68cdc8382cece82651b59d3c3988873b6e0f33d79694aa45f1be"); + } + #[test] fn rlpio() { let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new()); @@ -348,7 +369,6 @@ mod tests { #[test] fn new_account() { - let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new()); assert_eq!(a.rlp().to_hex(), "f8448045a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); assert_eq!(a.balance(), &U256::from(69u8)); @@ -359,7 +379,6 @@ mod tests { #[test] fn create_account() { - let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new()); assert_eq!(a.rlp().to_hex(), "f8448045a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1a437bfac..5d157b654 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -515,9 +515,6 @@ impl BlockChainClient for Client { ret } - fn vm_factory(&self) -> &EvmFactory { - &self.vm_factory - } fn block_header(&self, id: BlockID) -> Option { Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec())) @@ -829,6 +826,10 @@ impl MiningBlockChainClient for Client { open_block } + + fn vm_factory(&self) -> &EvmFactory { + &self.vm_factory + } } impl MayPanic for Client { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 2098d8a2f..bef814b4e 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -172,9 +172,6 @@ pub trait BlockChainClient : Sync + Send { // TODO: should be able to accept blockchain location for call. fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result; - /// Returns EvmFactory. - fn vm_factory(&self) -> &EvmFactory; - /// Returns traces matching given filter. fn filter_traces(&self, filter: TraceFilter) -> Option>; @@ -253,4 +250,7 @@ pub trait MiningBlockChainClient : BlockChainClient { /// Returns OpenBlock prepared for closing. fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock; + + /// Returns EvmFactory. + fn vm_factory(&self) -> &EvmFactory; } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a7f508a51..ed1f10e09 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -244,6 +244,10 @@ impl MiningBlockChainClient for TestBlockChainClient { fn prepare_open_block(&self, _author: Address, _gas_range_target: (U256, U256), _extra_data: Bytes) -> OpenBlock { unimplemented!(); } + + fn vm_factory(&self) -> &EvmFactory { + unimplemented!(); + } } impl BlockChainClient for TestBlockChainClient { @@ -463,10 +467,6 @@ impl BlockChainClient for TestBlockChainClient { } } - fn vm_factory(&self) -> &EvmFactory { - unimplemented!(); - } - fn filter_traces(&self, _filter: TraceFilter) -> Option> { unimplemented!(); } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7171e734b..f99fe5fac 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -16,6 +16,7 @@ use rayon::prelude::*; use std::sync::atomic::AtomicBool; +use std::time::{Instant, Duration}; use util::*; use account_provider::AccountProvider; @@ -53,12 +54,16 @@ pub struct MinerOptions { pub reseal_on_external_tx: bool, /// Reseal on receipt of new local transactions. pub reseal_on_own_tx: bool, + /// Minimum period between transaction-inspired reseals. + pub reseal_min_period: Duration, /// Maximum amount of gas to bother considering for block insertion. pub tx_gas_limit: U256, /// Maximum size of the transaction queue. pub tx_queue_size: usize, /// Whether we should fallback to providing all the queue's transactions or just pending. pub pending_set: PendingSet, + /// How many historical work packages can we store before running out? + pub work_queue_size: usize, } impl Default for MinerOptions { @@ -71,6 +76,8 @@ impl Default for MinerOptions { tx_gas_limit: !U256::zero(), tx_queue_size: 1024, pending_set: PendingSet::AlwaysQueue, + reseal_min_period: Duration::from_secs(0), + work_queue_size: 20, } } } @@ -84,6 +91,7 @@ pub struct Miner { // for sealing... options: MinerOptions, sealing_enabled: AtomicBool, + next_allowed_reseal: Mutex, sealing_block_last_request: Mutex, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -101,8 +109,9 @@ impl Miner { transaction_queue: Mutex::new(TransactionQueue::new()), options: Default::default(), sealing_enabled: AtomicBool::new(false), + next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(UsingQueue::new(5)), + sealing_work: Mutex::new(UsingQueue::new(20)), gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), @@ -118,12 +127,14 @@ impl Miner { Arc::new(Miner { transaction_queue: Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit)), sealing_enabled: AtomicBool::new(options.force_sealing || !options.new_work_notify.is_empty()), - options: options, + next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(UsingQueue::new(5)), + sealing_work: Mutex::new(UsingQueue::new(options.work_queue_size)), + options: options, gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), + options: options, accounts: accounts, spec: spec, work_poster: work_poster, @@ -282,6 +293,9 @@ impl Miner { // Return if !have_work } + + /// Are we allowed to do a non-mandatory reseal? + fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock().unwrap() } } const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; @@ -452,7 +466,7 @@ impl MinerService for Miner { .map(|tx| transaction_queue.add(tx, &fetch_account, TransactionOrigin::External)) .collect() }; - if !results.is_empty() && self.options.reseal_on_external_tx { + if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() { self.update_sealing(chain); } results @@ -487,7 +501,7 @@ impl MinerService for Miner { import }; - if imported.is_ok() && self.options.reseal_on_own_tx { + if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing let prepared = self.enable_and_prepare_sealing(chain); @@ -580,6 +594,7 @@ impl MinerService for Miner { self.sealing_enabled.store(false, atomic::Ordering::Relaxed); self.sealing_work.lock().unwrap().reset(); } else { + *self.next_allowed_reseal.lock().unwrap() = Instant::now() + self.options.reseal_min_period; self.prepare_sealing(chain); } } diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 17ca18272..7f5b59c38 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -441,7 +441,7 @@ impl TransactionQueue { trace!(target: "miner", "Importing: {:?}", tx.hash()); - if tx.gas_price < self.minimal_gas_price { + if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "miner", "Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})", tx.hash(), @@ -1055,7 +1055,7 @@ mod test { } #[test] - fn should_not_import_transaction_below_min_gas_price_threshold() { + fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { // given let mut txq = TransactionQueue::new(); let tx = new_tx(); @@ -1074,6 +1074,23 @@ mod test { assert_eq!(stats.future, 0); } + #[test] + fn should_import_transaction_below_min_gas_price_threshold_if_local() { + // given + let mut txq = TransactionQueue::new(); + let tx = new_tx(); + txq.set_minimal_gas_price(tx.gas_price + U256::one()); + + // when + let res = txq.add(tx, &default_nonce, TransactionOrigin::Local); + + // then + assert_eq!(res.unwrap(), TransactionImportResult::Current); + let stats = txq.status(); + assert_eq!(stats.pending, 1); + assert_eq!(stats.future, 0); + } + #[test] fn should_reject_incorectly_signed_transaction() { // given diff --git a/ethcore/src/miner/work_notify.rs b/ethcore/src/miner/work_notify.rs new file mode 100644 index 000000000..6144e2d3d --- /dev/null +++ b/ethcore/src/miner/work_notify.rs @@ -0,0 +1,105 @@ +// 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 . + +extern crate hyper; + +use hyper::header::ContentType; +use hyper::method::Method; +use hyper::client::{Request, Response, Client}; +use hyper::{Next}; +use hyper::net::HttpStream; +use ethash::SeedHashCompute; +use hyper::Url; +use util::*; +use ethereum::ethash::Ethash; + +pub struct WorkPoster { + urls: Vec, + client: Mutex>, + seed_compute: Mutex, +} + +impl WorkPoster { + pub fn new(urls: &[String]) -> Self { + let urls = urls.into_iter().filter_map(|u| { + match Url::parse(&u) { + Ok(url) => Some(url), + Err(e) => { + warn!("Error parsing URL {} : {}", u, e); + None + } + } + }).collect(); + let client = Client::::configure() + .keep_alive(false) + .build().expect("Error creating HTTP client"); + WorkPoster { + client: Mutex::new(client), + urls: urls, + seed_compute: Mutex::new(SeedHashCompute::new()), + } + } + + pub fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) { + // TODO: move this to engine + let target = Ethash::difficulty_to_boundary(&difficulty); + let seed_hash = &self.seed_compute.lock().unwrap().get_seedhash(number); + let seed_hash = H256::from_slice(&seed_hash[..]); + let body = format!(r#"{{ "result": ["0x{}","0x{}","0x{}","0x{:x}"] }}"#, + pow_hash.hex(), seed_hash.hex(), target.hex(), number); + let client = self.client.lock().unwrap(); + for u in &self.urls { + if let Err(e) = client.request(u.clone(), PostHandler { body: body.clone() }) { + warn!("Error sending HTTP notification to {} : {}", u, e); + } + } + } +} + +struct PostHandler { + body: String, +} + +impl hyper::client::Handler for PostHandler { + fn on_request(&mut self, request: &mut Request) -> Next { + request.set_method(Method::Post); + request.headers_mut().set(ContentType::json()); + Next::write() + } + + fn on_request_writable(&mut self, encoder: &mut hyper::Encoder) -> Next { + if let Err(e) = encoder.write_all(self.body.as_bytes()) { + trace!("Error posting work data: {}", e); + } + encoder.close(); + Next::read() + + } + + fn on_response(&mut self, _response: Response) -> Next { + Next::end() + } + + fn on_response_readable(&mut self, _decoder: &mut hyper::Decoder) -> Next { + Next::end() + } + + fn on_error(&mut self, err: hyper::Error) -> Next { + trace!("Error posting work data: {}", err); + Next::end() + } +} + diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index e7981abba..0c086ffc3 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -208,12 +208,17 @@ impl State { self.require(a, false).set_storage(key, value) } - /// Initialise the code of account `a` so that it is `value` for `key`. + /// Initialise the code of account `a` so that it is `code`. /// NOTE: Account should have been created with `new_contract`. pub fn init_code(&mut self, a: &Address, code: Bytes) { self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).init_code(code); } + /// Reset the code of account `a` so that it is `code`. + pub fn reset_code(&mut self, a: &Address, code: Bytes) { + self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).reset_code(code); + } + /// Execute a given transaction. /// This will change the state accordingly. pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, vm_factory: &EvmFactory, t: &SignedTransaction, tracing: bool) -> ApplyResult { diff --git a/parity/cli.rs b/parity/cli.rs index 752f6ce70..3c1eee8e8 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -137,6 +137,13 @@ Sealing/Mining Options: own - reseal only on a new local transaction; ext - reseal only on a new external transaction; all - reseal on all new transactions [default: all]. + --reseal-min-period MS Specify the minimum time between reseals from + incoming transactions. MS is time measured in + milliseconds [default: 2000]. + --work-queue-size ITEMS Specify the number of historical work packages + which are kept cached lest a solution is found for + them later. High values take more memory but result + in fewer unusable solutions [default: 20]. --tx-gas-limit GAS Apply a limit of GAS as the maximum amount of gas a single transaction may have for it to be mined. --relay-set SET Set of transactions to relay. SET may be: @@ -304,6 +311,8 @@ pub struct Args { pub flag_no_token: bool, pub flag_force_sealing: bool, pub flag_reseal_on_txs: String, + pub flag_reseal_min_period: u64, + pub flag_work_queue_size: usize, pub flag_tx_gas_limit: Option, pub flag_relay_set: String, pub flag_author: Option, diff --git a/parity/configuration.rs b/parity/configuration.rs index d708ef02c..0cfb7c44f 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -16,6 +16,7 @@ use std::env; use std::fs::File; +use std::time::Duration; use std::io::{BufRead, BufReader}; use std::net::{SocketAddr, IpAddr}; use std::path::PathBuf; @@ -108,6 +109,8 @@ impl Configuration { "lenient" => PendingSet::SealingOrElseQueue, x => die!("{}: Invalid value for --relay-set option. Use --help for more information.", x) }, + reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period), + work_queue_size: self.args.flag_work_queue_size, } } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index fdd2de8fa..1a6b1c398 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -165,7 +165,7 @@ fn transaction_error(error: EthcoreError) -> Error { "There is too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() }, InsufficientGasPrice { minimal, got } => { - format!("Transaction fee is to low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", minimal, got) + format!("Transaction fee is too low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", 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) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 88a12a74f..59d06e84a 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -17,6 +17,7 @@ //! rpc integration tests. use std::sync::Arc; use std::str::FromStr; +use std::time::Duration; use ethcore::client::{BlockChainClient, Client, ClientConfig}; use ethcore::ids::BlockID; @@ -57,6 +58,8 @@ fn miner_service(spec: Spec, accounts: Arc) -> Arc { tx_queue_size: 1024, tx_gas_limit: !U256::zero(), pending_set: PendingSet::SealingOrElseQueue, + reseal_min_period: Duration::from_secs(0), + work_queue_size: 50, }, spec, Some(accounts)