Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Zero-alloc trie lookups #3998

Merged
merged 10 commits into from
Jan 6, 2017
10 changes: 5 additions & 5 deletions ethcore/src/state/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ impl Account {
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
using it will not fail.");

let item: U256 = match db.get(key){
Ok(x) => x.map_or_else(U256::zero, |v| decode(&*v)),
let item: U256 = match db.get_with(key, ::rlp::decode) {
Ok(x) => x.unwrap_or_else(U256::zero),
Err(e) => panic!("Encountered potential DB corruption: {}", e),
};
let value: H256 = item.into();
Expand Down Expand Up @@ -446,12 +446,12 @@ impl Account {
/// omitted.
pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
use util::trie::{Trie, TrieDB};
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
use util::trie::recorder::Recorder;

let mut recorder = TrieRecorder::with_depth(from_level);
let mut recorder = Recorder::with_depth(from_level);

let trie = TrieDB::new(db, &self.storage_root)?;
let _ = trie.get_recorded(&storage_key, &mut recorder)?;
let _ = trie.get_with(&storage_key, &mut recorder)?;

Ok(recorder.drain().into_iter().map(|r| r.data).collect())
}
Expand Down
27 changes: 13 additions & 14 deletions ethcore/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use state_db::StateDB;

use util::*;

use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
use util::trie::recorder::Recorder;

mod account;
mod substate;
Expand Down Expand Up @@ -425,8 +425,8 @@ impl State {

// account is not found in the global cache, get from the DB and insert into local
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
let maybe_acc = match db.get(address) {
Ok(acc) => acc.map(|v| Account::from_rlp(&v)),
let maybe_acc = match db.get_with(address, Account::from_rlp) {
Ok(acc) => acc,
Err(e) => panic!("Potential DB corruption encountered: {}", e),
};
let r = maybe_acc.as_ref().map_or(H256::new(), |a| {
Expand Down Expand Up @@ -690,8 +690,8 @@ impl State {

// not found in the global cache, get from the DB and insert into local
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
let mut maybe_acc = match db.get(a) {
Ok(acc) => acc.map(|v| Account::from_rlp(&v)),
let mut maybe_acc = match db.get_with(a, Account::from_rlp) {
Ok(acc) => acc,
Err(e) => panic!("Potential DB corruption encountered: {}", e),
};
if let Some(ref mut account) = maybe_acc.as_mut() {
Expand Down Expand Up @@ -722,9 +722,8 @@ impl State {
None => {
let maybe_acc = if self.db.check_non_null_bloom(a) {
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
match db.get(a) {
Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))),
Ok(None) => AccountEntry::new_clean(None),
match db.get_with(a, Account::from_rlp) {
Ok(acc) => AccountEntry::new_clean(acc),
Err(e) => panic!("Potential DB corruption encountered: {}", e),
}
} else {
Expand Down Expand Up @@ -770,9 +769,9 @@ impl State {
/// Requires a secure trie to be used for accurate results.
/// `account_key` == sha3(address)
pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
let mut recorder = TrieRecorder::with_depth(from_level);
let mut recorder = Recorder::with_depth(from_level);
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let _ = trie.get_recorded(&account_key, &mut recorder)?;
trie.get_with(&account_key, &mut recorder)?;

Ok(recorder.drain().into_iter().map(|r| r.data).collect())
}
Expand All @@ -786,8 +785,8 @@ impl State {
// TODO: probably could look into cache somehow but it's keyed by
// address, not sha3(address).
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let acc = match trie.get(&account_key)? {
Some(rlp) => Account::from_rlp(&rlp),
let acc = match trie.get_with(&account_key, Account::from_rlp)? {
Some(acc) => acc,
None => return Ok(Vec::new()),
};

Expand All @@ -799,8 +798,8 @@ impl State {
/// Only works when backed by a secure trie.
pub fn code_by_address_hash(&self, account_key: H256) -> Result<Option<Bytes>, Box<TrieError>> {
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let mut acc = match trie.get(&account_key)? {
Some(rlp) => Account::from_rlp(&rlp),
let mut acc = match trie.get_with(&account_key, Account::from_rlp)? {
Some(acc) => acc,
None => return Ok(None),
};

Expand Down
6 changes: 3 additions & 3 deletions util/benches/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

//! benchmarking for rlp
//! benchmarking for bigint
//! should be started with:
//! ```bash
//! multirust run nightly cargo bench
Expand All @@ -24,10 +24,10 @@
#![feature(asm)]

extern crate test;
extern crate ethcore_bigint as bigint;
extern crate ethcore_util;

use test::{Bencher, black_box};
use bigint::uint::{U256, U512, Uint, U128};
use ethcore_util::{U256, U512, Uint, U128};

#[bench]
fn u256_add(b: &mut Bencher) {
Expand Down
4 changes: 2 additions & 2 deletions util/benches/rlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@

extern crate test;
extern crate rlp;
extern crate ethcore_bigint as bigint;
extern crate ethcore_util as util;

use test::Bencher;
use std::str::FromStr;
use rlp::*;
use bigint::uint::U256;
use util::U256;

#[bench]
fn bench_stream_u64_value(b: &mut Bencher) {
Expand Down
26 changes: 11 additions & 15 deletions util/src/nibblevec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,26 @@
//! An owning, nibble-oriented byte vector.

use ::NibbleSlice;
use elastic_array::ElasticArray36;

#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
/// Owning, nibble-oriented byte vector. Counterpart to `NibbleSlice`.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NibbleVec {
inner: Vec<u8>,
inner: ElasticArray36<u8>,
len: usize,
}

impl Default for NibbleVec {
fn default() -> Self {
NibbleVec::new()
}
}

impl NibbleVec {
/// Make a new `NibbleVec`
pub fn new() -> Self {
NibbleVec {
inner: Vec::new(),
len: 0
}
}

/// Make a `NibbleVec` with capacity for `n` nibbles.
pub fn with_capacity(n: usize) -> Self {
NibbleVec {
inner: Vec::with_capacity((n / 2) + (n % 2)),
inner: ElasticArray36::new(),
len: 0
}
}
Expand All @@ -49,9 +48,6 @@ impl NibbleVec {
/// Retrurns true if `NibbleVec` has zero length
pub fn is_empty(&self) -> bool { self.len == 0 }

/// Capacity of the `NibbleVec`.
pub fn capacity(&self) -> usize { self.inner.capacity() * 2 }

/// Try to get the nibble at the given offset.
pub fn at(&self, idx: usize) -> u8 {
if idx % 2 == 0 {
Expand Down Expand Up @@ -109,7 +105,7 @@ impl NibbleVec {

impl<'a> From<NibbleSlice<'a>> for NibbleVec {
fn from(s: NibbleSlice<'a>) -> Self {
let mut v = NibbleVec::with_capacity(s.len());
let mut v = NibbleVec::new();
for i in 0..s.len() {
v.push(s.at(i));
}
Expand Down
11 changes: 6 additions & 5 deletions util/src/trie/fatdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

use hash::H256;
use sha3::Hashable;
use hashdb::{HashDB, DBValue};
use super::{TrieDB, Trie, TrieDBIterator, TrieItem, Recorder, TrieIterator};
use hashdb::HashDB;
use super::{TrieDB, Trie, TrieDBIterator, TrieItem, TrieIterator, Query};

/// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database.
/// Additionaly it stores inserted hash-key mappings for later retrieval.
Expand Down Expand Up @@ -58,10 +58,10 @@ impl<'db> Trie for FatDB<'db> {
self.raw.contains(&key.sha3())
}

fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<DBValue>>
where 'a: 'b, R: Recorder
fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) -> super::Result<Option<Q::Item>>
where 'a: 'key
{
self.raw.get_recorded(&key.sha3(), rec)
self.raw.get_with(&key.sha3(), query)
}
}

Expand Down Expand Up @@ -104,6 +104,7 @@ impl<'db> Iterator for FatDBIterator<'db> {
#[test]
fn fatdb_to_trie() {
use memorydb::MemoryDB;
use hashdb::DBValue;
use trie::{FatDBMut, TrieMut};

let mut memdb = MemoryDB::new();
Expand Down
94 changes: 94 additions & 0 deletions util/src/trie/lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2015, 2016 Parity Technologies (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/>.

//! Trie lookup via HashDB.

use hashdb::HashDB;
use nibbleslice::NibbleSlice;
use rlp::{Rlp, View};
use ::{H256};

use super::{TrieError, Query};
use super::node::Node;

/// Trie lookup helper object.
pub struct Lookup<'a, Q: Query> {
/// database to query from.
pub db: &'a HashDB,
/// Query object to record nodes and transform data.
pub query: Q,
/// Hash to start at
pub hash: H256,
}

impl<'a, Q: Query> Lookup<'a, Q> {
/// Look up the given key. If the value is found, it will be passed to the given
/// function to decode or copy.
pub fn look_up(mut self, mut key: NibbleSlice) -> super::Result<Option<Q::Item>> {
let mut hash = self.hash;

// this loop iterates through non-inline nodes.
for depth in 0.. {
let node_data = match self.db.get(&hash) {
Some(value) => value,
None => return Err(Box::new(match depth {
0 => TrieError::InvalidStateRoot(hash),
_ => TrieError::IncompleteDatabase(hash),
})),
};

self.query.record(&hash, &node_data, depth);

// this loop iterates through all inline children (usually max 1)
// without incrementing the depth.
let mut node_data = &node_data[..];
loop {
match Node::decoded(node_data) {
Node::Leaf(slice, value) => {
return Ok(match slice == key {
true => Some(self.query.decode(value)),
false => None,
})
}
Node::Extension(slice, item) => {
if key.starts_with(&slice) {
node_data = item;
key = key.mid(slice.len());
} else {
return Ok(None)
}
}
Node::Branch(children, value) => match key.is_empty() {
true => return Ok(value.map(move |val| self.query.decode(val))),
false => {
node_data = children[key.at(0) as usize];
key = key.mid(1);
}
},
_ => return Ok(None),
}

// check if new node data is inline or hash.
let r = Rlp::new(node_data);
if r.is_data() && r.size() == 32 {
hash = r.as_val();
break
}
}
}
Ok(None)
}
}
Loading