Merge branch 'master' of github.com:ethcore/parity into network
This commit is contained in:
@@ -1,469 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Multilevel blockchain bloom filter.
|
||||
//!
|
||||
//! ```
|
||||
//! extern crate ethcore_util as util;
|
||||
//! use std::str::FromStr;
|
||||
//! use util::chainfilter::*;
|
||||
//! use util::sha3::*;
|
||||
//! use util::hash::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let (index_size, bloom_levels) = (16, 3);
|
||||
//! let mut cache = MemoryCache::new();
|
||||
//!
|
||||
//! let address = Address::from_str("ef2d6d194084c2de36e0dabfce45d046b37d1106").unwrap();
|
||||
//!
|
||||
//! // borrow cache for reading inside the scope
|
||||
//! let modified_blooms = {
|
||||
//! let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
//! let block_number = 39;
|
||||
//! let mut bloom = H2048::new();
|
||||
//! bloom.shift_bloomed(&address.sha3());
|
||||
//! filter.add_bloom(&bloom, block_number)
|
||||
//! };
|
||||
//!
|
||||
//! // number of updated blooms is equal number of levels
|
||||
//! assert_eq!(modified_blooms.len(), bloom_levels as usize);
|
||||
//!
|
||||
//! // lets inserts modified blooms into the cache
|
||||
//! cache.insert_blooms(modified_blooms);
|
||||
//!
|
||||
//! // borrow cache for another reading operations
|
||||
//! {
|
||||
//! let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
//! let blocks = filter.blocks_with_address(&address, 10, 40);
|
||||
//! assert_eq!(blocks.len(), 1);
|
||||
//! assert_eq!(blocks[0], 39);
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
use std::collections::{HashMap};
|
||||
use hash::*;
|
||||
use sha3::*;
|
||||
|
||||
/// Represents bloom index in cache
|
||||
///
|
||||
/// On cache level 0, every block bloom is represented by different index.
|
||||
/// On higher cache levels, multiple block blooms are represented by one
|
||||
/// index. Their `BloomIndex` can be created from block number and given level.
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub struct BloomIndex {
|
||||
/// Bloom level
|
||||
pub level: u8,
|
||||
/// Filter Index
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl BloomIndex {
|
||||
/// Default constructor for `BloomIndex`
|
||||
pub fn new(level: u8, index: usize) -> BloomIndex {
|
||||
BloomIndex {
|
||||
level: level,
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Types implementing this trait should provide read access for bloom filters database.
|
||||
pub trait FilterDataSource {
|
||||
/// returns reference to log at given position if it exists
|
||||
fn bloom_at_index(&self, index: &BloomIndex) -> Option<&H2048>;
|
||||
}
|
||||
|
||||
/// In memory cache for blooms.
|
||||
///
|
||||
/// Stores all blooms in HashMap, which indexes them by `BloomIndex`.
|
||||
pub struct MemoryCache {
|
||||
blooms: HashMap<BloomIndex, H2048>,
|
||||
}
|
||||
|
||||
impl MemoryCache {
|
||||
/// Default constructor for MemoryCache
|
||||
pub fn new() -> MemoryCache {
|
||||
MemoryCache { blooms: HashMap::new() }
|
||||
}
|
||||
|
||||
/// inserts all blooms into cache
|
||||
///
|
||||
/// if bloom at given index already exists, overwrites it
|
||||
pub fn insert_blooms(&mut self, blooms: HashMap<BloomIndex, H2048>) {
|
||||
self.blooms.extend(blooms);
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterDataSource for MemoryCache {
|
||||
fn bloom_at_index(&self, index: &BloomIndex) -> Option<&H2048> {
|
||||
self.blooms.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be used for search operations on blockchain.
|
||||
pub struct ChainFilter<'a, D>
|
||||
where D: FilterDataSource + 'a
|
||||
{
|
||||
data_source: &'a D,
|
||||
index_size: usize,
|
||||
level_sizes: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<'a, D> ChainFilter<'a, D> where D: FilterDataSource
|
||||
{
|
||||
/// Creates new filter instance.
|
||||
///
|
||||
/// Borrows `FilterDataSource` for reading.
|
||||
pub fn new(data_source: &'a D, index_size: usize, levels: u8) -> Self {
|
||||
if levels == 0 {
|
||||
panic!("ChainFilter requires at least 1 level");
|
||||
}
|
||||
|
||||
let mut filter = ChainFilter {
|
||||
data_source: data_source,
|
||||
index_size: index_size,
|
||||
// 0 level has always a size of 1
|
||||
level_sizes: vec![1]
|
||||
};
|
||||
|
||||
// cache level sizes, so we do not have to calculate them all the time
|
||||
// eg. if levels == 3, index_size = 16
|
||||
// level_sizes = [1, 16, 256]
|
||||
let additional: Vec<usize> = (1..).into_iter()
|
||||
.scan(1, |acc, _| {
|
||||
*acc = *acc * index_size;
|
||||
Some(*acc)
|
||||
})
|
||||
.take(levels as usize - 1)
|
||||
.collect();
|
||||
filter.level_sizes.extend(additional);
|
||||
|
||||
filter
|
||||
}
|
||||
|
||||
/// unsafely get level size
|
||||
fn level_size(&self, level: u8) -> usize {
|
||||
self.level_sizes[level as usize]
|
||||
}
|
||||
|
||||
/// converts block number and level to `BloomIndex`
|
||||
fn bloom_index(&self, block_number: usize, level: u8) -> BloomIndex {
|
||||
BloomIndex {
|
||||
level: level,
|
||||
index: block_number / self.level_size(level),
|
||||
}
|
||||
}
|
||||
|
||||
/// return bloom which are dependencies for given index
|
||||
///
|
||||
/// bloom indexes are ordered from lowest to highest
|
||||
fn lower_level_bloom_indexes(&self, index: &BloomIndex) -> Vec<BloomIndex> {
|
||||
// this is the lowest level
|
||||
if index.level == 0 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let new_level = index.level - 1;
|
||||
let offset = self.index_size * index.index;
|
||||
|
||||
(0..self.index_size).map(|i| BloomIndex::new(new_level, offset + i)).collect()
|
||||
}
|
||||
|
||||
/// return number of levels
|
||||
fn levels(&self) -> u8 {
|
||||
self.level_sizes.len() as u8
|
||||
}
|
||||
|
||||
/// returns max filter level
|
||||
fn max_level(&self) -> u8 {
|
||||
self.level_sizes.len() as u8 - 1
|
||||
}
|
||||
|
||||
/// internal function which does bloom search recursively
|
||||
fn blocks(&self, bloom: &H2048, from_block: usize, to_block: usize, level: u8, offset: usize) -> Option<Vec<usize>> {
|
||||
let index = self.bloom_index(offset, level);
|
||||
|
||||
match self.data_source.bloom_at_index(&index) {
|
||||
None => return None,
|
||||
Some(level_bloom) => match level {
|
||||
// if we are on the lowest level
|
||||
// take the value, exclude to_block
|
||||
0 if offset < to_block => return Some(vec![offset]),
|
||||
// return None if it is is equal to to_block
|
||||
0 => return None,
|
||||
// return None if current level doesnt contain given bloom
|
||||
_ if !level_bloom.contains(bloom) => return None,
|
||||
// continue processing && go down
|
||||
_ => ()
|
||||
}
|
||||
};
|
||||
|
||||
let level_size = self.level_size(level - 1);
|
||||
let from_index = self.bloom_index(from_block, level - 1);
|
||||
let to_index = self.bloom_index(to_block, level - 1);
|
||||
let res: Vec<usize> = self.lower_level_bloom_indexes(&index).into_iter()
|
||||
// chose only blooms in range
|
||||
.filter(|li| li.index >= from_index.index && li.index <= to_index.index)
|
||||
// map them to offsets
|
||||
.map(|li| li.index * level_size)
|
||||
// get all blocks that may contain our bloom
|
||||
.map(|off| self.blocks(bloom, from_block, to_block, level - 1, off))
|
||||
// filter existing ones
|
||||
.filter_map(|x| x)
|
||||
// flatten nested structures
|
||||
.flat_map(|v| v)
|
||||
.collect();
|
||||
Some(res)
|
||||
}
|
||||
|
||||
/// Adds new bloom to all filter levels
|
||||
pub fn add_bloom(&self, bloom: &H2048, block_number: usize) -> HashMap<BloomIndex, H2048> {
|
||||
let mut result: HashMap<BloomIndex, H2048> = HashMap::new();
|
||||
|
||||
for level in 0..self.levels() {
|
||||
let bloom_index = self.bloom_index(block_number, level);
|
||||
let new_bloom = match self.data_source.bloom_at_index(&bloom_index) {
|
||||
Some(old_bloom) => old_bloom | bloom,
|
||||
None => bloom.clone(),
|
||||
};
|
||||
|
||||
result.insert(bloom_index, new_bloom);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Adds new blooms starting from block number.
|
||||
pub fn add_blooms(&self, blooms: &[H2048], block_number: usize) -> HashMap<BloomIndex, H2048> {
|
||||
let mut result: HashMap<BloomIndex, H2048> = HashMap::new();
|
||||
|
||||
for level in 0..self.levels() {
|
||||
for i in 0..blooms.len() {
|
||||
let bloom_index = self.bloom_index(block_number + i, level);
|
||||
let is_new_bloom = match result.get_mut(&bloom_index) {
|
||||
|
||||
// it was already modified
|
||||
Some(to_shift) => {
|
||||
*to_shift = &blooms[i] | to_shift;
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// it hasn't been modified yet
|
||||
if is_new_bloom {
|
||||
let new_bloom = match self.data_source.bloom_at_index(&bloom_index) {
|
||||
Some(old_bloom) => old_bloom | &blooms[i],
|
||||
None => blooms[i].clone(),
|
||||
};
|
||||
result.insert(bloom_index, new_bloom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Resets bloom at level 0 and forces rebuild on higher levels.
|
||||
pub fn reset_bloom(&self, bloom: &H2048, block_number: usize) -> HashMap<BloomIndex, H2048> {
|
||||
let mut result: HashMap<BloomIndex, H2048> = HashMap::new();
|
||||
|
||||
let mut reset_index = self.bloom_index(block_number, 0);
|
||||
result.insert(reset_index.clone(), bloom.clone());
|
||||
|
||||
for level in 1..self.levels() {
|
||||
let index = self.bloom_index(block_number, level);
|
||||
// get all bloom indexes that were used to construct this bloom
|
||||
let lower_indexes = self.lower_level_bloom_indexes(&index);
|
||||
let new_bloom = lower_indexes.into_iter()
|
||||
// skip reseted one
|
||||
.filter(|li| li != &reset_index)
|
||||
// get blooms for these indexes
|
||||
.map(|li| self.data_source.bloom_at_index(&li))
|
||||
// filter existing ones
|
||||
.filter_map(|b| b)
|
||||
// BitOr all of them
|
||||
.fold(H2048::new(), |acc, bloom| &acc | bloom);
|
||||
|
||||
reset_index = index.clone();
|
||||
result.insert(index, &new_bloom | bloom);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Sets lowest level bloom to 0 and forces rebuild on higher levels.
|
||||
pub fn clear_bloom(&self, block_number: usize) -> HashMap<BloomIndex, H2048> {
|
||||
self.reset_bloom(&H2048::new(), block_number)
|
||||
}
|
||||
|
||||
/// Returns numbers of blocks that may contain Address.
|
||||
pub fn blocks_with_address(&self, address: &Address, from_block: usize, to_block: usize) -> Vec<usize> {
|
||||
let mut bloom = H2048::new();
|
||||
bloom.shift_bloomed(&address.sha3());
|
||||
self.blocks_with_bloom(&bloom, from_block, to_block)
|
||||
}
|
||||
|
||||
/// Returns numbers of blocks that may contain Topic.
|
||||
pub fn blocks_with_topic(&self, topic: &H256, from_block: usize, to_block: usize) -> Vec<usize> {
|
||||
let mut bloom = H2048::new();
|
||||
bloom.shift_bloomed(&topic.sha3());
|
||||
self.blocks_with_bloom(&bloom, from_block, to_block)
|
||||
}
|
||||
|
||||
/// Returns numbers of blocks that may log bloom.
|
||||
pub fn blocks_with_bloom(&self, bloom: &H2048, from_block: usize, to_block: usize) -> Vec<usize> {
|
||||
let mut result = vec![];
|
||||
// lets start from highest level
|
||||
let max_level = self.max_level();
|
||||
let level_size = self.level_size(max_level);
|
||||
let from_index = self.bloom_index(from_block, max_level);
|
||||
let to_index = self.bloom_index(to_block, max_level);
|
||||
|
||||
for index in from_index.index..to_index.index + 1 {
|
||||
// offset will be used to calculate where we are right now
|
||||
let offset = level_size * index;
|
||||
|
||||
// go doooown!
|
||||
if let Some(blocks) = self.blocks(bloom, from_block, to_block, max_level, offset) {
|
||||
result.extend(blocks);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hash::*;
|
||||
use chainfilter::*;
|
||||
use sha3::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_level_size() {
|
||||
let cache = MemoryCache::new();
|
||||
let filter = ChainFilter::new(&cache, 16, 3);
|
||||
assert_eq!(filter.level_size(0), 1);
|
||||
assert_eq!(filter.level_size(1), 16);
|
||||
assert_eq!(filter.level_size(2), 256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_index() {
|
||||
let cache = MemoryCache::new();
|
||||
let filter = ChainFilter::new(&cache, 16, 3);
|
||||
|
||||
let bi0 = filter.bloom_index(0, 0);
|
||||
assert_eq!(bi0.level, 0);
|
||||
assert_eq!(bi0.index, 0);
|
||||
|
||||
let bi1 = filter.bloom_index(1, 0);
|
||||
assert_eq!(bi1.level, 0);
|
||||
assert_eq!(bi1.index, 1);
|
||||
|
||||
let bi2 = filter.bloom_index(2, 0);
|
||||
assert_eq!(bi2.level, 0);
|
||||
assert_eq!(bi2.index, 2);
|
||||
|
||||
let bi3 = filter.bloom_index(3, 1);
|
||||
assert_eq!(bi3.level, 1);
|
||||
assert_eq!(bi3.index, 0);
|
||||
|
||||
let bi4 = filter.bloom_index(15, 1);
|
||||
assert_eq!(bi4.level, 1);
|
||||
assert_eq!(bi4.index, 0);
|
||||
|
||||
let bi5 = filter.bloom_index(16, 1);
|
||||
assert_eq!(bi5.level, 1);
|
||||
assert_eq!(bi5.index, 1);
|
||||
|
||||
let bi6 = filter.bloom_index(255, 2);
|
||||
assert_eq!(bi6.level, 2);
|
||||
assert_eq!(bi6.index, 0);
|
||||
|
||||
let bi7 = filter.bloom_index(256, 2);
|
||||
assert_eq!(bi7.level, 2);
|
||||
assert_eq!(bi7.index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower_level_bloom_indexes() {
|
||||
let cache = MemoryCache::new();
|
||||
let filter = ChainFilter::new(&cache, 16, 3);
|
||||
|
||||
let bi = filter.bloom_index(256, 2);
|
||||
assert_eq!(bi.level, 2);
|
||||
assert_eq!(bi.index, 1);
|
||||
|
||||
let mut ebis = vec![];
|
||||
for i in 16..32 {
|
||||
ebis.push(BloomIndex::new(1, i));
|
||||
}
|
||||
|
||||
let bis = filter.lower_level_bloom_indexes(&bi);
|
||||
assert_eq!(ebis, bis);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topic_basic_search() {
|
||||
let index_size = 16;
|
||||
let bloom_levels = 3;
|
||||
|
||||
let mut cache = MemoryCache::new();
|
||||
let topic = H256::from_str("8d936b1bd3fc635710969ccfba471fb17d598d9d1971b538dd712e1e4b4f4dba").unwrap();
|
||||
|
||||
let modified_blooms = {
|
||||
let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
let block_number = 23;
|
||||
let mut bloom = H2048::new();
|
||||
bloom.shift_bloomed(&topic.sha3());
|
||||
filter.add_bloom(&bloom, block_number)
|
||||
};
|
||||
|
||||
// number of modified blooms should always be equal number of levels
|
||||
assert_eq!(modified_blooms.len(), bloom_levels as usize);
|
||||
cache.insert_blooms(modified_blooms);
|
||||
|
||||
{
|
||||
let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
let blocks = filter.blocks_with_topic(&topic, 0, 100);
|
||||
assert_eq!(blocks.len(), 1);
|
||||
assert_eq!(blocks[0], 23);
|
||||
}
|
||||
|
||||
{
|
||||
let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
let blocks = filter.blocks_with_topic(&topic, 0, 23);
|
||||
assert_eq!(blocks.len(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
let blocks = filter.blocks_with_topic(&topic, 23, 24);
|
||||
assert_eq!(blocks.len(), 1);
|
||||
assert_eq!(blocks[0], 23);
|
||||
}
|
||||
|
||||
{
|
||||
let filter = ChainFilter::new(&cache, index_size, bloom_levels);
|
||||
let blocks = filter.blocks_with_topic(&topic, 24, 100);
|
||||
assert_eq!(blocks.len(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use common::*;
|
||||
use rlp::*;
|
||||
use hashdb::*;
|
||||
use memorydb::*;
|
||||
use rocksdb::{DB, Writable, WriteBatch, IteratorMode};
|
||||
use kvdb::{Database, DBTransaction, DatabaseConfig};
|
||||
#[cfg(test)]
|
||||
use std::env;
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::env;
|
||||
/// the removals actually take effect.
|
||||
pub struct JournalDB {
|
||||
overlay: MemoryDB,
|
||||
backing: Arc<DB>,
|
||||
backing: Arc<Database>,
|
||||
counters: Arc<RwLock<HashMap<H256, i32>>>,
|
||||
}
|
||||
|
||||
@@ -47,21 +47,25 @@ impl Clone for JournalDB {
|
||||
}
|
||||
}
|
||||
|
||||
const LATEST_ERA_KEY : [u8; 4] = [ b'l', b'a', b's', b't' ];
|
||||
const VERSION_KEY : [u8; 4] = [ b'j', b'v', b'e', b'r' ];
|
||||
// all keys must be at least 12 bytes
|
||||
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 DB_VERSION: u32 = 2;
|
||||
const DB_VERSION: u32 = 3;
|
||||
|
||||
const PADDING : [u8; 10] = [ 0u8; 10 ];
|
||||
|
||||
impl JournalDB {
|
||||
/// Create a new instance given a `backing` database.
|
||||
pub fn new(backing: DB) -> JournalDB {
|
||||
let db = Arc::new(backing);
|
||||
JournalDB::new_with_arc(db)
|
||||
}
|
||||
|
||||
/// Create a new instance given a shared `backing` database.
|
||||
pub fn new_with_arc(backing: Arc<DB>) -> JournalDB {
|
||||
if backing.iterator(IteratorMode::Start).next().is_some() {
|
||||
/// Create a new instance from file
|
||||
pub fn new(path: &str) -> JournalDB {
|
||||
let opts = DatabaseConfig {
|
||||
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| {
|
||||
panic!("Error opening state db: {}", e);
|
||||
});
|
||||
if !backing.is_empty() {
|
||||
match backing.get(&VERSION_KEY).map(|d| d.map(|v| decode::<u32>(&v))) {
|
||||
Ok(Some(DB_VERSION)) => {},
|
||||
v => panic!("Incompatible DB version, expected {}, got {:?}", DB_VERSION, v)
|
||||
@@ -72,7 +76,7 @@ impl JournalDB {
|
||||
let counters = JournalDB::read_counters(&backing);
|
||||
JournalDB {
|
||||
overlay: MemoryDB::new(),
|
||||
backing: backing,
|
||||
backing: Arc::new(backing),
|
||||
counters: Arc::new(RwLock::new(counters)),
|
||||
}
|
||||
}
|
||||
@@ -82,7 +86,7 @@ impl JournalDB {
|
||||
pub fn new_temp() -> JournalDB {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(H32::random().hex());
|
||||
Self::new(DB::open_default(dir.to_str().unwrap()).unwrap())
|
||||
Self::new(dir.to_str().unwrap())
|
||||
}
|
||||
|
||||
/// Check if this database has any commits
|
||||
@@ -117,16 +121,17 @@ impl JournalDB {
|
||||
// and the key is safe to delete.
|
||||
|
||||
// record new commit's details.
|
||||
let batch = WriteBatch::new();
|
||||
let batch = DBTransaction::new();
|
||||
let mut counters = self.counters.write().unwrap();
|
||||
{
|
||||
let mut index = 0usize;
|
||||
let mut last;
|
||||
|
||||
while try!(self.backing.get({
|
||||
let mut r = RlpStream::new_list(2);
|
||||
let mut r = RlpStream::new_list(3);
|
||||
r.append(&now);
|
||||
r.append(&index);
|
||||
r.append(&&PADDING[..]);
|
||||
last = r.drain();
|
||||
&last
|
||||
})).is_some() {
|
||||
@@ -154,9 +159,10 @@ impl JournalDB {
|
||||
let mut to_remove: Vec<H256> = Vec::new();
|
||||
let mut canon_inserts: Vec<H256> = Vec::new();
|
||||
while let Some(rlp_data) = try!(self.backing.get({
|
||||
let mut r = RlpStream::new_list(2);
|
||||
let mut r = RlpStream::new_list(3);
|
||||
r.append(&end_era);
|
||||
r.append(&index);
|
||||
r.append(&&PADDING[..]);
|
||||
last = r.drain();
|
||||
&last
|
||||
})) {
|
||||
@@ -226,16 +232,17 @@ impl JournalDB {
|
||||
self.backing.get(&key.bytes()).expect("Low-level database error. Some issue with your hard disk?").map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
fn read_counters(db: &DB) -> HashMap<H256, i32> {
|
||||
fn read_counters(db: &Database) -> HashMap<H256, i32> {
|
||||
let mut res = HashMap::new();
|
||||
if let Some(val) = db.get(&LATEST_ERA_KEY).expect("Low-level database error.") {
|
||||
let mut era = decode::<u64>(&val);
|
||||
loop {
|
||||
let mut index = 0usize;
|
||||
while let Some(rlp_data) = db.get({
|
||||
let mut r = RlpStream::new_list(2);
|
||||
let mut r = RlpStream::new_list(3);
|
||||
r.append(&era);
|
||||
r.append(&index);
|
||||
r.append(&&PADDING[..]);
|
||||
&r.drain()
|
||||
}).expect("Low-level database error.") {
|
||||
let rlp = Rlp::new(&rlp_data);
|
||||
@@ -259,7 +266,7 @@ impl JournalDB {
|
||||
impl HashDB for JournalDB {
|
||||
fn keys(&self) -> HashMap<H256, i32> {
|
||||
let mut ret: HashMap<H256, i32> = HashMap::new();
|
||||
for (key, _) in self.backing.iterator(IteratorMode::Start) {
|
||||
for (key, _) in self.backing.iter() {
|
||||
let h = H256::from_slice(key.deref());
|
||||
ret.insert(h, 1);
|
||||
}
|
||||
@@ -429,12 +436,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reopen() {
|
||||
use rocksdb::DB;
|
||||
let mut dir = ::std::env::temp_dir();
|
||||
dir.push(H32::random().hex());
|
||||
|
||||
let foo = {
|
||||
let mut jdb = JournalDB::new(DB::open_default(dir.to_str().unwrap()).unwrap());
|
||||
let mut jdb = JournalDB::new(dir.to_str().unwrap());
|
||||
// history is 1
|
||||
let foo = jdb.insert(b"foo");
|
||||
jdb.commit(0, &b"0".sha3(), None).unwrap();
|
||||
@@ -442,13 +448,13 @@ mod tests {
|
||||
};
|
||||
|
||||
{
|
||||
let mut jdb = JournalDB::new(DB::open_default(dir.to_str().unwrap()).unwrap());
|
||||
let mut jdb = JournalDB::new(dir.to_str().unwrap());
|
||||
jdb.remove(&foo);
|
||||
jdb.commit(1, &b"1".sha3(), Some((0, b"0".sha3()))).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut jdb = JournalDB::new(DB::open_default(dir.to_str().unwrap()).unwrap());
|
||||
let mut jdb = JournalDB::new(dir.to_str().unwrap());
|
||||
assert!(jdb.exists(&foo));
|
||||
jdb.commit(2, &b"2".sha3(), Some((1, b"1".sha3()))).unwrap();
|
||||
assert!(!jdb.exists(&foo));
|
||||
|
||||
206
util/src/kvdb.rs
Normal file
206
util/src/kvdb.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
// 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/>.
|
||||
|
||||
//! Key-Value store abstraction with RocksDB backend.
|
||||
|
||||
use rocksdb::{DB, Writable, WriteBatch, IteratorMode, DBVector, DBIterator,
|
||||
IndexType, Options, DBCompactionStyle, BlockBasedOptions, Direction};
|
||||
|
||||
/// Write transaction. Batches a sequence of put/delete operations for efficiency.
|
||||
pub struct DBTransaction {
|
||||
batch: WriteBatch,
|
||||
}
|
||||
|
||||
impl DBTransaction {
|
||||
/// Create new transaction.
|
||||
pub fn new() -> DBTransaction {
|
||||
DBTransaction { batch: WriteBatch::new() }
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
pub fn put(&self, key: &[u8], value: &[u8]) -> Result<(), String> {
|
||||
self.batch.put(key, value)
|
||||
}
|
||||
|
||||
/// Delete value by key.
|
||||
pub fn delete(&self, key: &[u8]) -> Result<(), String> {
|
||||
self.batch.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Database configuration
|
||||
pub struct DatabaseConfig {
|
||||
/// Optional prefix size in bytes. Allows lookup by partial key.
|
||||
pub prefix_size: Option<usize>
|
||||
}
|
||||
|
||||
/// Database iterator
|
||||
pub struct DatabaseIterator<'a> {
|
||||
iter: DBIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DatabaseIterator<'a> {
|
||||
type Item = (Box<[u8]>, Box<[u8]>);
|
||||
|
||||
#[cfg_attr(feature="dev", allow(type_complexity))]
|
||||
fn next(&mut self) -> Option<(Box<[u8]>, Box<[u8]>)> {
|
||||
self.iter.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Key-Value database.
|
||||
pub struct Database {
|
||||
db: DB,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Open database with default settings.
|
||||
pub fn open_default(path: &str) -> Result<Database, String> {
|
||||
Database::open(&DatabaseConfig { prefix_size: None }, path)
|
||||
}
|
||||
|
||||
/// Open database file. Creates if it does not exist.
|
||||
pub fn open(config: &DatabaseConfig, path: &str) -> Result<Database, String> {
|
||||
let mut opts = Options::new();
|
||||
opts.set_max_open_files(256);
|
||||
opts.create_if_missing(true);
|
||||
opts.set_use_fsync(false);
|
||||
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
|
||||
/*
|
||||
opts.set_bytes_per_sync(8388608);
|
||||
opts.set_disable_data_sync(false);
|
||||
opts.set_block_cache_size_mb(1024);
|
||||
opts.set_table_cache_num_shard_bits(6);
|
||||
opts.set_max_write_buffer_number(32);
|
||||
opts.set_write_buffer_size(536870912);
|
||||
opts.set_target_file_size_base(1073741824);
|
||||
opts.set_min_write_buffer_number_to_merge(4);
|
||||
opts.set_level_zero_stop_writes_trigger(2000);
|
||||
opts.set_level_zero_slowdown_writes_trigger(0);
|
||||
opts.set_compaction_style(DBUniversalCompaction);
|
||||
opts.set_max_background_compactions(4);
|
||||
opts.set_max_background_flushes(4);
|
||||
opts.set_filter_deletes(false);
|
||||
opts.set_disable_auto_compactions(false);
|
||||
*/
|
||||
|
||||
if let Some(size) = config.prefix_size {
|
||||
let mut block_opts = BlockBasedOptions::new();
|
||||
block_opts.set_index_type(IndexType::HashSearch);
|
||||
opts.set_block_based_table_factory(&block_opts);
|
||||
opts.set_prefix_extractor_fixed_size(size);
|
||||
}
|
||||
let db = try!(DB::open(&opts, path));
|
||||
Ok(Database { db: db })
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten.
|
||||
pub fn put(&self, key: &[u8], value: &[u8]) -> Result<(), String> {
|
||||
self.db.put(key, value)
|
||||
}
|
||||
|
||||
/// Delete value by key.
|
||||
pub fn delete(&self, key: &[u8]) -> Result<(), String> {
|
||||
self.db.delete(key)
|
||||
}
|
||||
|
||||
/// Commit transaction to database.
|
||||
pub fn write(&self, tr: DBTransaction) -> Result<(), String> {
|
||||
self.db.write(tr.batch)
|
||||
}
|
||||
|
||||
/// Get value by key.
|
||||
pub fn get(&self, key: &[u8]) -> Result<Option<DBVector>, String> {
|
||||
self.db.get(key)
|
||||
}
|
||||
|
||||
/// Get value by partial key. Prefix size should match configured prefix size.
|
||||
pub fn get_by_prefix(&self, prefix: &[u8]) -> Option<Box<[u8]>> {
|
||||
let mut iter = self.db.iterator(IteratorMode::From(prefix, Direction::forward));
|
||||
match iter.next() {
|
||||
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
|
||||
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Some(v) } else { None },
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there is anything in the database.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.db.iterator(IteratorMode::Start).next().is_none()
|
||||
}
|
||||
|
||||
/// Check if there is anything in the database.
|
||||
pub fn iter(&self) -> DatabaseIterator {
|
||||
DatabaseIterator { iter: self.db.iterator(IteratorMode::Start) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hash::*;
|
||||
use super::*;
|
||||
use devtools::*;
|
||||
use std::str::FromStr;
|
||||
use std::ops::Deref;
|
||||
|
||||
fn test_db(config: &DatabaseConfig) {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let db = Database::open(config, path.as_path().to_str().unwrap()).unwrap();
|
||||
let key1 = H256::from_str("02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap();
|
||||
let key2 = H256::from_str("03c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap();
|
||||
let key3 = H256::from_str("01c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap();
|
||||
|
||||
db.put(&key1, b"cat").unwrap();
|
||||
db.put(&key2, b"dog").unwrap();
|
||||
|
||||
assert_eq!(db.get(&key1).unwrap().unwrap().deref(), b"cat");
|
||||
|
||||
let contents: Vec<_> = db.iter().collect();
|
||||
assert_eq!(contents.len(), 2);
|
||||
assert_eq!(&*contents[0].0, key1.deref());
|
||||
assert_eq!(&*contents[0].1, b"cat");
|
||||
assert_eq!(&*contents[1].0, key2.deref());
|
||||
assert_eq!(&*contents[1].1, b"dog");
|
||||
|
||||
db.delete(&key1).unwrap();
|
||||
assert!(db.get(&key1).unwrap().is_none());
|
||||
db.put(&key1, b"cat").unwrap();
|
||||
|
||||
let transaction = DBTransaction::new();
|
||||
transaction.put(&key3, b"elephant").unwrap();
|
||||
transaction.delete(&key1).unwrap();
|
||||
db.write(transaction).unwrap();
|
||||
assert!(db.get(&key1).unwrap().is_none());
|
||||
assert_eq!(db.get(&key3).unwrap().unwrap().deref(), b"elephant");
|
||||
|
||||
if config.prefix_size.is_some() {
|
||||
assert_eq!(db.get_by_prefix(&key3).unwrap().deref(), b"elephant");
|
||||
assert_eq!(db.get_by_prefix(&key2).unwrap().deref(), b"dog");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kvdb() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let smoke = Database::open_default(path.as_path().to_str().unwrap()).unwrap();
|
||||
assert!(smoke.is_empty());
|
||||
test_db(&DatabaseConfig { prefix_size: None });
|
||||
test_db(&DatabaseConfig { prefix_size: Some(1) });
|
||||
test_db(&DatabaseConfig { prefix_size: Some(8) });
|
||||
test_db(&DatabaseConfig { prefix_size: Some(32) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ pub mod hashdb;
|
||||
pub mod memorydb;
|
||||
pub mod overlaydb;
|
||||
pub mod journaldb;
|
||||
pub mod kvdb;
|
||||
mod math;
|
||||
pub mod chainfilter;
|
||||
pub mod crypto;
|
||||
pub mod triehash;
|
||||
pub mod trie;
|
||||
@@ -153,7 +153,6 @@ pub use memorydb::*;
|
||||
pub use overlaydb::*;
|
||||
pub use journaldb::*;
|
||||
pub use math::*;
|
||||
pub use chainfilter::*;
|
||||
pub use crypto::*;
|
||||
pub use triehash::*;
|
||||
pub use trie::*;
|
||||
@@ -163,4 +162,5 @@ pub use semantic_version::*;
|
||||
pub use network::*;
|
||||
pub use io::*;
|
||||
pub use log::*;
|
||||
pub use kvdb::*;
|
||||
|
||||
|
||||
@@ -609,7 +609,7 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
}
|
||||
}
|
||||
if kill {
|
||||
self.kill_connection(token, io, true); //TODO: mark connection as dead an check in kill_connection
|
||||
self.kill_connection(token, io, true);
|
||||
return;
|
||||
} else if create_session {
|
||||
self.start_session(token, io);
|
||||
@@ -651,7 +651,7 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
}
|
||||
}
|
||||
if kill {
|
||||
self.kill_connection(token, io, true); //TODO: mark connection as dead an check in kill_connection
|
||||
self.kill_connection(token, io, true);
|
||||
}
|
||||
for p in ready_data {
|
||||
let h = self.handlers.read().unwrap().get(p).unwrap().clone();
|
||||
@@ -716,6 +716,7 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
fn kill_connection(&self, token: StreamToken, io: &IoContext<NetworkIoMessage<Message>>, remote: bool) {
|
||||
let mut to_disconnect: Vec<ProtocolId> = Vec::new();
|
||||
let mut failure_id = None;
|
||||
let mut deregister = false;
|
||||
match token {
|
||||
FIRST_HANDSHAKE ... LAST_HANDSHAKE => {
|
||||
let handshakes = self.handshakes.write().unwrap();
|
||||
@@ -724,7 +725,7 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
if !handshake.expired() {
|
||||
handshake.set_expired();
|
||||
failure_id = Some(handshake.id().clone());
|
||||
io.deregister_stream(token).expect("Error deregistering stream");
|
||||
deregister = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -742,7 +743,7 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
}
|
||||
s.set_expired();
|
||||
failure_id = Some(s.id().clone());
|
||||
io.deregister_stream(token).expect("Error deregistering stream");
|
||||
deregister = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -757,6 +758,9 @@ impl<Message> Host<Message> where Message: Send + Sync + Clone {
|
||||
let h = self.handlers.read().unwrap().get(p).unwrap().clone();
|
||||
h.disconnected(&NetworkContext::new(io, p, Some(token), self.sessions.clone()), &token);
|
||||
}
|
||||
if deregister {
|
||||
io.deregister_stream(token).expect("Error deregistering stream");
|
||||
}
|
||||
}
|
||||
|
||||
fn update_nodes(&self, io: &IoContext<NetworkIoMessage<Message>>, node_changes: TableUpdates) {
|
||||
|
||||
@@ -26,7 +26,7 @@ use std::ops::*;
|
||||
use std::sync::*;
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
use rocksdb::{DB, Writable, IteratorMode};
|
||||
use kvdb::{Database};
|
||||
|
||||
/// Implementation of the HashDB trait for a disk-backed database with a memory overlay.
|
||||
///
|
||||
@@ -38,15 +38,15 @@ use rocksdb::{DB, Writable, IteratorMode};
|
||||
/// queries have an immediate effect in terms of these functions.
|
||||
pub struct OverlayDB {
|
||||
overlay: MemoryDB,
|
||||
backing: Arc<DB>,
|
||||
backing: Arc<Database>,
|
||||
}
|
||||
|
||||
impl OverlayDB {
|
||||
/// Create a new instance of OverlayDB given a `backing` database.
|
||||
pub fn new(backing: DB) -> OverlayDB { Self::new_with_arc(Arc::new(backing)) }
|
||||
pub fn new(backing: Database) -> OverlayDB { Self::new_with_arc(Arc::new(backing)) }
|
||||
|
||||
/// Create a new instance of OverlayDB given a `backing` database.
|
||||
pub fn new_with_arc(backing: Arc<DB>) -> OverlayDB {
|
||||
pub fn new_with_arc(backing: Arc<Database>) -> OverlayDB {
|
||||
OverlayDB{ overlay: MemoryDB::new(), backing: backing }
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ impl OverlayDB {
|
||||
pub fn new_temp() -> OverlayDB {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(H32::random().hex());
|
||||
Self::new(DB::open_default(dir.to_str().unwrap()).unwrap())
|
||||
Self::new(Database::open_default(dir.to_str().unwrap()).unwrap())
|
||||
}
|
||||
|
||||
/// Commit all memory operations to the backing database.
|
||||
@@ -164,7 +164,7 @@ impl OverlayDB {
|
||||
impl HashDB for OverlayDB {
|
||||
fn keys(&self) -> HashMap<H256, i32> {
|
||||
let mut ret: HashMap<H256, i32> = HashMap::new();
|
||||
for (key, _) in self.backing.iterator(IteratorMode::Start) {
|
||||
for (key, _) in self.backing.iter() {
|
||||
let h = H256::from_slice(key.deref());
|
||||
let r = self.payload(&h).unwrap().1;
|
||||
ret.insert(h, r as i32);
|
||||
@@ -318,7 +318,7 @@ fn overlaydb_complex() {
|
||||
fn playpen() {
|
||||
use std::fs;
|
||||
{
|
||||
let db: DB = DB::open_default("/tmp/test").unwrap();
|
||||
let db: Database = Database::open_default("/tmp/test").unwrap();
|
||||
db.put(b"test", b"test2").unwrap();
|
||||
match db.get(b"test") {
|
||||
Ok(Some(value)) => println!("Got value {:?}", value.deref()),
|
||||
|
||||
Reference in New Issue
Block a user