diff --git a/Cargo.lock b/Cargo.lock index 8e1e4e3f2d0..52bdee49431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,8 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -281,6 +283,7 @@ dependencies = [ "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", + "evmjit 1.4.0", "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)", @@ -622,6 +625,13 @@ dependencies = [ "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "evmjit" +version = "1.4.0" +dependencies = [ + "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fdlimit" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f52f0dc85ef..84edb6c1e4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.90", optional = true} ethcore-stratum = { path = "stratum" } +serde = "0.8.0" +serde_json = "0.8.0" [target.'cfg(windows)'.dependencies] winapi = "0.2" @@ -61,6 +63,7 @@ ui = ["dapps", "ethcore-signer/ui"] use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"] dapps = ["ethcore-dapps"] ipc = ["ethcore/ipc"] +jit = ["ethcore/jit"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] json-tests = ["ethcore/json-tests"] stratum = ["ipc"] diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a60cce01db2..445ec37f74b 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -170,7 +170,7 @@ impl Client { let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); - let tracedb = RwLock::new(try!(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone()))); + let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); let mut state_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); if state_db.is_empty() && try!(spec.ensure_db_good(state_db.as_hashdb_mut())) { @@ -687,7 +687,7 @@ impl snapshot::DatabaseRestore for Client { *state_db = journaldb::new(db.clone(), self.pruning, ::db::COL_STATE); *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); - *tracedb = try!(TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()).map_err(ClientError::from)); + *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); Ok(()) } } diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index bb70de6cdac..0146293dfde 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -18,7 +18,7 @@ use std::str::FromStr; pub use std::time::Duration; pub use block_queue::BlockQueueConfig; pub use blockchain::Config as BlockChainConfig; -pub use trace::{Config as TraceConfig, Switch}; +pub use trace::Config as TraceConfig; pub use evm::VMType; pub use verification::VerifierType; use util::{journaldb, CompactionProfile}; @@ -102,7 +102,7 @@ pub struct ClientConfig { /// State db compaction profile pub db_compaction: DatabaseCompactionProfile, /// Should db have WAL enabled? - pub db_wal: bool, + pub db_wal: bool, /// Operating mode pub mode: Mode, /// Type of block verifier used by client. diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 32582ddf212..a5ff89c478f 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -23,7 +23,7 @@ mod trace; mod client; pub use self::client::*; -pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockQueueConfig, BlockChainConfig, Switch, VMType}; +pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockQueueConfig, BlockChainConfig, VMType}; pub use self::error::Error; pub use types::ids::*; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; diff --git a/ethcore/src/evm/jit.rs b/ethcore/src/evm/jit.rs index 4f43d327b96..c62f87ab7f9 100644 --- a/ethcore/src/evm/jit.rs +++ b/ethcore/src/evm/jit.rs @@ -18,6 +18,7 @@ use common::*; use evmjit; use evm::{self, GasLeft}; +use types::executed::CallType; /// Should be used to convert jit types to ethcore trait FromJit: Sized { @@ -77,10 +78,11 @@ impl IntoJit for U256 { impl IntoJit for H256 { fn into_jit(self) -> evmjit::I256 { let mut ret = [0; 4]; - for i in 0..self.bytes().len() { - let rev = self.bytes().len() - 1 - i; + let len = self.len(); + for i in 0..len { + let rev = len - 1 - i; let pos = rev / 8; - ret[pos] += (self.bytes()[i] as u64) << ((rev % 8) * 8); + ret[pos] += (self[i] as u64) << ((rev % 8) * 8); } evmjit::I256 { words: ret } } @@ -206,6 +208,7 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { let sender_address = unsafe { Address::from_jit(&*sender_address) }; let receive_address = unsafe { Address::from_jit(&*receive_address) }; let code_address = unsafe { Address::from_jit(&*code_address) }; + // TODO Is it always safe in case of DELEGATE_CALL? let transfer_value = unsafe { U256::from_jit(&*transfer_value) }; let value = Some(transfer_value); @@ -239,6 +242,12 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { } } + // TODO [ToDr] Any way to detect DelegateCall? + let call_type = match is_callcode { + true => CallType::CallCode, + false => CallType::Call, + }; + match self.ext.call( &call_gas, &sender_address, @@ -246,7 +255,9 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { value, unsafe { slice::from_raw_parts(in_beg, in_size as usize) }, &code_address, - unsafe { slice::from_raw_parts_mut(out_beg, out_size as usize) }) { + unsafe { slice::from_raw_parts_mut(out_beg, out_size as usize) }, + call_type, + ) { evm::MessageCallResult::Success(gas_left) => unsafe { *io_gas = (gas + gas_left).low_u64(); true diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 73c1c9cf9bf..182234280c2 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -292,6 +292,7 @@ impl Miner { }; let mut invalid_transactions = HashSet::new(); + let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); // TODO: push new uncles, too. for tx in transactions { @@ -299,6 +300,12 @@ impl Miner { match open_block.push_transaction(tx, None) { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); + + // Penalize transaction if it's above current gas limit + if gas > gas_limit { + transactions_to_penalize.insert(hash); + } + // Exit early if gas left is smaller then min_tx_gas let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. if gas_limit - gas_used < min_tx_gas { @@ -334,6 +341,9 @@ impl Miner { for hash in invalid_transactions.into_iter() { queue.remove_invalid(&hash, &fetch_account); } + for hash in transactions_to_penalize { + queue.penalize(&hash); + } } (block, original_work_hash) } diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index af054aa9859..7db65eacbc3 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -134,6 +134,8 @@ struct TransactionOrder { hash: H256, /// Origin of the transaction origin: TransactionOrigin, + /// Penalties + penalties: usize, } @@ -144,6 +146,7 @@ impl TransactionOrder { gas_price: tx.transaction.gas_price, hash: tx.hash(), origin: tx.origin, + penalties: 0, } } @@ -151,6 +154,11 @@ impl TransactionOrder { self.nonce_height = nonce - base_nonce; self } + + fn penalize(mut self) -> Self { + self.penalties = self.penalties.saturating_add(1); + self + } } impl Eq for TransactionOrder {} @@ -167,6 +175,11 @@ impl PartialOrd for TransactionOrder { impl Ord for TransactionOrder { fn cmp(&self, b: &TransactionOrder) -> Ordering { + // First check number of penalties + if self.penalties != b.penalties { + 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); @@ -387,7 +400,7 @@ pub struct AccountDetails { } /// Transactions with `gas > (gas_limit + gas_limit * Factor(in percents))` are not imported to the queue. -const GAS_LIMIT_HYSTERESIS: usize = 10; // % +const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) % /// `TransactionQueue` implementation pub struct TransactionQueue { @@ -506,8 +519,6 @@ impl TransactionQueue { pub fn add(&mut self, tx: SignedTransaction, fetch_account: &T, origin: TransactionOrigin) -> Result where T: Fn(&Address) -> AccountDetails { - trace!(target: "txqueue", "Importing: {:?}", tx.hash()); - if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "txqueue", "Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})", @@ -593,6 +604,39 @@ impl TransactionQueue { assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); } + /// Penalize transactions from sender of transaction with given hash. + /// I.e. it should change the priority of the transaction in the queue. + /// + /// NOTE: We need to penalize all transactions from particular sender + /// to avoid breaking invariants in queue (ordered by nonces). + /// Consecutive transactions from this sender would fail otherwise (because of invalid nonce). + pub fn penalize(&mut self, transaction_hash: &H256) { + let transaction = match self.by_hash.get(transaction_hash) { + None => return, + Some(t) => t, + }; + let sender = transaction.sender(); + + // Penalize all transactions from this sender + let nonces_from_sender = match self.current.by_address.row(&sender) { + Some(row_map) => row_map.keys().cloned().collect::>(), + None => vec![], + }; + for k in nonces_from_sender { + let order = self.current.drop(&sender, &k).unwrap(); + self.current.insert(sender, k, order.penalize()); + } + // Same thing for future + let nonces_from_sender = match self.future.by_address.row(&sender) { + Some(row_map) => row_map.keys().cloned().collect::>(), + None => vec![], + }; + for k in nonces_from_sender { + let order = self.future.drop(&sender, &k).unwrap(); + self.current.insert(sender, k, order.penalize()); + } + } + /// Removes invalid transaction identified by hash from queue. /// Assumption is that this transaction nonce is not related to client nonce, /// so transactions left in queue are processed according to client nonce. @@ -764,6 +808,7 @@ impl TransactionQueue { let address = tx.sender(); let nonce = tx.nonce(); + let hash = tx.hash(); let next_nonce = self.last_nonces .get(&address) @@ -785,6 +830,9 @@ impl TransactionQueue { try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.future, &mut self.by_hash))); // Return an error if this transaction is not imported because of limit. try!(check_if_removed(&address, &nonce, self.future.enforce_limit(&mut self.by_hash))); + + debug!(target: "txqueue", "Importing transaction to future: {:?}", hash); + debug!(target: "txqueue", "status: {:?}", self.status()); return Ok(TransactionImportResult::Future); } try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.current, &mut self.by_hash))); @@ -811,7 +859,8 @@ impl TransactionQueue { // Trigger error if the transaction we are importing was removed. try!(check_if_removed(&address, &nonce, removed)); - trace!(target: "txqueue", "status: {:?}", self.status()); + debug!(target: "txqueue", "Imported transaction to current: {:?}", hash); + debug!(target: "txqueue", "status: {:?}", self.status()); Ok(TransactionImportResult::Current) } @@ -945,6 +994,17 @@ mod test { (tx1.sign(secret), tx2.sign(secret)) } + /// Returns two consecutive transactions, both with increased gas price + fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { + let gas = default_gas_price() + gas_price_increment; + let tx1 = new_unsigned_tx(default_nonce(), gas); + let tx2 = new_unsigned_tx(default_nonce() + 1.into(), gas); + + let keypair = Random.generate().unwrap(); + let secret = &keypair.secret(); + (tx1.sign(secret), tx2.sign(secret)) + } + fn new_tx_pair_default(nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { new_tx_pair(default_nonce(), default_gas_price(), nonce_increment, gas_price_increment) } @@ -1332,6 +1392,39 @@ mod test { assert_eq!(top.len(), 2); } + #[test] + fn should_penalize_transactions_from_sender() { + // given + let mut txq = TransactionQueue::new(); + // 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(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(txb.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).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 + let top = txq.top_transactions(); + assert_eq!(top[0], txa); + assert_eq!(top[1], txb); + assert_eq!(top[2], tx1); + assert_eq!(top[3], tx2); + assert_eq!(top.len(), 4); + } + #[test] fn should_return_pending_hashes() { // given diff --git a/ethcore/src/trace/config.rs b/ethcore/src/trace/config.rs index ff96cea74cf..9dab7524d99 100644 --- a/ethcore/src/trace/config.rs +++ b/ethcore/src/trace/config.rs @@ -15,57 +15,14 @@ // along with Parity. If not, see . //! Traces config. -use std::str::FromStr; use bloomchain::Config as BloomConfig; -use trace::Error; - -/// 3-value enum. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Switch { - /// True. - On, - /// False. - Off, - /// Auto. - Auto, -} - -impl Default for Switch { - fn default() -> Self { - Switch::Auto - } -} - -impl FromStr for Switch { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "on" => Ok(Switch::On), - "off" => Ok(Switch::Off), - "auto" => Ok(Switch::Auto), - other => Err(format!("Invalid switch value: {}", other)) - } - } -} - -impl Switch { - /// Tries to turn old switch to new value. - pub fn turn_to(&self, to: Switch) -> Result { - match (*self, to) { - (Switch::On, Switch::On) | (Switch::On, Switch::Auto) | (Switch::Auto, Switch::On) => Ok(true), - (Switch::Off, Switch::On) => Err(Error::ResyncRequired), - _ => Ok(false), - } - } -} /// Traces config. #[derive(Debug, PartialEq, Clone)] pub struct Config { /// Indicates if tracing should be enabled or not. /// If it's None, it will be automatically configured. - pub enabled: Switch, + pub enabled: bool, /// Traces blooms configuration. pub blooms: BloomConfig, /// Preferef cache-size. @@ -77,7 +34,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Config { - enabled: Switch::default(), + enabled: false, blooms: BloomConfig { levels: 3, elements_per_index: 16, @@ -87,20 +44,3 @@ impl Default for Config { } } } - -#[cfg(test)] -mod tests { - use super::Switch; - - #[test] - fn test_switch_parsing() { - assert_eq!(Switch::On, "on".parse().unwrap()); - assert_eq!(Switch::Off, "off".parse().unwrap()); - assert_eq!(Switch::Auto, "auto".parse().unwrap()); - } - - #[test] - fn test_switch_default() { - assert_eq!(Switch::default(), Switch::Auto); - } -} diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index e7bd7c825c4..b608ad68578 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -22,7 +22,7 @@ use bloomchain::{Number, Config as BloomConfig}; use bloomchain::group::{BloomGroupDatabase, BloomGroupChain, GroupPosition, BloomGroup}; use util::{H256, H264, Database, DBTransaction, RwLock, HeapSizeOf}; use header::BlockNumber; -use trace::{LocalizedTrace, Config, Switch, Filter, Database as TraceDatabase, ImportRequest, DatabaseExtras, Error}; +use trace::{LocalizedTrace, Config, Filter, Database as TraceDatabase, ImportRequest, DatabaseExtras}; use db::{self, Key, Writable, Readable, CacheUpdatePolicy}; use blooms; use super::flat::{FlatTrace, FlatBlockTraces, FlatTransactionTraces}; @@ -126,38 +126,20 @@ impl BloomGroupDatabase for TraceDB where T: DatabaseExtras { impl TraceDB where T: DatabaseExtras { /// Creates new instance of `TraceDB`. - pub fn new(config: Config, tracesdb: Arc, extras: Arc) -> Result { - // check if in previously tracing was enabled - let old_tracing = match tracesdb.get(db::COL_TRACE, b"enabled").unwrap() { - Some(ref value) if value as &[u8] == &[0x1] => Switch::On, - Some(ref value) if value as &[u8] == &[0x0] => Switch::Off, - Some(_) => { panic!("tracesdb is corrupted") }, - None => Switch::Auto, - }; - - let enabled = try!(old_tracing.turn_to(config.enabled)); - - let encoded_tracing = match enabled { - true => [0x1], - false => [0x0] - }; - + pub fn new(config: Config, tracesdb: Arc, extras: Arc) -> Self { let mut batch = DBTransaction::new(&tracesdb); - batch.put(db::COL_TRACE, b"enabled", &encoded_tracing); batch.put(db::COL_TRACE, b"version", TRACE_DB_VER); tracesdb.write(batch).unwrap(); - let db = TraceDB { + TraceDB { traces: RwLock::new(HashMap::new()), blooms: RwLock::new(HashMap::new()), cache_manager: RwLock::new(CacheManager::new(config.pref_cache_size, config.max_cache_size, 10 * 1024)), tracesdb: tracesdb, bloom_config: config.blooms, - enabled: enabled, + enabled: config.enabled, extras: extras, - }; - - Ok(db) + } } fn cache_size(&self) -> usize { @@ -419,7 +401,7 @@ mod tests { use util::{Address, U256, H256, Database, DatabaseConfig, DBTransaction}; use devtools::RandomTempPath; use header::BlockNumber; - use trace::{Config, Switch, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest}; + use trace::{Config, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest}; use trace::{Filter, LocalizedTrace, AddressesFilter, TraceError}; use trace::trace::{Call, Action, Res}; use trace::flat::{FlatTrace, FlatBlockTraces, FlatTransactionTraces}; @@ -474,22 +456,10 @@ mod tests { let mut config = Config::default(); // set autotracing - config.enabled = Switch::Auto; - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), false); - } - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), false); - } - - config.enabled = Switch::Off; + config.enabled = false; { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); + let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)); assert_eq!(tracedb.tracing_enabled(), false); } } @@ -501,50 +471,12 @@ mod tests { let mut config = Config::default(); // set tracing on - config.enabled = Switch::On; - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), true); - } - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), true); - } - - config.enabled = Switch::Auto; - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), true); - } - - config.enabled = Switch::Off; - - { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); - assert_eq!(tracedb.tracing_enabled(), false); - } - } - - #[test] - #[should_panic] - fn test_invalid_reopening_db() { - let temp = RandomTempPath::new(); - let db = new_db(temp.as_str()); - let mut config = Config::default(); - - // set tracing on - config.enabled = Switch::Off; + config.enabled = true; { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); + let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)); assert_eq!(tracedb.tracing_enabled(), true); } - - config.enabled = Switch::On; - TraceDB::new(config.clone(), db.clone(), Arc::new(NoopExtras)).unwrap(); // should panic! } fn create_simple_import_request(block_number: BlockNumber, block_hash: H256) -> ImportRequest { @@ -595,7 +527,7 @@ mod tests { let temp = RandomTempPath::new(); let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap()); let mut config = Config::default(); - config.enabled = Switch::On; + config.enabled = true; let block_0 = H256::from(0xa1); let block_1 = H256::from(0xa2); let tx_0 = H256::from(0xff); @@ -607,7 +539,7 @@ mod tests { extras.transaction_hashes.insert(0, vec![tx_0.clone()]); extras.transaction_hashes.insert(1, vec![tx_1.clone()]); - let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras)).unwrap(); + let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras)); // import block 0 let request = create_simple_import_request(0, block_0.clone()); @@ -679,10 +611,10 @@ mod tests { extras.transaction_hashes.insert(0, vec![tx_0.clone()]); // set tracing on - config.enabled = Switch::On; + config.enabled = true; { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone())).unwrap(); + let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone())); // import block 0 let request = create_simple_import_request(0, block_0.clone()); @@ -692,7 +624,7 @@ mod tests { } { - let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras)).unwrap(); + let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras)); let traces = tracedb.transaction_traces(0, 0); assert_eq!(traces.unwrap(), vec![create_simple_localized_trace(0, block_0, tx_0)]); } diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 06604450fdf..da3bbc02be3 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -26,7 +26,7 @@ mod noop_tracer; pub use types::trace_types::{filter, flat, localized, trace}; pub use types::trace_types::error::Error as TraceError; -pub use self::config::{Config, Switch}; +pub use self::config::Config; pub use self::db::TraceDB; pub use self::error::Error; pub use types::trace_types::trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff}; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 72215ca5926..3dfdac80400 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -26,15 +26,16 @@ use io::{PanicHandler, ForwardPanic}; use util::{ToPretty, Uint}; use rlp::PayloadInfo; use ethcore::service::ClientService; -use ethcore::client::{Mode, DatabaseCompactionProfile, Switch, VMType, BlockImportError, BlockChainClient, BlockID}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockID}; use ethcore::error::ImportError; use ethcore::miner::Miner; use cache::CacheConfig; +use params::{SpecType, Pruning, Switch, tracing_switch_to_bool}; use informant::{Informant, MillisecondDuration}; use io_handler::ImportIoHandler; -use params::{SpecType, Pruning}; use helpers::{to_client_config, execute_upgrades}; use dir::Directories; +use user_defaults::UserDefaults; use fdlimit; #[derive(Debug, PartialEq)] @@ -113,29 +114,44 @@ fn execute_import(cmd: ImportBlockchain) -> Result { // Setup panic handler let panic_handler = PanicHandler::new_in_arc(); + // Setup logging + let _logger = setup_log(&cmd.logger_config); + + // create dirs used by parity + try!(cmd.dirs.create_dirs()); + // load spec file let spec = try!(cmd.spec.spec()); // load genesis hash let genesis_hash = spec.genesis_header().hash(); - // Setup logging - let _logger = setup_log(&cmd.logger_config); + // database paths + let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone()); + + // user defaults path + let user_defaults_path = db_dirs.user_defaults_path(); + + // load user defaults + let mut user_defaults = try!(UserDefaults::load(&user_defaults_path)); + + // check if tracing is on + let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults)); fdlimit::raise_fd_limit(); // select pruning algorithm - let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, spec.fork_name.as_ref()); + let algorithm = cmd.pruning.to_algorithm(&user_defaults); // prepare client and snapshot paths. - let client_path = cmd.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm); - let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref()); + let client_path = db_dirs.client_path(algorithm); + let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&cmd.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile())); + try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile())); // prepare client config - let client_config = to_client_config(&cmd.cache_config, &cmd.dirs, genesis_hash, cmd.mode, cmd.tracing, cmd.pruning, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), spec.fork_name.as_ref()); + let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm); // build client let service = try!(ClientService::start( @@ -220,6 +236,12 @@ fn execute_import(cmd: ImportBlockchain) -> Result { } } client.flush_queue(); + + // save user defaults + user_defaults.pruning = algorithm; + user_defaults.tracing = tracing; + try!(user_defaults.save(&user_defaults_path)); + let report = client.report(); let ms = timer.elapsed().as_milliseconds(); @@ -238,6 +260,12 @@ fn execute_export(cmd: ExportBlockchain) -> Result { // Setup panic handler let panic_handler = PanicHandler::new_in_arc(); + // Setup logging + let _logger = setup_log(&cmd.logger_config); + + // create dirs used by parity + try!(cmd.dirs.create_dirs()); + let format = cmd.format.unwrap_or_default(); // load spec file @@ -246,23 +274,32 @@ fn execute_export(cmd: ExportBlockchain) -> Result { // load genesis hash let genesis_hash = spec.genesis_header().hash(); - // Setup logging - let _logger = setup_log(&cmd.logger_config); + // database paths + let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone()); + + // user defaults path + let user_defaults_path = db_dirs.user_defaults_path(); + + // load user defaults + let user_defaults = try!(UserDefaults::load(&user_defaults_path)); + + // check if tracing is on + let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults)); fdlimit::raise_fd_limit(); // select pruning algorithm - let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, spec.fork_name.as_ref()); + let algorithm = cmd.pruning.to_algorithm(&user_defaults); // prepare client and snapshot paths. - let client_path = cmd.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm); - let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref()); + let client_path = db_dirs.client_path(algorithm); + let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&cmd.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile())); + try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile())); // prepare client config - let client_config = to_client_config(&cmd.cache_config, &cmd.dirs, genesis_hash, cmd.mode, cmd.tracing, cmd.pruning, cmd.compaction, cmd.wal, VMType::default(), "".into(), spec.fork_name.as_ref()); + let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm); let service = try!(ClientService::start( client_config, diff --git a/parity/dir.rs b/parity/dir.rs index d31e81e2c33..158b5b2c580 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -52,38 +52,62 @@ impl Directories { Ok(()) } - /// Get the chain's root path. - pub fn chain_path(&self, genesis_hash: H256, fork_name: Option<&String>) -> PathBuf { + /// Database paths. + pub fn database(&self, genesis_hash: H256, fork_name: Option) -> DatabaseDirectories { + DatabaseDirectories { + path: self.db.clone(), + genesis_hash: genesis_hash, + fork_name: fork_name, + } + } + + /// Get the ipc sockets path + pub fn ipc_path(&self) -> PathBuf { let mut dir = Path::new(&self.db).to_path_buf(); - dir.push(format!("{:?}{}", H64::from(genesis_hash), fork_name.map(|f| format!("-{}", f)).unwrap_or_default())); + dir.push("ipc"); + dir + } +} + +#[derive(Debug, PartialEq)] +pub struct DatabaseDirectories { + pub path: String, + pub genesis_hash: H256, + pub fork_name: Option, +} + +impl DatabaseDirectories { + fn fork_path(&self) -> PathBuf { + let mut dir = Path::new(&self.path).to_path_buf(); + dir.push(format!("{:?}{}", H64::from(self.genesis_hash), self.fork_name.as_ref().map(|f| format!("-{}", f)).unwrap_or_default())); dir } /// Get the root path for database - pub fn db_version_path(&self, genesis_hash: H256, fork_name: Option<&String>, pruning: Algorithm) -> PathBuf { - let mut dir = self.chain_path(genesis_hash, fork_name); + pub fn version_path(&self, pruning: Algorithm) -> PathBuf { + let mut dir = self.fork_path(); dir.push(format!("v{}-sec-{}", LEGACY_CLIENT_DB_VER_STR, pruning.as_internal_name_str())); dir } /// Get the path for the databases given the genesis_hash and information on the databases. - pub fn client_path(&self, genesis_hash: H256, fork_name: Option<&String>, pruning: Algorithm) -> PathBuf { - let mut dir = self.db_version_path(genesis_hash, fork_name, pruning); + pub fn client_path(&self, pruning: Algorithm) -> PathBuf { + let mut dir = self.version_path(pruning); dir.push("db"); dir } - /// Get the path for the snapshot directory given the genesis hash and fork name. - pub fn snapshot_path(&self, genesis_hash: H256, fork_name: Option<&String>) -> PathBuf { - let mut dir = self.chain_path(genesis_hash, fork_name); - dir.push("snapshot"); + /// Get user defaults path + pub fn user_defaults_path(&self) -> PathBuf { + let mut dir = self.fork_path(); + dir.push("user_defaults"); dir } - /// Get the ipc sockets path - pub fn ipc_path(&self) -> PathBuf { - let mut dir = Path::new(&self.db).to_path_buf(); - dir.push("ipc"); + /// Get the path for the snapshot directory given the genesis hash and fork name. + pub fn snapshot_path(&self) -> PathBuf { + let mut dir = self.fork_path(); + dir.push("snapshot"); dir } } diff --git a/parity/helpers.rs b/parity/helpers.rs index 778dc1265be..0649e7fe91b 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -19,13 +19,12 @@ use std::io::{Write, Read, BufReader, BufRead}; use std::time::Duration; use std::path::Path; use std::fs::File; -use util::{clean_0x, U256, Uint, Address, path, H256, CompactionProfile}; +use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; -use ethcore::client::{Mode, BlockID, Switch, VMType, DatabaseCompactionProfile, ClientConfig}; +use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig}; use ethcore::miner::PendingSet; use cache::CacheConfig; -use dir::Directories; -use params::Pruning; +use dir::DatabaseDirectories; use upgrade::upgrade; use migration::migrate; use ethsync::is_valid_node_url; @@ -190,16 +189,13 @@ pub fn default_network_config() -> ::ethsync::NetworkConfiguration { #[cfg_attr(feature = "dev", allow(too_many_arguments))] pub fn to_client_config( cache_config: &CacheConfig, - dirs: &Directories, - genesis_hash: H256, mode: Mode, - tracing: Switch, - pruning: Pruning, + tracing: bool, compaction: DatabaseCompactionProfile, wal: bool, vm_type: VMType, name: String, - fork_name: Option<&String>, + pruning: Algorithm, ) -> ClientConfig { let mut client_config = ClientConfig::default(); @@ -221,7 +217,7 @@ pub fn to_client_config( client_config.mode = mode; client_config.tracing.enabled = tracing; - client_config.pruning = pruning.to_algorithm(dirs, genesis_hash, fork_name); + client_config.pruning = pruning; client_config.db_compaction = compaction; client_config.db_wal = wal; client_config.vm_type = vm_type; @@ -230,14 +226,12 @@ pub fn to_client_config( } pub fn execute_upgrades( - dirs: &Directories, - genesis_hash: H256, - fork_name: Option<&String>, + dirs: &DatabaseDirectories, pruning: Algorithm, compaction_profile: CompactionProfile ) -> Result<(), String> { - match upgrade(Some(&dirs.db)) { + match upgrade(Some(&dirs.path)) { Ok(upgrades_applied) if upgrades_applied > 0 => { debug!("Executed {} upgrade scripts - ok", upgrades_applied); }, @@ -247,7 +241,7 @@ pub fn execute_upgrades( _ => {}, } - let client_path = dirs.db_version_path(genesis_hash, fork_name, pruning); + let client_path = dirs.version_path(pruning); migrate(&client_path, pruning, compaction_profile).map_err(|e| format!("{}", e)) } diff --git a/parity/main.rs b/parity/main.rs index 0cb466dc34f..b74af7b3d87 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -39,6 +39,8 @@ extern crate semver; extern crate ethcore_io as io; extern crate ethcore_ipc as ipc; extern crate ethcore_ipc_nano as nanoipc; +extern crate serde; +extern crate serde_json; extern crate rlp; extern crate json_ipc_server as jsonipc; @@ -106,6 +108,7 @@ mod run; mod sync; #[cfg(feature="ipc")] mod boot; +mod user_defaults; #[cfg(feature="stratum")] mod stratum; diff --git a/parity/params.rs b/parity/params.rs index c67520aa1f8..71f702cfbe4 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -14,15 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::str::FromStr; -use std::fs; +use std::{str, fs}; use std::time::Duration; -use util::{H256, Address, U256, version_data}; +use util::{Address, U256, version_data}; use util::journaldb::Algorithm; use ethcore::spec::Spec; use ethcore::ethereum; use ethcore::miner::{GasPricer, GasPriceCalibratorOptions}; -use dir::Directories; +use user_defaults::UserDefaults; #[derive(Debug, PartialEq)] pub enum SpecType { @@ -39,7 +38,7 @@ impl Default for SpecType { } } -impl FromStr for SpecType { +impl str::FromStr for SpecType { type Err = String; fn from_str(s: &str) -> Result { @@ -81,7 +80,7 @@ impl Default for Pruning { } } -impl FromStr for Pruning { +impl str::FromStr for Pruning { type Err = String; fn from_str(s: &str) -> Result { @@ -93,24 +92,12 @@ impl FromStr for Pruning { } impl Pruning { - pub fn to_algorithm(&self, dirs: &Directories, genesis_hash: H256, fork_name: Option<&String>) -> Algorithm { + pub fn to_algorithm(&self, user_defaults: &UserDefaults) -> Algorithm { match *self { Pruning::Specific(algo) => algo, - Pruning::Auto => Self::find_best_db(dirs, genesis_hash, fork_name), + Pruning::Auto => user_defaults.pruning, } } - - fn find_best_db(dirs: &Directories, genesis_hash: H256, fork_name: Option<&String>) -> Algorithm { - let mut algo_types = Algorithm::all_types(); - // if all dbs have the same modification time, the last element is the default one - algo_types.push(Algorithm::default()); - - algo_types.into_iter().max_by_key(|i| { - let mut client_path = dirs.client_path(genesis_hash, fork_name, *i); - client_path.push("CURRENT"); - fs::metadata(&client_path).and_then(|m| m.modified()).ok() - }).unwrap() - } } #[derive(Debug, PartialEq)] @@ -128,7 +115,7 @@ impl Default for ResealPolicy { } } -impl FromStr for ResealPolicy { +impl str::FromStr for ResealPolicy { type Err = String; fn from_str(s: &str) -> Result { @@ -223,10 +210,50 @@ impl Default for MinerExtras { } } +/// 3-value enum. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Switch { + /// True. + On, + /// False. + Off, + /// Auto. + Auto, +} + +impl Default for Switch { + fn default() -> Self { + Switch::Auto + } +} + +impl str::FromStr for Switch { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "on" => Ok(Switch::On), + "off" => Ok(Switch::Off), + "auto" => Ok(Switch::Auto), + other => Err(format!("Invalid switch value: {}", other)) + } + } +} + +pub fn tracing_switch_to_bool(switch: Switch, user_defaults: &UserDefaults) -> Result { + match (user_defaults.is_first_launch, switch, user_defaults.tracing) { + (false, Switch::On, false) => Err("TraceDB resync required".into()), + (_, Switch::On, _) => Ok(true), + (_, Switch::Off, _) => Ok(false), + (_, Switch::Auto, def) => Ok(def), + } +} + #[cfg(test)] mod tests { use util::journaldb::Algorithm; - use super::{SpecType, Pruning, ResealPolicy}; + use user_defaults::UserDefaults; + use super::{SpecType, Pruning, ResealPolicy, Switch, tracing_switch_to_bool}; #[test] fn test_spec_type_parsing() { @@ -274,4 +301,36 @@ mod tests { let all = ResealPolicy { own: true, external: true }; assert_eq!(all, ResealPolicy::default()); } + + #[test] + fn test_switch_parsing() { + assert_eq!(Switch::On, "on".parse().unwrap()); + assert_eq!(Switch::Off, "off".parse().unwrap()); + assert_eq!(Switch::Auto, "auto".parse().unwrap()); + } + + #[test] + fn test_switch_default() { + assert_eq!(Switch::default(), Switch::Auto); + } + + fn user_defaults_with_tracing(first_launch: bool, tracing: bool) -> UserDefaults { + let mut ud = UserDefaults::default(); + ud.is_first_launch = first_launch; + ud.tracing = tracing; + ud + } + + #[test] + fn test_switch_to_bool() { + assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, true)).unwrap()); + assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, false)).unwrap()); + assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, true)).unwrap()); + assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, false)).unwrap()); + + assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, true)).unwrap()); + assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, false)).unwrap()); + assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, true)).unwrap()); + assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, false)).is_err()); + } } diff --git a/parity/run.rs b/parity/run.rs index 6e368522ea3..e95b5c9f52e 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -23,7 +23,7 @@ use ethcore_rpc::NetworkSettings; use ethsync::NetworkConfiguration; use util::{Colour, version, U256}; use io::{MayPanic, ForwardPanic, PanicHandler}; -use ethcore::client::{Mode, Switch, DatabaseCompactionProfile, VMType, ChainNotify}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, ChainNotify}; use ethcore::service::ClientService; use ethcore::account_provider::AccountProvider; use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions}; @@ -35,10 +35,11 @@ use rpc::{HttpServer, IpcServer, HttpConfiguration, IpcConfiguration}; use signer::SignerServer; use dapps::WebappServer; use io_handler::ClientIoHandler; -use params::{SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras}; +use params::{SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, tracing_switch_to_bool}; use helpers::{to_client_config, execute_upgrades, passwords_from_files}; use dir::Directories; use cache::CacheConfig; +use user_defaults::UserDefaults; use dapps; use signer; use modules; @@ -87,34 +88,45 @@ pub struct RunCmd { } pub fn execute(cmd: RunCmd) -> Result<(), String> { - // increase max number of open files - raise_fd_limit(); + // set up panic handler + let panic_handler = PanicHandler::new_in_arc(); // set up logger let logger = try!(setup_log(&cmd.logger_config)); - // set up panic handler - let panic_handler = PanicHandler::new_in_arc(); + // increase max number of open files + raise_fd_limit(); // create dirs used by parity try!(cmd.dirs.create_dirs()); // load spec let spec = try!(cmd.spec.spec()); - let fork_name = spec.fork_name.clone(); // load genesis hash let genesis_hash = spec.genesis_header().hash(); + // database paths + let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone()); + + // user defaults path + let user_defaults_path = db_dirs.user_defaults_path(); + + // load user defaults + let mut user_defaults = try!(UserDefaults::load(&user_defaults_path)); + + // check if tracing is on + let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults)); + // select pruning algorithm - let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, fork_name.as_ref()); + let algorithm = cmd.pruning.to_algorithm(&user_defaults); // prepare client and snapshot paths. - let client_path = cmd.dirs.client_path(genesis_hash, fork_name.as_ref(), algorithm); - let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, fork_name.as_ref()); + let client_path = db_dirs.client_path(algorithm); + let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&cmd.dirs, genesis_hash, fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile())); + try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile())); // run in daemon mode if let Some(pid_file) = cmd.daemon { @@ -152,16 +164,13 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> { // create client config let client_config = to_client_config( &cmd.cache_config, - &cmd.dirs, - genesis_hash, cmd.mode, - cmd.tracing, - cmd.pruning, + tracing, cmd.compaction, cmd.wal, cmd.vm_type, cmd.name, - fork_name.as_ref(), + algorithm, ); // set up bootnodes @@ -288,6 +297,11 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> { url::open(&format!("http://{}:{}/", cmd.dapps_conf.interface, cmd.dapps_conf.port)); } + // save user defaults + user_defaults.pruning = algorithm; + user_defaults.tracing = tracing; + try!(user_defaults.save(&user_defaults_path)); + // Handle exit wait_for_exit(panic_handler, http_server, ipc_server, dapps_server, signer_server); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 73d06426fb5..f3a8a45d3d6 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -25,14 +25,15 @@ use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService as SS}; use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter}; use ethcore::snapshot::service::Service as SnapshotService; use ethcore::service::ClientService; -use ethcore::client::{Mode, DatabaseCompactionProfile, Switch, VMType}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType}; use ethcore::miner::Miner; use ethcore::ids::BlockID; use cache::CacheConfig; -use params::{SpecType, Pruning}; +use params::{SpecType, Pruning, Switch, tracing_switch_to_bool}; use helpers::{to_client_config, execute_upgrades}; use dir::Directories; +use user_defaults::UserDefaults; use fdlimit; use io::PanicHandler; @@ -129,23 +130,35 @@ impl SnapshotCommand { // load genesis hash let genesis_hash = spec.genesis_header().hash(); + // database paths + let db_dirs = self.dirs.database(genesis_hash, spec.fork_name.clone()); + + // user defaults path + let user_defaults_path = db_dirs.user_defaults_path(); + + // load user defaults + let user_defaults = try!(UserDefaults::load(&user_defaults_path)); + + // check if tracing is on + let tracing = try!(tracing_switch_to_bool(self.tracing, &user_defaults)); + // Setup logging let _logger = setup_log(&self.logger_config); fdlimit::raise_fd_limit(); // select pruning algorithm - let algorithm = self.pruning.to_algorithm(&self.dirs, genesis_hash, spec.fork_name.as_ref()); + let algorithm = self.pruning.to_algorithm(&user_defaults); // prepare client and snapshot paths. - let client_path = self.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm); - let snapshot_path = self.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref()); + let client_path = db_dirs.client_path(algorithm); + let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&self.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, self.compaction.compaction_profile())); + try!(execute_upgrades(&db_dirs, algorithm, self.compaction.compaction_profile())); // prepare client config - let client_config = to_client_config(&self.cache_config, &self.dirs, genesis_hash, self.mode, self.tracing, self.pruning, self.compaction, self.wal, VMType::default(), "".into(), spec.fork_name.as_ref()); + let client_config = to_client_config(&self.cache_config, self.mode, tracing, self.compaction, self.wal, VMType::default(), "".into(), algorithm); let service = try!(ClientService::start( client_config, diff --git a/parity/user_defaults.rs b/parity/user_defaults.rs new file mode 100644 index 00000000000..8a1feebae32 --- /dev/null +++ b/parity/user_defaults.rs @@ -0,0 +1,98 @@ +// 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 . + +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::collections::BTreeMap; +use serde::{Serialize, Serializer, Error, Deserialize, Deserializer}; +use serde::de::{Visitor, MapVisitor}; +use serde::de::impls::BTreeMapVisitor; +use serde_json::Value; +use serde_json::de::from_reader; +use serde_json::ser::to_string; +use util::journaldb::Algorithm; + +pub struct UserDefaults { + pub is_first_launch: bool, + pub pruning: Algorithm, + pub tracing: bool, +} + +impl Serialize for UserDefaults { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer { + let mut map: BTreeMap = BTreeMap::new(); + map.insert("pruning".into(), Value::String(self.pruning.as_str().into())); + map.insert("tracing".into(), Value::Bool(self.tracing)); + map.serialize(serializer) + } +} + +struct UserDefaultsVisitor; + +impl Deserialize for UserDefaults { + fn deserialize(deserializer: &mut D) -> Result + where D: Deserializer { + deserializer.deserialize(UserDefaultsVisitor) + } +} + +impl Visitor for UserDefaultsVisitor { + type Value = UserDefaults; + + fn visit_map(&mut self, visitor: V) -> Result + where V: MapVisitor { + let mut map: BTreeMap = try!(BTreeMapVisitor::new().visit_map(visitor)); + let pruning: Value = try!(map.remove("pruning".into()).ok_or_else(|| Error::custom("missing pruning"))); + let pruning = try!(pruning.as_str().ok_or_else(|| Error::custom("invalid pruning value"))); + let pruning = try!(pruning.parse().map_err(|_| Error::custom("invalid pruning method"))); + let tracing: Value = try!(map.remove("tracing".into()).ok_or_else(|| Error::custom("missing tracing"))); + let tracing = try!(tracing.as_bool().ok_or_else(|| Error::custom("invalid tracing value"))); + + let user_defaults = UserDefaults { + is_first_launch: false, + pruning: pruning, + tracing: tracing, + }; + + Ok(user_defaults) + } +} + +impl Default for UserDefaults { + fn default() -> Self { + UserDefaults { + is_first_launch: true, + pruning: Algorithm::default(), + tracing: false, + } + } +} + +impl UserDefaults { + pub fn load

