Merge branch 'master' into tx_queue_integration

Conflicts:
	ethcore/src/client.rs
	sync/src/tests/helpers.rs
This commit is contained in:
Tomasz Drwięga 2016-03-05 00:53:59 +01:00
commit 77d526fd0b
39 changed files with 984 additions and 174 deletions

19
Cargo.lock generated
View File

@ -81,6 +81,15 @@ name = "cfg-if"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "clippy" name = "clippy"
version = "0.0.44" version = "0.0.44"
@ -235,6 +244,7 @@ dependencies = [
"serde_codegen 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "syntex 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -243,6 +253,7 @@ version = "0.9.99"
dependencies = [ dependencies = [
"arrayvec 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"bigint 0.1.0", "bigint 0.1.0",
"chrono 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"elastic-array 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "elastic-array 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -841,6 +852,14 @@ name = "traitobject"
version = "0.0.1" version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "transient-hashmap"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "typeable" name = "typeable"
version = "0.1.2" version = "0.1.2"

View File

@ -448,7 +448,8 @@ impl BlockChain {
let mut write_details = self.block_details.write().unwrap(); let mut write_details = self.block_details.write().unwrap();
for (hash, details) in update.block_details.into_iter() { for (hash, details) in update.block_details.into_iter() {
batch.put_extras(&hash, &details); batch.put_extras(&hash, &details);
write_details.insert(hash, details); write_details.insert(hash.clone(), details);
self.note_used(CacheID::Extras(ExtrasIndex::BlockDetails, hash));
} }
let mut write_receipts = self.block_receipts.write().unwrap(); let mut write_receipts = self.block_receipts.write().unwrap();
@ -759,6 +760,14 @@ impl BlockChain {
// TODO: handle block_hashes properly. // TODO: handle block_hashes properly.
block_hashes.clear(); block_hashes.clear();
blocks.shrink_to_fit();
block_details.shrink_to_fit();
block_hashes.shrink_to_fit();
transaction_addresses.shrink_to_fit();
block_logs.shrink_to_fit();
blocks_blooms.shrink_to_fit();
block_receipts.shrink_to_fit();
} }
if self.cache_size().total() < self.max_cache_size { break; } if self.cache_size().total() < self.max_cache_size { break; }
} }

View File

@ -16,6 +16,7 @@
//! Blockchain database client. //! Blockchain database client.
use std::marker::PhantomData;
use util::*; use util::*;
use util::panics::*; use util::panics::*;
use blockchain::{BlockChain, BlockProvider}; use blockchain::{BlockChain, BlockProvider};
@ -35,6 +36,7 @@ use transaction::LocalizedTransaction;
use extras::TransactionAddress; use extras::TransactionAddress;
use filter::Filter; use filter::Filter;
use log_entry::LocalizedLogEntry; use log_entry::LocalizedLogEntry;
use util::keys::store::SecretStore;
pub use block_queue::{BlockQueueConfig, BlockQueueInfo}; pub use block_queue::{BlockQueueConfig, BlockQueueInfo};
pub use blockchain::{TreeRoute, BlockChainConfig, CacheSize as BlockChainCacheSize}; pub use blockchain::{TreeRoute, BlockChainConfig, CacheSize as BlockChainCacheSize};
@ -76,12 +78,24 @@ pub enum BlockStatus {
} }
/// Client configuration. Includes configs for all sub-systems. /// Client configuration. Includes configs for all sub-systems.
#[derive(Debug, Default)] #[derive(Debug)]
pub struct ClientConfig { pub struct ClientConfig {
/// Block queue configuration. /// Block queue configuration.
pub queue: BlockQueueConfig, pub queue: BlockQueueConfig,
/// Blockchain configuration. /// Blockchain configuration.
pub blockchain: BlockChainConfig, pub blockchain: BlockChainConfig,
/// Prefer journal rather than archive.
pub prefer_journal: bool,
}
impl Default for ClientConfig {
fn default() -> ClientConfig {
ClientConfig {
queue: Default::default(),
blockchain: Default::default(),
prefer_journal: false,
}
}
} }
/// Information about the blockchain gathered together. /// Information about the blockchain gathered together.
@ -126,6 +140,9 @@ pub trait BlockChainClient : Sync + Send {
/// Get address nonce. /// Get address nonce.
fn nonce(&self, address: &Address) -> U256; fn nonce(&self, address: &Address) -> U256;
/// Get block hash.
fn block_hash(&self, id: BlockId) -> Option<H256>;
/// Get address code. /// Get address code.
fn code(&self, address: &Address) -> Option<Bytes>; fn code(&self, address: &Address) -> Option<Bytes>;
@ -188,7 +205,7 @@ impl ClientReport {
/// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue. /// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue.
/// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue.
pub struct Client { pub struct Client<V = CanonVerifier> where V: Verifier {
chain: Arc<RwLock<BlockChain>>, chain: Arc<RwLock<BlockChain>>,
engine: Arc<Box<Engine>>, engine: Arc<Box<Engine>>,
state_db: Mutex<JournalDB>, state_db: Mutex<JournalDB>,
@ -201,18 +218,27 @@ pub struct Client {
sealing_block: Mutex<Option<ClosedBlock>>, sealing_block: Mutex<Option<ClosedBlock>>,
author: RwLock<Address>, author: RwLock<Address>,
extra_data: RwLock<Bytes>, extra_data: RwLock<Bytes>,
verifier: PhantomData<V>,
secret_store: Arc<RwLock<SecretStore>>,
} }
const HISTORY: u64 = 1000; const HISTORY: u64 = 1000;
const CLIENT_DB_VER_STR: &'static str = "4.0"; const CLIENT_DB_VER_STR: &'static str = "4.0";
impl Client { impl Client<CanonVerifier> {
/// Create a new client with given spec and DB path. /// Create a new client with given spec and DB path.
pub fn new(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Arc<Client>, Error> { pub fn new(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Arc<Client>, Error> {
Client::<CanonVerifier>::new_with_verifier(config, spec, path, message_channel)
}
}
impl<V> Client<V> where V: Verifier {
/// Create a new client with given spec and DB path and custom verifier.
pub fn new_with_verifier(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Arc<Client>, Error> {
let mut dir = path.to_path_buf(); let mut dir = path.to_path_buf();
dir.push(H64::from(spec.genesis_header().hash()).hex()); dir.push(H64::from(spec.genesis_header().hash()).hex());
//TODO: sec/fat: pruned/full versioning //TODO: sec/fat: pruned/full versioning
dir.push(format!("v{}-sec-pruned", CLIENT_DB_VER_STR)); dir.push(format!("v{}-sec-{}", CLIENT_DB_VER_STR, if config.prefer_journal { "pruned" } else { "archive" }));
let path = dir.as_path(); let path = dir.as_path();
let gb = spec.genesis_block(); let gb = spec.genesis_block();
let chain = Arc::new(RwLock::new(BlockChain::new(config.blockchain, &gb, path))); let chain = Arc::new(RwLock::new(BlockChain::new(config.blockchain, &gb, path)));
@ -220,7 +246,7 @@ impl Client {
state_path.push("state"); state_path.push("state");
let engine = Arc::new(try!(spec.to_engine())); let engine = Arc::new(try!(spec.to_engine()));
let mut state_db = JournalDB::new(state_path.to_str().unwrap()); let mut state_db = JournalDB::from_prefs(state_path.to_str().unwrap(), config.prefer_journal);
if state_db.is_empty() && engine.spec().ensure_db_good(&mut state_db) { if state_db.is_empty() && engine.spec().ensure_db_good(&mut state_db) {
state_db.commit(0, &engine.spec().genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); state_db.commit(0, &engine.spec().genesis_header().hash(), None).expect("Error commiting genesis state to state DB");
} }
@ -229,6 +255,9 @@ impl Client {
let panic_handler = PanicHandler::new_in_arc(); let panic_handler = PanicHandler::new_in_arc();
panic_handler.forward_from(&block_queue); panic_handler.forward_from(&block_queue);
let secret_store = Arc::new(RwLock::new(SecretStore::new()));
secret_store.write().unwrap().try_import_existing();
Ok(Arc::new(Client { Ok(Arc::new(Client {
chain: chain, chain: chain,
engine: engine, engine: engine,
@ -240,6 +269,8 @@ impl Client {
sealing_block: Mutex::new(None), sealing_block: Mutex::new(None),
author: RwLock::new(Address::new()), author: RwLock::new(Address::new()),
extra_data: RwLock::new(Vec::new()), extra_data: RwLock::new(Vec::new()),
verifier: PhantomData,
secret_store: secret_store,
})) }))
} }
@ -264,6 +295,11 @@ impl Client {
last_hashes last_hashes
} }
/// Secret store (key manager)
pub fn secret_store(&self) -> &Arc<RwLock<SecretStore>> {
&self.secret_store
}
fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result<ClosedBlock, ()> { fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result<ClosedBlock, ()> {
let engine = self.engine.deref().deref(); let engine = self.engine.deref().deref();
let header = &block.header; let header = &block.header;
@ -302,7 +338,7 @@ impl Client {
// Final Verification // Final Verification
let closed_block = enact_result.unwrap(); let closed_block = enact_result.unwrap();
if let Err(e) = verify_block_final(&header, closed_block.block().header()) { if let Err(e) = V::verify_block_final(&header, closed_block.block().header()) {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
return Err(()); return Err(());
} }
@ -505,7 +541,7 @@ impl Client {
// TODO: need MinerService MinerIoHandler // TODO: need MinerService MinerIoHandler
impl BlockChainClient for Client { impl<V> BlockChainClient for Client<V> where V: Verifier {
fn block_header(&self, id: BlockId) -> Option<Bytes> { fn block_header(&self, id: BlockId) -> Option<Bytes> {
let chain = self.chain.read().unwrap(); let chain = self.chain.read().unwrap();
Self::block_hash(&chain, id).and_then(|hash| chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec())) Self::block_hash(&chain, id).and_then(|hash| chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()))
@ -549,6 +585,11 @@ impl BlockChainClient for Client {
self.state().nonce(address) self.state().nonce(address)
} }
fn block_hash(&self, id: BlockId) -> Option<H256> {
let chain = self.chain.read().unwrap();
Self::block_hash(&chain, id)
}
fn code(&self, address: &Address) -> Option<Bytes> { fn code(&self, address: &Address) -> Option<Bytes> {
self.state().code(address) self.state().code(address)
} }

View File

@ -42,6 +42,22 @@ pub struct Filter {
pub topics: [Option<Vec<H256>>; 4], pub topics: [Option<Vec<H256>>; 4],
} }
impl Clone for Filter {
fn clone(&self) -> Self {
let mut topics = [None, None, None, None];
for i in 0..4 {
topics[i] = self.topics[i].clone();
}
Filter {
from_block: self.from_block.clone(),
to_block: self.to_block.clone(),
address: self.address.clone(),
topics: topics
}
}
}
impl Filter { impl Filter {
/// Returns combinations of each address and topic. /// Returns combinations of each address and topic.
pub fn bloom_possibilities(&self) -> Vec<H2048> { pub fn bloom_possibilities(&self) -> Vec<H2048> {

View File

@ -108,6 +108,25 @@ fn can_collect_garbage() {
assert!(client.blockchain_cache_info().blocks < 100 * 1024); assert!(client.blockchain_cache_info().blocks < 100 * 1024);
} }
#[test]
fn can_handle_long_fork() {
let client_result = generate_dummy_client(1200);
let client = client_result.reference();
for _ in 0..10 {
client.import_verified_blocks(&IoChannel::disconnected());
}
assert_eq!(1200, client.chain_info().best_block_number);
push_blocks_to_client(client, 45, 1201, 800);
push_blocks_to_client(client, 49, 1201, 800);
push_blocks_to_client(client, 53, 1201, 600);
for _ in 0..20 {
client.import_verified_blocks(&IoChannel::disconnected());
}
assert_eq!(2000, client.chain_info().best_block_number);
}
#[test] #[test]
fn can_mine() { fn can_mine() {
let dummy_blocks = get_good_dummy_block_seq(2); let dummy_blocks = get_good_dummy_block_seq(2);

View File

@ -156,10 +156,9 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult<Arc<Client>
rolling_block_number = rolling_block_number + 1; rolling_block_number = rolling_block_number + 1;
rolling_timestamp = rolling_timestamp + 10; rolling_timestamp = rolling_timestamp + 10;
if let Err(_) = client.import_block(create_test_block(&header)) { if let Err(e) = client.import_block(create_test_block(&header)) {
panic!("error importing block which is valid by definition"); panic!("error importing block which is valid by definition: {:?}", e);
} }
} }
client.flush_queue(); client.flush_queue();
client.import_verified_blocks(&IoChannel::disconnected()); client.import_verified_blocks(&IoChannel::disconnected());
@ -170,6 +169,34 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult<Arc<Client>
} }
} }
pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting_number: usize, block_number: usize) {
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let state_root = test_engine.spec().genesis_header().state_root;
let mut rolling_hash = client.chain_info().best_block_hash;
let mut rolling_block_number = starting_number as u64;
let mut rolling_timestamp = timestamp_salt + starting_number as u64 * 10;
for _ in 0..block_number {
let mut header = Header::new();
header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap());
header.timestamp = rolling_timestamp;
header.number = rolling_block_number;
header.parent_hash = rolling_hash;
header.state_root = state_root.clone();
rolling_hash = header.hash();
rolling_block_number = rolling_block_number + 1;
rolling_timestamp = rolling_timestamp + 10;
if let Err(e) = client.import_block(create_test_block(&header)) {
panic!("error importing block which is valid by definition: {:?}", e);
}
}
}
pub fn get_test_client_with_blocks(blocks: Vec<Bytes>) -> GuardedTempResult<Arc<Client>> { pub fn get_test_client_with_blocks(blocks: Vec<Bytes>) -> GuardedTempResult<Arc<Client>> {
let dir = RandomTempPath::new(); let dir = RandomTempPath::new();
let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap();
@ -253,18 +280,29 @@ pub fn get_temp_state_in(path: &Path) -> State {
pub fn get_good_dummy_block_seq(count: usize) -> Vec<Bytes> { pub fn get_good_dummy_block_seq(count: usize) -> Vec<Bytes> {
let test_spec = get_test_spec(); let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap(); let test_engine = test_spec.to_engine().unwrap();
let mut parent = test_engine.spec().genesis_header().hash(); get_good_dummy_block_fork_seq(1, count, &test_engine.spec().genesis_header().hash())
}
pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec<Bytes> {
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let mut rolling_timestamp = start_number as u64 * 10;
let mut parent = *parent_hash;
let mut r = Vec::new(); let mut r = Vec::new();
for i in 1 .. count + 1 { for i in start_number .. start_number + count + 1 {
let mut block_header = Header::new(); let mut block_header = Header::new();
block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); block_header.difficulty = U256::from(i).mul(U256([0, 1, 0, 0]));
block_header.timestamp = i as u64; block_header.timestamp = rolling_timestamp;
block_header.number = i as u64; block_header.number = i as u64;
block_header.parent_hash = parent; block_header.parent_hash = parent;
block_header.state_root = test_engine.spec().genesis_header().state_root; block_header.state_root = test_engine.spec().genesis_header().state_root;
parent = block_header.hash(); parent = block_header.hash();
rolling_timestamp = rolling_timestamp + 10;
r.push(create_test_block(&block_header)); r.push(create_test_block(&block_header));
} }
r r
} }

View File

@ -0,0 +1,28 @@
// 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 <http://www.gnu.org/licenses/>.
use error::Error;
use header::Header;
use super::Verifier;
use super::verification;
pub struct CanonVerifier;
impl Verifier for CanonVerifier {
fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error> {
verification::verify_block_final(expected, got)
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
pub mod verification;
pub mod verifier;
mod canon_verifier;
mod noop_verifier;
pub use self::verification::*;
pub use self::verifier::Verifier;
pub use self::canon_verifier::CanonVerifier;
pub use self::noop_verifier::NoopVerifier;

View File

@ -0,0 +1,27 @@
// 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 <http://www.gnu.org/licenses/>.
use error::Error;
use header::Header;
use super::Verifier;
pub struct NoopVerifier;
impl Verifier for NoopVerifier {
fn verify_block_final(_expected: &Header, _got: &Header) -> Result<(), Error> {
Ok(())
}
}

View File

@ -0,0 +1,23 @@
// 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 <http://www.gnu.org/licenses/>.
use error::Error;
use header::Header;
/// Should be used to verify blocks.
pub trait Verifier: Send + Sync {
fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error>;
}

View File

@ -65,6 +65,7 @@ Usage:
Options: Options:
--chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file --chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file
or frontier, mainnet, morden, or testnet [default: frontier]. or frontier, mainnet, morden, or testnet [default: frontier].
--archive Client should not prune the state/storage trie.
-d --db-path PATH Specify the database & configuration directory path [default: $HOME/.parity] -d --db-path PATH Specify the database & configuration directory path [default: $HOME/.parity]
--keys-path PATH Specify the path for JSON key files to be found [default: $HOME/.web3/keys] --keys-path PATH Specify the path for JSON key files to be found [default: $HOME/.web3/keys]
@ -102,6 +103,7 @@ struct Args {
flag_chain: String, flag_chain: String,
flag_db_path: String, flag_db_path: String,
flag_keys_path: String, flag_keys_path: String,
flag_archive: bool,
flag_no_bootstrap: bool, flag_no_bootstrap: bool,
flag_listen_address: String, flag_listen_address: String,
flag_public_address: Option<String>, flag_public_address: Option<String>,
@ -311,6 +313,7 @@ impl Configuration {
let mut client_config = ClientConfig::default(); let mut client_config = ClientConfig::default();
client_config.blockchain.pref_cache_size = self.args.flag_cache_pref_size; client_config.blockchain.pref_cache_size = self.args.flag_cache_pref_size;
client_config.blockchain.max_cache_size = self.args.flag_cache_max_size; client_config.blockchain.max_cache_size = self.args.flag_cache_max_size;
client_config.prefer_journal = !self.args.flag_archive;
client_config.queue.max_mem_use = self.args.flag_queue_max_size; client_config.queue.max_mem_use = self.args.flag_queue_max_size;
let mut service = ClientService::start(client_config, spec, net_settings, &Path::new(&self.path())).unwrap(); let mut service = ClientService::start(client_config, spec, net_settings, &Path::new(&self.path())).unwrap();
let client = service.client().clone(); let client = service.client().clone();

View File

@ -20,6 +20,7 @@ ethash = { path = "../ethash" }
ethsync = { path = "../sync" } ethsync = { path = "../sync" }
clippy = { version = "0.0.44", optional = true } clippy = { version = "0.0.44", optional = true }
rustc-serialize = "0.3" rustc-serialize = "0.3"
transient-hashmap = "0.1"
serde_macros = { version = "0.7.0", optional = true } serde_macros = { version = "0.7.0", optional = true }
[build-dependencies] [build-dependencies]

View File

@ -9,8 +9,8 @@ mod inner {
pub fn main() { pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("src/lib.rs.in"); let src = Path::new("src/v1/types/mod.rs.in");
let dst = Path::new(&out_dir).join("lib.rs"); let dst = Path::new(&out_dir).join("mod.rs");
let mut registry = syntex::Registry::new(); let mut registry = syntex::Registry::new();

View File

@ -27,9 +27,35 @@ extern crate jsonrpc_http_server;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate ethcore; extern crate ethcore;
extern crate ethsync; extern crate ethsync;
extern crate transient_hashmap;
#[cfg(feature = "serde_macros")] use self::jsonrpc_core::{IoHandler, IoDelegate};
include!("lib.rs.in");
#[cfg(not(feature = "serde_macros"))] pub mod v1;
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
/// Http server.
pub struct HttpServer {
handler: IoHandler,
threads: usize
}
impl HttpServer {
/// Construct new http server object with given number of threads.
pub fn new(threads: usize) -> HttpServer {
HttpServer {
handler: IoHandler::new(),
threads: threads
}
}
/// Add io delegate.
pub fn add_delegate<D>(&mut self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Start server asynchronously in new thread
pub fn start_async(self, addr: &str, cors_domain: &str) {
let server = jsonrpc_http_server::Server::new(self.handler, self.threads);
server.start_async(addr, jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain.to_owned()))
}
}

View File

@ -1,30 +0,0 @@
use self::jsonrpc_core::{IoHandler, IoDelegate};
pub mod v1;
/// Http server.
pub struct HttpServer {
handler: IoHandler,
threads: usize
}
impl HttpServer {
/// Construct new http server object with given number of threads.
pub fn new(threads: usize) -> HttpServer {
HttpServer {
handler: IoHandler::new(),
threads: threads
}
}
/// Add io delegate.
pub fn add_delegate<D>(&mut self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Start server asynchronously in new thread
pub fn start_async(self, addr: &str, cors_domain: &str) {
let server = jsonrpc_http_server::Server::new(self.handler, self.threads);
server.start_async(addr, jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain.to_owned()))
}
}

21
rpc/src/v1/helpers/mod.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
mod poll_manager;
mod poll_filter;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::PollFilter;

View File

@ -0,0 +1,10 @@
//! Helper type with all filter possibilities.
use ethcore::filter::Filter;
#[derive(Clone)]
pub enum PollFilter {
Block,
PendingTransaction,
Logs(Filter)
}

View File

@ -0,0 +1,144 @@
// 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 <http://www.gnu.org/licenses/>.
//! Indexes all rpc poll requests.
use transient_hashmap::{TransientHashMap, Timer, StandardTimer};
/// Lifetime of poll (in seconds).
const POLL_LIFETIME: u64 = 60;
pub type PollId = usize;
pub type BlockNumber = u64;
pub struct PollInfo<F> {
pub filter: F,
pub block_number: BlockNumber
}
impl<F> Clone for PollInfo<F> where F: Clone {
fn clone(&self) -> Self {
PollInfo {
filter: self.filter.clone(),
block_number: self.block_number.clone()
}
}
}
/// Indexes all poll requests.
///
/// Lazily garbage collects unused polls info.
pub struct PollManager<F, T = StandardTimer> where T: Timer {
polls: TransientHashMap<PollId, PollInfo<F>, T>,
next_available_id: PollId
}
impl<F> PollManager<F, StandardTimer> {
/// Creates new instance of indexer.
pub fn new() -> Self {
PollManager::new_with_timer(Default::default())
}
}
impl<F, T> PollManager<F, T> where T: Timer {
pub fn new_with_timer(timer: T) -> Self {
PollManager {
polls: TransientHashMap::new_with_timer(POLL_LIFETIME, timer),
next_available_id: 0,
}
}
/// Returns id which can be used for new poll.
///
/// Stores information when last poll happend.
pub fn create_poll(&mut self, filter: F, block: BlockNumber) -> PollId {
self.polls.prune();
let id = self.next_available_id;
self.next_available_id += 1;
self.polls.insert(id, PollInfo {
filter: filter,
block_number: block,
});
id
}
/// Updates information when last poll happend.
pub fn update_poll(&mut self, id: &PollId, block: BlockNumber) {
self.polls.prune();
if let Some(info) = self.polls.get_mut(id) {
info.block_number = block;
}
}
/// Returns number of block when last poll happend.
pub fn get_poll_info(&mut self, id: &PollId) -> Option<&PollInfo<F>> {
self.polls.prune();
self.polls.get(id)
}
/// Removes poll info.
pub fn remove_poll(&mut self, id: &PollId) {
self.polls.remove(id);
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use transient_hashmap::Timer;
use v1::helpers::PollManager;
struct TestTimer<'a> {
time: &'a RefCell<i64>,
}
impl<'a> Timer for TestTimer<'a> {
fn get_time(&self) -> i64 {
*self.time.borrow()
}
}
#[test]
fn test_poll_indexer() {
let time = RefCell::new(0);
let timer = TestTimer {
time: &time,
};
let mut indexer = PollManager::new_with_timer(timer);
assert_eq!(indexer.create_poll(false, 20), 0);
assert_eq!(indexer.create_poll(true, 20), 1);
*time.borrow_mut() = 10;
indexer.update_poll(&0, 21);
assert_eq!(indexer.get_poll_info(&0).unwrap().filter, false);
assert_eq!(indexer.get_poll_info(&0).unwrap().block_number, 21);
*time.borrow_mut() = 30;
indexer.update_poll(&1, 23);
assert_eq!(indexer.get_poll_info(&1).unwrap().filter, true);
assert_eq!(indexer.get_poll_info(&1).unwrap().block_number, 23);
*time.borrow_mut() = 75;
indexer.update_poll(&0, 30);
assert!(indexer.get_poll_info(&0).is_none());
assert_eq!(indexer.get_poll_info(&1).unwrap().filter, true);
assert_eq!(indexer.get_poll_info(&1).unwrap().block_number, 23);
indexer.remove_poll(&1);
assert!(indexer.get_poll_info(&1).is_none());
}
}

View File

@ -15,11 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Eth rpc implementation. //! Eth rpc implementation.
use std::collections::HashMap;
use std::sync::{Arc, Weak, Mutex, RwLock};
use ethsync::{EthSync, SyncState}; use ethsync::{EthSync, SyncState};
use jsonrpc_core::*; use jsonrpc_core::*;
use util::numbers::*; use util::numbers::*;
use util::sha3::*; use util::sha3::*;
use util::standard::{RwLock, HashMap, Arc, Weak};
use util::rlp::encode; use util::rlp::encode;
use ethcore::client::*; use ethcore::client::*;
use ethcore::block::{IsBlock}; use ethcore::block::{IsBlock};
@ -29,6 +30,7 @@ use ethcore::ethereum::Ethash;
use ethcore::ethereum::denominations::shannon; use ethcore::ethereum::denominations::shannon;
use v1::traits::{Eth, EthFilter}; use v1::traits::{Eth, EthFilter};
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, OptionalValue, Index, Filter, Log}; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, OptionalValue, Index, Filter, Log};
use v1::helpers::{PollFilter, PollManager};
/// Eth rpc implementation. /// Eth rpc implementation.
pub struct EthClient { pub struct EthClient {
@ -255,28 +257,98 @@ impl Eth for EthClient {
/// Eth filter rpc implementation. /// Eth filter rpc implementation.
pub struct EthFilterClient { pub struct EthFilterClient {
client: Weak<Client> client: Weak<Client>,
polls: Mutex<PollManager<PollFilter>>,
} }
impl EthFilterClient { impl EthFilterClient {
/// Creates new Eth filter client. /// Creates new Eth filter client.
pub fn new(client: &Arc<Client>) -> Self { pub fn new(client: &Arc<Client>) -> Self {
EthFilterClient { EthFilterClient {
client: Arc::downgrade(client) client: Arc::downgrade(client),
polls: Mutex::new(PollManager::new())
} }
} }
} }
impl EthFilter for EthFilterClient { impl EthFilter for EthFilterClient {
fn new_block_filter(&self, _params: Params) -> Result<Value, Error> { fn new_filter(&self, params: Params) -> Result<Value, Error> {
Ok(Value::U64(0)) from_params::<(Filter,)>(params)
.and_then(|(filter,)| {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Logs(filter.into()), take_weak!(self.client).chain_info().best_block_number);
to_value(&U256::from(id))
})
} }
fn new_pending_transaction_filter(&self, _params: Params) -> Result<Value, Error> { fn new_block_filter(&self, params: Params) -> Result<Value, Error> {
Ok(Value::U64(1)) match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Block, take_weak!(self.client).chain_info().best_block_number);
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
} }
fn filter_changes(&self, _: Params) -> Result<Value, Error> { fn new_pending_transaction_filter(&self, params: Params) -> Result<Value, Error> {
to_value(&take_weak!(self.client).chain_info().best_block_hash).map(|v| Value::Array(vec![v])) match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::PendingTransaction, take_weak!(self.client).chain_info().best_block_number);
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn filter_changes(&self, params: Params) -> Result<Value, Error> {
let client = take_weak!(self.client);
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let info = self.polls.lock().unwrap().get_poll_info(&index.value()).cloned();
match info {
None => Ok(Value::Array(vec![] as Vec<Value>)),
Some(info) => match info.filter {
PollFilter::Block => {
let current_number = client.chain_info().best_block_number;
let hashes = (info.block_number..current_number).into_iter()
.map(BlockId::Number)
.filter_map(|id| client.block_hash(id))
.collect::<Vec<H256>>();
self.polls.lock().unwrap().update_poll(&index.value(), current_number);
to_value(&hashes)
},
PollFilter::PendingTransaction => {
// TODO: fix implementation once TransactionQueue is merged
to_value(&vec![] as &Vec<H256>)
},
PollFilter::Logs(mut filter) => {
filter.from_block = BlockId::Number(info.block_number);
filter.to_block = BlockId::Latest;
let logs = client.logs(filter)
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
let current_number = client.chain_info().best_block_number;
self.polls.lock().unwrap().update_poll(&index.value(), current_number);
to_value(&logs)
}
}
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
self.polls.lock().unwrap().remove_poll(&index.value());
to_value(&true)
})
} }
} }

View File

@ -28,7 +28,9 @@ macro_rules! take_weak {
mod web3; mod web3;
mod eth; mod eth;
mod net; mod net;
mod personal;
pub use self::web3::Web3Client; pub use self::web3::Web3Client;
pub use self::eth::{EthClient, EthFilterClient}; pub use self::eth::{EthClient, EthFilterClient};
pub use self::net::NetClient; pub use self::net::NetClient;
pub use self::personal::PersonalClient;

View File

@ -0,0 +1,78 @@
// 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 <http://www.gnu.org/licenses/>.
//! Account management (personal) rpc implementation
use std::sync::{Arc, Weak};
use jsonrpc_core::*;
use v1::traits::Personal;
use util::keys::store::*;
use util::Address;
use std::sync::RwLock;
/// Account management (personal) rpc implementation.
pub struct PersonalClient {
secret_store: Weak<RwLock<SecretStore>>,
}
impl PersonalClient {
/// Creates new PersonalClient
pub fn new(store: &Arc<RwLock<SecretStore>>) -> Self {
PersonalClient {
secret_store: Arc::downgrade(store),
}
}
}
impl Personal for PersonalClient {
fn accounts(&self, _: Params) -> Result<Value, Error> {
let store_wk = take_weak!(self.secret_store);
let store = store_wk.read().unwrap();
match store.accounts() {
Ok(account_list) => {
Ok(Value::Array(account_list.iter()
.map(|&(account, _)| Value::String(format!("{:?}", account)))
.collect::<Vec<Value>>())
)
}
Err(_) => Err(Error::internal_error())
}
}
fn new_account(&self, params: Params) -> Result<Value, Error> {
from_params::<(String, )>(params).and_then(
|(pass, )| {
let store_wk = take_weak!(self.secret_store);
let mut store = store_wk.write().unwrap();
match store.new_account(&pass) {
Ok(address) => Ok(Value::String(format!("{:?}", address))),
Err(_) => Err(Error::internal_error())
}
}
)
}
fn unlock_account(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, String, u64)>(params).and_then(
|(account, account_pass, _)|{
let store_wk = take_weak!(self.secret_store);
let store = store_wk.read().unwrap();
match store.unlock_account(&account, &account_pass) {
Ok(_) => Ok(Value::Bool(true)),
Err(_) => Ok(Value::Bool(false)),
}
})
}
}

View File

@ -30,9 +30,7 @@ impl Web3Client {
impl Web3 for Web3Client { impl Web3 for Web3Client {
fn client_version(&self, params: Params) -> Result<Value, Error> { fn client_version(&self, params: Params) -> Result<Value, Error> {
match params { match params {
Params::None => { Params::None => Ok(Value::String(version().to_owned().replace("Parity/", "Parity//"))),
Ok(Value::String(version().to_owned().replace("Parity/", "Parity//"))),
}
_ => Err(Error::invalid_params()) _ => Err(Error::invalid_params())
} }
} }

View File

@ -23,6 +23,7 @@ mod impls;
mod types; mod types;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod helpers;
pub use self::traits::{Web3, Eth, EthFilter, Net}; pub use self::traits::{Web3, Eth, EthFilter, Personal, Net};
pub use self::impls::*; pub use self::impls::*;

View File

@ -23,7 +23,9 @@ macro_rules! rpc_unimplemented {
pub mod web3; pub mod web3;
pub mod eth; pub mod eth;
pub mod net; pub mod net;
pub mod personal;
pub use self::web3::Web3; pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter}; pub use self::eth::{Eth, EthFilter};
pub use self::net::Net; pub use self::net::Net;
pub use self::personal::Personal;

View File

@ -0,0 +1,41 @@
// 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 <http://www.gnu.org/licenses/>.
//! Personal rpc interface.
use std::sync::Arc;
use jsonrpc_core::*;
/// Personal rpc interface.
pub trait Personal: Sized + Send + Sync + 'static {
/// Lists all stored accounts
fn accounts(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Creates new account (it becomes new current unlocked account)
fn new_account(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Unlocks specified account for use (can only be one unlocked account at one moment)
fn unlock_account(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("personal_listAccounts", Personal::accounts);
delegate.add_method("personal_newAccount", Personal::new_account);
delegate.add_method("personal_unlockAccount", Personal::unlock_account);
delegate
}
}

View File

@ -14,22 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
mod block; #[cfg(feature = "serde_macros")]
mod block_number; include!("mod.rs.in");
mod bytes;
mod filter;
mod index;
mod log;
mod optionals;
mod sync;
mod transaction;
pub use self::block::{Block, BlockTransactions}; #[cfg(not(feature = "serde_macros"))]
pub use self::block_number::BlockNumber; include!(concat!(env!("OUT_DIR"), "/mod.rs"));
pub use self::bytes::Bytes;
pub use self::filter::Filter;
pub use self::index::Index;
pub use self::log::Log;
pub use self::optionals::OptionalValue;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;

View File

@ -0,0 +1,35 @@
// 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 <http://www.gnu.org/licenses/>.
mod block;
mod block_number;
mod bytes;
mod filter;
mod index;
mod log;
mod optionals;
mod sync;
mod transaction;
pub use self::block::{Block, BlockTransactions};
pub use self::block_number::BlockNumber;
pub use self::bytes::Bytes;
pub use self::filter::Filter;
pub use self::index::Index;
pub use self::log::Log;
pub use self::optionals::OptionalValue;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;

View File

@ -9,7 +9,7 @@ authors = ["Ethcore <admin@ethcore.io"]
[dependencies] [dependencies]
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
ethcore = { path = ".." } ethcore = { path = "../ethcore" }
clippy = { version = "0.0.44", optional = true } clippy = { version = "0.0.44", optional = true }
log = "0.3" log = "0.3"
env_logger = "0.3" env_logger = "0.3"

View File

@ -34,7 +34,7 @@ use rayon::prelude::*;
use std::mem::{replace}; use std::mem::{replace};
use ethcore::views::{HeaderView, BlockView}; use ethcore::views::{HeaderView, BlockView};
use ethcore::header::{BlockNumber, Header as BlockHeader}; use ethcore::header::{BlockNumber, Header as BlockHeader};
use ethcore::client::{BlockChainClient, BlockStatus, BlockId}; use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo};
use range_collection::{RangeCollection, ToUsize, FromUsize}; use range_collection::{RangeCollection, ToUsize, FromUsize};
use ethcore::error::*; use ethcore::error::*;
use ethcore::block::Block; use ethcore::block::Block;
@ -482,7 +482,7 @@ impl ChainSync {
peer.latest_number = Some(header.number()); peer.latest_number = Some(header.number());
} }
// TODO: Decompose block and add to self.headers and self.bodies instead // TODO: Decompose block and add to self.headers and self.bodies instead
if header.number == From::from(self.current_base_block() + 1) { if header.number <= From::from(self.current_base_block() + 1) {
match io.chain().import_block(block_rlp.as_raw().to_vec()) { match io.chain().import_block(block_rlp.as_raw().to_vec()) {
Err(Error::Import(ImportError::AlreadyInChain)) => { Err(Error::Import(ImportError::AlreadyInChain)) => {
trace!(target: "sync", "New block already in chain {:?}", h); trace!(target: "sync", "New block already in chain {:?}", h);
@ -491,7 +491,10 @@ impl ChainSync {
trace!(target: "sync", "New block already queued {:?}", h); trace!(target: "sync", "New block already queued {:?}", h);
}, },
Ok(_) => { Ok(_) => {
self.last_imported_block = Some(header.number); if self.current_base_block() < header.number {
self.last_imported_block = Some(header.number);
self.remove_downloaded_blocks(header.number);
}
trace!(target: "sync", "New block queued {:?}", h); trace!(target: "sync", "New block queued {:?}", h);
}, },
Err(Error::Block(BlockError::UnknownParent(p))) => { Err(Error::Block(BlockError::UnknownParent(p))) => {
@ -1171,9 +1174,7 @@ impl ChainSync {
} }
/// returns peer ids that have less blocks than our chain /// returns peer ids that have less blocks than our chain
fn get_lagging_peers(&mut self, io: &SyncIo) -> Vec<(PeerId, BlockNumber)> { fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec<(PeerId, BlockNumber)> {
let chain = io.chain();
let chain_info = chain.chain_info();
let latest_hash = chain_info.best_block_hash; let latest_hash = chain_info.best_block_hash;
let latest_number = chain_info.best_block_number; let latest_number = chain_info.best_block_number;
self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)|
@ -1192,9 +1193,9 @@ impl ChainSync {
} }
/// propagates latest block to lagging peers /// propagates latest block to lagging peers
fn propagate_blocks(&mut self, local_best: &H256, best_number: BlockNumber, io: &mut SyncIo) -> usize { fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo) -> usize {
let updated_peers = { let updated_peers = {
let lagging_peers = self.get_lagging_peers(io); let lagging_peers = self.get_lagging_peers(chain_info, io);
// sqrt(x)/x scaled to max u32 // 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 fraction = (self.peers.len() as f64).powf(-0.5).mul(u32::max_value() as f64).round() as u32;
@ -1211,30 +1212,30 @@ impl ChainSync {
for peer_id in updated_peers { for peer_id in updated_peers {
let rlp = ChainSync::create_latest_block_rlp(io.chain()); let rlp = ChainSync::create_latest_block_rlp(io.chain());
self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp);
self.peers.get_mut(&peer_id).unwrap().latest_hash = local_best.clone(); self.peers.get_mut(&peer_id).unwrap().latest_hash = chain_info.best_block_hash.clone();
self.peers.get_mut(&peer_id).unwrap().latest_number = Some(best_number); self.peers.get_mut(&peer_id).unwrap().latest_number = Some(chain_info.best_block_number);
sent = sent + 1; sent = sent + 1;
} }
sent sent
} }
/// propagates new known hashes to all peers /// propagates new known hashes to all peers
fn propagate_new_hashes(&mut self, local_best: &H256, best_number: BlockNumber, io: &mut SyncIo) -> usize { fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo) -> usize {
let updated_peers = self.get_lagging_peers(io); let updated_peers = self.get_lagging_peers(chain_info, io);
let mut sent = 0; let mut sent = 0;
let last_parent = HeaderView::new(&io.chain().block_header(BlockId::Hash(local_best.clone())).unwrap()).parent_hash(); let last_parent = HeaderView::new(&io.chain().block_header(BlockId::Hash(chain_info.best_block_hash.clone())).unwrap()).parent_hash();
for (peer_id, peer_number) in updated_peers { for (peer_id, peer_number) in updated_peers {
let mut peer_best = self.peers.get(&peer_id).unwrap().latest_hash.clone(); let mut peer_best = self.peers.get(&peer_id).unwrap().latest_hash.clone();
if best_number - peer_number > MAX_PEERS_PROPAGATION as BlockNumber { if chain_info.best_block_number - peer_number > MAX_PEERS_PROPAGATION as BlockNumber {
// If we think peer is too far behind just send one latest hash // If we think peer is too far behind just send one latest hash
peer_best = last_parent.clone(); peer_best = last_parent.clone();
} }
sent = sent + match ChainSync::create_new_hashes_rlp(io.chain(), &peer_best, &local_best) { sent = sent + match ChainSync::create_new_hashes_rlp(io.chain(), &peer_best, &chain_info.best_block_hash) {
Some(rlp) => { Some(rlp) => {
{ {
let peer = self.peers.get_mut(&peer_id).unwrap(); let peer = self.peers.get_mut(&peer_id).unwrap();
peer.latest_hash = local_best.clone(); peer.latest_hash = chain_info.best_block_hash.clone();
peer.latest_number = Some(best_number); peer.latest_number = Some(chain_info.best_block_number);
} }
self.send_packet(io, peer_id, NEW_BLOCK_HASHES_PACKET, rlp); self.send_packet(io, peer_id, NEW_BLOCK_HASHES_PACKET, rlp);
1 1
@ -1254,8 +1255,8 @@ impl ChainSync {
pub fn chain_blocks_verified(&mut self, io: &mut SyncIo) { pub fn chain_blocks_verified(&mut self, io: &mut SyncIo) {
let chain = io.chain().chain_info(); let chain = io.chain().chain_info();
if (((chain.best_block_number as i64) - (self.last_send_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION { if (((chain.best_block_number as i64) - (self.last_send_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION {
let blocks = self.propagate_blocks(&chain.best_block_hash, chain.best_block_number, io); let blocks = self.propagate_blocks(&chain, io);
let hashes = self.propagate_new_hashes(&chain.best_block_hash, chain.best_block_number, io); let hashes = self.propagate_new_hashes(&chain, io);
if blocks != 0 || hashes != 0 { if blocks != 0 || hashes != 0 {
trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes); trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes);
} }
@ -1435,9 +1436,10 @@ mod tests {
client.add_blocks(100, BlocksWith::Uncle); client.add_blocks(100, BlocksWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(10)); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(10));
let chain_info = client.chain_info();
let io = TestIo::new(&mut client, &mut queue, None); let io = TestIo::new(&mut client, &mut queue, None);
let lagging_peers = sync.get_lagging_peers(&io); let lagging_peers = sync.get_lagging_peers(&chain_info, &io);
assert_eq!(1, lagging_peers.len()) assert_eq!(1, lagging_peers.len())
} }
@ -1465,11 +1467,10 @@ mod tests {
client.add_blocks(100, BlocksWith::Uncle); client.add_blocks(100, BlocksWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5)); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5));
let best_hash = client.chain_info().best_block_hash.clone(); let chain_info = client.chain_info();
let best_number = client.chain_info().best_block_number;
let mut io = TestIo::new(&mut client, &mut queue, None); let mut io = TestIo::new(&mut client, &mut queue, None);
let peer_count = sync.propagate_new_hashes(&best_hash, best_number, &mut io); let peer_count = sync.propagate_new_hashes(&chain_info, &mut io);
// 1 message should be send // 1 message should be send
assert_eq!(1, io.queue.len()); assert_eq!(1, io.queue.len());
@ -1485,11 +1486,9 @@ mod tests {
client.add_blocks(100, BlocksWith::Uncle); client.add_blocks(100, BlocksWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5)); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5));
let best_hash = client.chain_info().best_block_hash.clone(); let chain_info = client.chain_info();
let best_number = client.chain_info().best_block_number;
let mut io = TestIo::new(&mut client, &mut queue, None); let mut io = TestIo::new(&mut client, &mut queue, None);
let peer_count = sync.propagate_blocks(&chain_info, &mut io);
let peer_count = sync.propagate_blocks(&best_hash, best_number, &mut io);
// 1 message should be send // 1 message should be send
assert_eq!(1, io.queue.len()); assert_eq!(1, io.queue.len());
@ -1591,11 +1590,10 @@ mod tests {
client.add_blocks(100, BlocksWith::Uncle); client.add_blocks(100, BlocksWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5)); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5));
let best_hash = client.chain_info().best_block_hash.clone(); let chain_info = client.chain_info();
let best_number = client.chain_info().best_block_number;
let mut io = TestIo::new(&mut client, &mut queue, None); let mut io = TestIo::new(&mut client, &mut queue, None);
sync.propagate_new_hashes(&best_hash, best_number, &mut io); sync.propagate_new_hashes(&chain_info, &mut io);
let data = &io.queue[0].data.clone(); let data = &io.queue[0].data.clone();
let result = sync.on_peer_new_hashes(&mut io, 0, &UntrustedRlp::new(&data)); let result = sync.on_peer_new_hashes(&mut io, 0, &UntrustedRlp::new(&data));
@ -1610,11 +1608,10 @@ mod tests {
client.add_blocks(100, BlocksWith::Uncle); client.add_blocks(100, BlocksWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5)); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5));
let best_hash = client.chain_info().best_block_hash.clone(); let chain_info = client.chain_info();
let best_number = client.chain_info().best_block_number;
let mut io = TestIo::new(&mut client, &mut queue, None); let mut io = TestIo::new(&mut client, &mut queue, None);
sync.propagate_blocks(&best_hash, best_number, &mut io); sync.propagate_blocks(&chain_info, &mut io);
let data = &io.queue[0].data.clone(); let data = &io.queue[0].data.clone();
let result = sync.on_peer_new_block(&mut io, 0, &UntrustedRlp::new(&data)); let result = sync.on_peer_new_block(&mut io, 0, &UntrustedRlp::new(&data));

View File

@ -136,6 +136,10 @@ impl BlockChainClient for TestBlockChainClient {
Some(U256::zero()) Some(U256::zero())
} }
fn block_hash(&self, _id: BlockId) -> Option<H256> {
unimplemented!();
}
fn nonce(&self, _address: &Address) -> U256 { fn nonce(&self, _address: &Address) -> U256 {
U256::zero() U256::zero()
} }

View File

@ -36,6 +36,7 @@ libc = "0.2.7"
vergen = "0.1" vergen = "0.1"
target_info = "0.1" target_info = "0.1"
bigint = { path = "bigint" } bigint = { path = "bigint" }
chrono = "0.2"
[features] [features]
default = [] default = []

View File

@ -778,6 +778,35 @@ macro_rules! construct_uint {
} }
} }
impl serde::Deserialize for $name {
fn deserialize<D>(deserializer: &mut D) -> Result<$name, D::Error>
where D: serde::Deserializer {
struct UintVisitor;
impl serde::de::Visitor for UintVisitor {
type Value = $name;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: serde::Error {
// 0x + len
if value.len() != 2 + $n_words / 8 {
return Err(serde::Error::custom("Invalid length."));
}
match $name::from_str(&value[2..]) {
Ok(val) => Ok(val),
Err(_) => { return Err(serde::Error::custom("Invalid length.")); }
}
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: serde::Error {
self.visit_str(value.as_ref())
}
}
deserializer.deserialize(UintVisitor)
}
}
impl From<u64> for $name { impl From<u64> for $name {
fn from(value: u64) -> $name { fn from(value: u64) -> $name {
let mut ret = [0; $n_words]; let mut ret = [0; $n_words];

View File

@ -25,7 +25,10 @@ use kvdb::{Database, DBTransaction, DatabaseConfig};
use std::env; use std::env;
/// Implementation of the HashDB trait for a disk-backed database with a memory overlay /// Implementation of the HashDB trait for a disk-backed database with a memory overlay
/// and latent-removal semantics. /// and, possibly, latent-removal semantics.
///
/// If `counters` is `None`, then it behaves exactly like OverlayDB. If not it behaves
/// differently:
/// ///
/// Like OverlayDB, there is a memory overlay; `commit()` must be called in order to /// Like OverlayDB, there is a memory overlay; `commit()` must be called in order to
/// write operations out to disk. Unlike OverlayDB, `remove()` operations do not take effect /// write operations out to disk. Unlike OverlayDB, `remove()` operations do not take effect
@ -34,7 +37,7 @@ use std::env;
pub struct JournalDB { pub struct JournalDB {
overlay: MemoryDB, overlay: MemoryDB,
backing: Arc<Database>, backing: Arc<Database>,
counters: Arc<RwLock<HashMap<H256, i32>>>, counters: Option<Arc<RwLock<HashMap<H256, i32>>>>,
} }
impl Clone for JournalDB { impl Clone for JournalDB {
@ -51,7 +54,8 @@ impl Clone for JournalDB {
const LATEST_ERA_KEY : [u8; 12] = [ b'l', b'a', b's', b't', 0, 0, 0, 0, 0, 0, 0, 0 ]; const LATEST_ERA_KEY : [u8; 12] = [ b'l', b'a', b's', b't', 0, 0, 0, 0, 0, 0, 0, 0 ];
const VERSION_KEY : [u8; 12] = [ b'j', b'v', b'e', b'r', 0, 0, 0, 0, 0, 0, 0, 0 ]; const VERSION_KEY : [u8; 12] = [ b'j', b'v', b'e', b'r', 0, 0, 0, 0, 0, 0, 0, 0 ];
const DB_VERSION: u32 = 3; const DB_VERSION : u32 = 3;
const DB_VERSION_NO_JOURNAL : u32 = 3 + 256;
const PADDING : [u8; 10] = [ 0u8; 10 ]; const PADDING : [u8; 10] = [ 0u8; 10 ];
@ -59,25 +63,38 @@ impl JournalDB {
/// Create a new instance from file /// Create a new instance from file
pub fn new(path: &str) -> JournalDB { pub fn new(path: &str) -> JournalDB {
Self::from_prefs(path, true)
}
/// Create a new instance from file
pub fn from_prefs(path: &str, prefer_journal: bool) -> JournalDB {
let opts = DatabaseConfig { let opts = DatabaseConfig {
prefix_size: Some(12) //use 12 bytes as prefix, this must match account_db prefix prefix_size: Some(12) //use 12 bytes as prefix, this must match account_db prefix
}; };
let backing = Database::open(&opts, path).unwrap_or_else(|e| { let backing = Database::open(&opts, path).unwrap_or_else(|e| {
panic!("Error opening state db: {}", e); panic!("Error opening state db: {}", e);
}); });
let with_journal;
if !backing.is_empty() { if !backing.is_empty() {
match backing.get(&VERSION_KEY).map(|d| d.map(|v| decode::<u32>(&v))) { match backing.get(&VERSION_KEY).map(|d| d.map(|v| decode::<u32>(&v))) {
Ok(Some(DB_VERSION)) => {}, Ok(Some(DB_VERSION)) => { with_journal = true; },
Ok(Some(DB_VERSION_NO_JOURNAL)) => { with_journal = false; },
v => panic!("Incompatible DB version, expected {}, got {:?}", DB_VERSION, v) v => panic!("Incompatible DB version, expected {}, got {:?}", DB_VERSION, v)
} }
} else { } else {
backing.put(&VERSION_KEY, &encode(&DB_VERSION)).expect("Error writing version to database"); backing.put(&VERSION_KEY, &encode(&(if prefer_journal { DB_VERSION } else { DB_VERSION_NO_JOURNAL }))).expect("Error writing version to database");
with_journal = prefer_journal;
} }
let counters = JournalDB::read_counters(&backing);
let counters = if with_journal {
Some(Arc::new(RwLock::new(JournalDB::read_counters(&backing))))
} else {
None
};
JournalDB { JournalDB {
overlay: MemoryDB::new(), overlay: MemoryDB::new(),
backing: Arc::new(backing), backing: Arc::new(backing),
counters: Arc::new(RwLock::new(counters)), counters: counters,
} }
} }
@ -94,9 +111,47 @@ impl JournalDB {
self.backing.get(&LATEST_ERA_KEY).expect("Low level database error").is_none() self.backing.get(&LATEST_ERA_KEY).expect("Low level database error").is_none()
} }
/// Commit all recent insert operations.
pub fn commit(&mut self, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
let have_counters = self.counters.is_some();
if have_counters {
self.commit_with_counters(now, id, end)
} else {
self.commit_without_counters()
}
}
/// Drain the overlay and place it into a batch for the DB.
fn batch_overlay_insertions(overlay: &mut MemoryDB, batch: &DBTransaction) -> (usize, usize) {
let mut ret = 0usize;
let mut deletes = 0usize;
for i in overlay.drain().into_iter() {
let (key, (value, rc)) = i;
if rc > 0 {
assert!(rc == 1);
batch.put(&key.bytes(), &value).expect("Low-level database error. Some issue with your hard disk?");
ret += 1;
}
if rc < 0 {
assert!(rc == -1);
ret += 1;
deletes += 1;
}
}
(ret, deletes)
}
/// Just commit the overlay into the backing DB.
fn commit_without_counters(&mut self) -> Result<u32, UtilError> {
let batch = DBTransaction::new();
let (ret, _) = Self::batch_overlay_insertions(&mut self.overlay, &batch);
try!(self.backing.write(batch));
Ok(ret as u32)
}
/// Commit all recent insert operations and historical removals from the old era /// Commit all recent insert operations and historical removals from the old era
/// to the backing database. /// to the backing database.
pub fn commit(&mut self, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> { fn commit_with_counters(&mut self, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
// journal format: // journal format:
// [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ] // [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ]
// [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ] // [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ]
@ -121,9 +176,9 @@ impl JournalDB {
// and the key is safe to delete. // and the key is safe to delete.
// record new commit's details. // record new commit's details.
debug!("commit: #{} ({}), end era: {:?}", now, id, end); trace!("commit: #{} ({}), end era: {:?}", now, id, end);
let mut counters = self.counters.as_ref().unwrap().write().unwrap();
let batch = DBTransaction::new(); let batch = DBTransaction::new();
let mut counters = self.counters.write().unwrap();
{ {
let mut index = 0usize; let mut index = 0usize;
let mut last; let mut last;
@ -192,29 +247,15 @@ impl JournalDB {
try!(batch.delete(&h)); try!(batch.delete(&h));
deletes += 1; deletes += 1;
} }
debug!("commit: Delete journal for time #{}.{}, (canon was {}): {} entries", end_era, index, canon_id, deletes); trace!("commit: Delete journal for time #{}.{}, (canon was {}): {} entries", end_era, index, canon_id, deletes);
} }
// Commit overlay insertions // Commit overlay insertions
let mut ret = 0u32; let (ret, deletes) = Self::batch_overlay_insertions(&mut self.overlay, &batch);
let mut deletes = 0usize;
for i in self.overlay.drain().into_iter() {
let (key, (value, rc)) = i;
if rc > 0 {
assert!(rc == 1);
batch.put(&key.bytes(), &value).expect("Low-level database error. Some issue with your hard disk?");
ret += 1;
}
if rc < 0 {
assert!(rc == -1);
ret += 1;
deletes += 1;
}
}
try!(self.backing.write(batch)); try!(self.backing.write(batch));
debug!("commit: Deleted {} nodes", deletes); trace!("commit: Deleted {} nodes", deletes);
Ok(ret) Ok(ret as u32)
} }
@ -262,7 +303,7 @@ impl JournalDB {
era -= 1; era -= 1;
} }
} }
debug!("Recovered {} counters", res.len()); trace!("Recovered {} counters", res.len());
res res
} }
} }

View File

@ -461,17 +461,17 @@ enum KeyFileLoadError {
pub struct KeyDirectory { pub struct KeyDirectory {
/// Directory path for key management. /// Directory path for key management.
path: String, path: String,
cache: RefCell<HashMap<Uuid, KeyFileContent>>, cache: RwLock<HashMap<Uuid, KeyFileContent>>,
cache_usage: RefCell<VecDeque<Uuid>>, cache_usage: RwLock<VecDeque<Uuid>>,
} }
impl KeyDirectory { impl KeyDirectory {
/// Initializes new cache directory context with a given `path` /// Initializes new cache directory context with a given `path`
pub fn new(path: &Path) -> KeyDirectory { pub fn new(path: &Path) -> KeyDirectory {
KeyDirectory { KeyDirectory {
cache: RefCell::new(HashMap::new()), cache: RwLock::new(HashMap::new()),
path: path.to_str().expect("Initialized key directory with empty path").to_owned(), path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
cache_usage: RefCell::new(VecDeque::new()), cache_usage: RwLock::new(VecDeque::new()),
} }
} }
@ -484,7 +484,7 @@ impl KeyDirectory {
let json_bytes = json_text.into_bytes(); let json_bytes = json_text.into_bytes();
try!(file.write(&json_bytes)); try!(file.write(&json_bytes));
} }
let mut cache = self.cache.borrow_mut(); let mut cache = self.cache.write().unwrap();
let id = key_file.id.clone(); let id = key_file.id.clone();
cache.insert(id.clone(), key_file); cache.insert(id.clone(), key_file);
Ok(id.clone()) Ok(id.clone())
@ -495,14 +495,14 @@ impl KeyDirectory {
pub fn get(&self, id: &Uuid) -> Option<KeyFileContent> { pub fn get(&self, id: &Uuid) -> Option<KeyFileContent> {
let path = self.key_path(id); let path = self.key_path(id);
{ {
let mut usage = self.cache_usage.borrow_mut(); let mut usage = self.cache_usage.write().unwrap();
usage.push_back(id.clone()); usage.push_back(id.clone());
} }
if !self.cache.borrow().contains_key(id) { if !self.cache.read().unwrap().contains_key(id) {
match KeyDirectory::load_key(&path) { match KeyDirectory::load_key(&path) {
Ok(loaded_key) => { Ok(loaded_key) => {
self.cache.borrow_mut().insert(id.to_owned(), loaded_key); self.cache.write().unwrap().insert(id.to_owned(), loaded_key);
} }
Err(error) => { Err(error) => {
warn!(target: "sstore", "error loading key {:?}: {:?}", id, error); warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
@ -512,7 +512,7 @@ impl KeyDirectory {
} }
// todo: replace with Ref::map when it stabilized to avoid copies // todo: replace with Ref::map when it stabilized to avoid copies
Some(self.cache.borrow().get(id) Some(self.cache.read().unwrap().get(id)
.expect("Key should be there, we have just inserted or checked it.") .expect("Key should be there, we have just inserted or checked it.")
.clone()) .clone())
} }
@ -524,7 +524,7 @@ impl KeyDirectory {
/// Removes keys that never been requested during last `MAX_USAGE_TRACK` times /// Removes keys that never been requested during last `MAX_USAGE_TRACK` times
pub fn collect_garbage(&mut self) { pub fn collect_garbage(&mut self) {
let mut cache_usage = self.cache_usage.borrow_mut(); let mut cache_usage = self.cache_usage.write().unwrap();
let total_usages = cache_usage.len(); let total_usages = cache_usage.len();
let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize; let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize;
@ -532,31 +532,31 @@ impl KeyDirectory {
cache_usage.drain(..untracked_usages); cache_usage.drain(..untracked_usages);
} }
if self.cache.borrow().len() <= MAX_CACHE_USAGE_TRACK { return; } if self.cache.read().unwrap().len() <= MAX_CACHE_USAGE_TRACK { return; }
let uniqs: HashSet<&Uuid> = cache_usage.iter().collect(); let uniqs: HashSet<&Uuid> = cache_usage.iter().collect();
let removes:Vec<Uuid> = { let removes:Vec<Uuid> = {
let cache = self.cache.borrow(); let cache = self.cache.read().unwrap();
cache.keys().cloned().filter(|key| !uniqs.contains(key)).collect() cache.keys().cloned().filter(|key| !uniqs.contains(key)).collect()
}; };
if removes.is_empty() { return; } if removes.is_empty() { return; }
let mut cache = self.cache.borrow_mut(); let mut cache = self.cache.write().unwrap();
for key in removes { cache.remove(&key); } for key in removes { cache.remove(&key); }
} }
/// Reports how many keys are currently cached. /// Reports how many keys are currently cached.
pub fn cache_size(&self) -> usize { pub fn cache_size(&self) -> usize {
self.cache.borrow().len() self.cache.read().unwrap().len()
} }
/// Removes key file from key directory /// Removes key file from key directory
pub fn delete(&mut self, id: &Uuid) -> Result<(), ::std::io::Error> { pub fn delete(&mut self, id: &Uuid) -> Result<(), ::std::io::Error> {
let path = self.key_path(id); let path = self.key_path(id);
if !self.cache.borrow().contains_key(id) { if !self.cache.read().unwrap().contains_key(id) {
return match fs::remove_file(&path) { return match fs::remove_file(&path) {
Ok(_) => { Ok(_) => {
self.cache.borrow_mut().remove(&id); self.cache.write().unwrap().remove(&id);
Ok(()) Ok(())
}, },
Err(e) => Err(e) Err(e) => Err(e)

View File

@ -22,6 +22,7 @@ use rcrypto::pbkdf2::*;
use rcrypto::scrypt::*; use rcrypto::scrypt::*;
use rcrypto::hmac::*; use rcrypto::hmac::*;
use crypto; use crypto;
use chrono::*;
const KEY_LENGTH: u32 = 32; const KEY_LENGTH: u32 = 32;
const KEY_ITERATIONS: u32 = 10240; const KEY_ITERATIONS: u32 = 10240;
@ -55,9 +56,26 @@ pub enum EncryptedHashMapError {
InvalidValueFormat(FromBytesError), InvalidValueFormat(FromBytesError),
} }
/// Error retrieving value from encrypted hashmap
#[derive(Debug)]
pub enum SigningError {
/// Account passed does not exist
NoAccount,
/// Account passed is not unlocked
AccountNotUnlocked,
/// Invalid secret in store
InvalidSecret
}
/// Represent service for storing encrypted arbitrary data /// Represent service for storing encrypted arbitrary data
pub struct SecretStore { pub struct SecretStore {
directory: KeyDirectory directory: KeyDirectory,
unlocks: RwLock<HashMap<Address, AccountUnlock>>,
}
struct AccountUnlock {
secret: H256,
expires: DateTime<UTC>,
} }
impl SecretStore { impl SecretStore {
@ -72,7 +90,8 @@ impl SecretStore {
/// new instance of Secret Store in specific directory /// new instance of Secret Store in specific directory
pub fn new_in(path: &Path) -> SecretStore { pub fn new_in(path: &Path) -> SecretStore {
SecretStore { SecretStore {
directory: KeyDirectory::new(path) directory: KeyDirectory::new(path),
unlocks: RwLock::new(HashMap::new()),
} }
} }
@ -120,9 +139,57 @@ impl SecretStore {
#[cfg(test)] #[cfg(test)]
fn new_test(path: &::devtools::RandomTempPath) -> SecretStore { fn new_test(path: &::devtools::RandomTempPath) -> SecretStore {
SecretStore { SecretStore {
directory: KeyDirectory::new(path.as_path()) directory: KeyDirectory::new(path.as_path()),
unlocks: RwLock::new(HashMap::new()),
} }
} }
/// Unlocks account for use
pub fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
let secret_id = try!(self.account(&account).ok_or(EncryptedHashMapError::UnknownIdentifier));
let secret = try!(self.get(&secret_id, pass));
{
let mut write_lock = self.unlocks.write().unwrap();
let mut unlock = write_lock.entry(*account)
.or_insert_with(|| AccountUnlock { secret: secret, expires: UTC::now() });
unlock.secret = secret;
unlock.expires = UTC::now() + Duration::minutes(20);
}
Ok(())
}
/// Creates new account
pub fn new_account(&mut self, pass: &str) -> Result<Address, ::std::io::Error> {
let secret = H256::random();
let key_id = H128::random();
self.insert(key_id.clone(), secret, pass);
let mut key_file = self.directory.get(&key_id).expect("the key was just inserted");
let address = Address::random();
key_file.account = Some(address);
try!(self.directory.save(key_file));
Ok(address)
}
/// Signs message with unlocked account
pub fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
let read_lock = self.unlocks.read().unwrap();
let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked));
match crypto::KeyPair::from_secret(unlock.secret) {
Ok(pair) => match pair.sign(message) {
Ok(signature) => Ok(signature),
Err(_) => Err(SigningError::InvalidSecret)
},
Err(_) => Err(SigningError::InvalidSecret)
}
}
/// Returns secret for unlocked account
pub fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError> {
let read_lock = self.unlocks.read().unwrap();
let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked));
Ok(unlock.secret as crypto::Secret)
}
} }
fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) { fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) {
@ -369,6 +436,40 @@ mod tests {
assert_eq!(4, sstore.directory.list().unwrap().len()) assert_eq!(4, sstore.directory.list().unwrap().len())
} }
#[test]
fn can_create_account() {
let temp = RandomTempPath::create_dir();
let mut sstore = SecretStore::new_test(&temp);
sstore.new_account("123").unwrap();
assert_eq!(1, sstore.accounts().unwrap().len());
}
#[test]
fn can_unlock_account() {
let temp = RandomTempPath::create_dir();
let mut sstore = SecretStore::new_test(&temp);
let address = sstore.new_account("123").unwrap();
let secret = sstore.unlock_account(&address, "123");
assert!(secret.is_ok());
}
#[test]
fn can_sign_data() {
let temp = RandomTempPath::create_dir();
let address = {
let mut sstore = SecretStore::new_test(&temp);
sstore.new_account("334").unwrap()
};
let signature = {
let sstore = SecretStore::new_test(&temp);
sstore.unlock_account(&address, "334").unwrap();
sstore.sign(&address, &H256::random()).unwrap()
};
assert!(signature != x!(0));
}
#[test] #[test]
fn can_import_account() { fn can_import_account() {
use keys::directory::{KeyFileContent, KeyFileCrypto}; use keys::directory::{KeyFileContent, KeyFileCrypto};

View File

@ -111,6 +111,7 @@ extern crate rustc_version;
extern crate target_info; extern crate target_info;
extern crate vergen; extern crate vergen;
extern crate bigint; extern crate bigint;
extern crate chrono;
pub mod standard; pub mod standard;
#[macro_use] #[macro_use]

View File

@ -400,7 +400,8 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
// public_endpoint in host info contains local adderss at this point // public_endpoint in host info contains local adderss at this point
let listen_address = self.info.read().unwrap().public_endpoint.address.clone(); let listen_address = self.info.read().unwrap().public_endpoint.address.clone();
let udp_port = self.info.read().unwrap().config.udp_port.unwrap_or(listen_address.port()); let udp_port = self.info.read().unwrap().config.udp_port.unwrap_or(listen_address.port());
let public_endpoint = match self.info.read().unwrap().config.public_address { let public_address = self.info.read().unwrap().config.public_address.clone();
let public_endpoint = match public_address {
None => { None => {
let public_address = select_public_address(listen_address.port()); let public_address = select_public_address(listen_address.port());
let local_endpoint = NodeEndpoint { address: public_address, udp_port: udp_port }; let local_endpoint = NodeEndpoint { address: public_address, udp_port: udp_port };

View File

@ -146,7 +146,7 @@ impl OverlayDB {
}) })
} }
/// Get the refs and value of the given key. /// Put the refs and value of the given key, possibly deleting it from the db.
fn put_payload(&self, key: &H256, payload: (Bytes, u32)) -> bool { fn put_payload(&self, key: &H256, payload: (Bytes, u32)) -> bool {
if payload.1 > 0 { if payload.1 > 0 {
let mut s = RlpStream::new_list(2); let mut s = RlpStream::new_list(2);