(path: P) -> Result where P: AsRef { + match File::open(path) { + Ok(file) => from_reader(file).map_err(|e| e.to_string()), + _ => Ok(UserDefaults::default()), + } + } + + pub fn save

(self, path: P) -> Result<(), String> where P: AsRef { + let mut file: File = try!(File::create(path).map_err(|_| "Cannot create user defaults file".to_owned())); + file.write_all(to_string(&self).unwrap().as_bytes()).map_err(|_| "Failed to save user defaults".to_owned()) + } +} diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index b9f8c4bf594..220ead3dd9b 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -30,7 +30,7 @@ use ethcore::client::{MiningBlockChainClient}; use jsonrpc_core::*; use v1::traits::Ethcore; -use v1::types::{Bytes, U256, H160, H512, Peers}; +use v1::types::{Bytes, U256, H160, H512, Peers, Transaction}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::params::expect_no_params; @@ -226,4 +226,11 @@ impl Ethcore for EthcoreClient where M: MinerService + Ok(to_value(&Bytes::from(s))) }) } + + fn pending_transactions(&self, params: Params) -> Result { + try!(self.active()); + try!(expect_no_params(params)); + + Ok(to_value(&take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::>())) + } } diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index a9d0c4e1dc7..811ccced42d 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -286,3 +286,18 @@ fn rpc_ethcore_unsigned_transactions_count_when_signer_disabled() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } + +#[test] +fn rpc_ethcore_pending_transactions() { + let miner = miner_service(); + let client = client_service(); + let sync = sync_provider(); + let net = network_service(); + let io = IoHandler::new(); + io.add_delegate(ethcore_client(&client, &miner, &sync, &net).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "ethcore_pendingTransactions", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":[],"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index 90661129bef..56c27534a15 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -80,6 +80,9 @@ pub trait Ethcore: Sized + Send + Sync + 'static { /// First parameter is the 512-byte destination public key, second is the message. fn encrypt_message(&self, _: Params) -> Result; + /// Returns all pending (current) transactions from transaction queue. + fn pending_transactions(&self, _: Params) -> Result; + /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); @@ -103,7 +106,7 @@ pub trait Ethcore: Sized + Send + Sync + 'static { delegate.add_method("ethcore_phraseToAddress", Ethcore::phrase_to_address); delegate.add_method("ethcore_registryAddress", Ethcore::registry_address); delegate.add_method("ethcore_encryptMessage", Ethcore::encrypt_message); - + delegate.add_method("ethcore_pendingTransactions", Ethcore::pending_transactions); delegate } } diff --git a/rpc/src/v1/types/bytes.rs b/rpc/src/v1/types/bytes.rs index acf2c83ef4b..74d538b72e4 100644 --- a/rpc/src/v1/types/bytes.rs +++ b/rpc/src/v1/types/bytes.rs @@ -70,7 +70,13 @@ impl Visitor for BytesVisitor { type Value = Bytes; fn visit_str(&mut self, value: &str) -> Result where E: Error { - if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { + if value.is_empty() { + warn!( + target: "deprecated", + "Deserializing empty string as empty bytes. This is a non-standard behaviour that will be removed in future versions. Please update your code to send `0x` instead!" + ); + Ok(Bytes::new(Vec::new())) + } else if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { Ok(Bytes::new(FromHex::from_hex(&value[2..]).unwrap_or_else(|_| vec![]))) } else { Err(Error::custom("invalid hex")) @@ -98,18 +104,26 @@ mod tests { #[test] fn test_bytes_deserialize() { - let bytes1: Result = serde_json::from_str(r#""""#); + // TODO [ToDr] Uncomment when Mist starts sending correct data + // let bytes1: Result = serde_json::from_str(r#""""#); let bytes2: Result = serde_json::from_str(r#""0x123""#); let bytes3: Bytes = serde_json::from_str(r#""0x""#).unwrap(); let bytes4: Bytes = serde_json::from_str(r#""0x12""#).unwrap(); let bytes5: Bytes = serde_json::from_str(r#""0x0123""#).unwrap(); - assert!(bytes1.is_err()); + // assert!(bytes1.is_err()); assert!(bytes2.is_err()); assert_eq!(bytes3, Bytes(vec![])); assert_eq!(bytes4, Bytes(vec![0x12])); assert_eq!(bytes5, Bytes(vec![0x1, 0x23])); } + + // TODO [ToDr] Remove when Mist starts sending correct data + #[test] + fn test_bytes_lenient_against_the_spec_deserialize_for_empty_string_for_mist_compatibility() { + let deserialized: Bytes = serde_json::from_str(r#""""#).unwrap(); + assert_eq!(deserialized, Bytes(Vec::new())); + } }