Trie query recording and AccountDB factory for no mangling (#1944)

* optionally use no mangling for accountdb

* add the recorder module

* get_recorded for tries, no virtual dispatch on readonly tries

* add recording test
This commit is contained in:
Robert Habermeier
2016-08-24 16:53:36 +02:00
committed by Gav Wood
parent 33e0a234f2
commit 190e4db266
24 changed files with 590 additions and 182 deletions

View File

@@ -136,4 +136,4 @@ impl<T: HashDB> AsHashDB for T {
fn as_hashdb_mut(&mut self) -> &mut HashDB {
self
}
}
}

View File

@@ -17,7 +17,7 @@
use hash::H256;
use sha3::Hashable;
use hashdb::HashDB;
use super::{TrieDB, Trie, TrieDBIterator, TrieItem};
use super::{TrieDB, Trie, TrieDBIterator, TrieItem, Recorder};
/// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database.
/// Additionaly it stores inserted hash-key mappings for later retrieval.
@@ -43,16 +43,11 @@ impl<'db> FatDB<'db> {
pub fn db(&self) -> &HashDB {
self.raw.db()
}
/// Iterator over all key / vlaues in the trie.
pub fn iter(&self) -> FatDBIterator {
FatDBIterator::new(&self.raw)
}
}
impl<'db> Trie for FatDB<'db> {
fn iter<'a>(&'a self) -> Box<Iterator<Item = TrieItem> + 'a> {
Box::new(FatDB::iter(self))
Box::new(FatDBIterator::new(&self.raw))
}
fn root(&self) -> &H256 {
@@ -63,10 +58,10 @@ impl<'db> Trie for FatDB<'db> {
self.raw.contains(&key.sha3())
}
fn get<'a, 'key>(&'a self, key: &'key [u8]) -> super::Result<Option<&'a [u8]>>
where 'a: 'key
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<&'a [u8]>>
where 'a: 'b, R: Recorder
{
self.raw.get(&key.sha3())
self.raw.get_recorded(&key.sha3(), rec)
}
}

View File

@@ -34,6 +34,9 @@ pub mod triedbmut;
pub mod sectriedb;
/// Export the sectriedbmut module.
pub mod sectriedbmut;
/// Trie query recording.
pub mod recorder;
mod fatdb;
mod fatdbmut;
@@ -45,6 +48,7 @@ pub use self::sectriedbmut::SecTrieDBMut;
pub use self::sectriedb::SecTrieDB;
pub use self::fatdb::{FatDB, FatDBIterator};
pub use self::fatdbmut::FatDBMut;
pub use self::recorder::Recorder;
/// Trie Errors.
///
@@ -88,7 +92,14 @@ pub trait Trie {
}
/// What is the value of the given key in this trie?
fn get<'a, 'key>(&'a self, key: &'key [u8]) -> Result<Option<&'a [u8]>> where 'a: 'key;
fn get<'a, 'key>(&'a self, key: &'key [u8]) -> Result<Option<&'a [u8]>> where 'a: 'key {
self.get_recorded(key, &mut recorder::NoOp)
}
/// Query the value of the given key in this trie while recording visited nodes
/// to the given recorder. If the query fails, the nodes passed to the recorder are unspecified.
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result<Option<&'a [u8]>>
where 'a: 'b, R: Recorder;
/// Returns an iterator over elements of trie.
fn iter<'a>(&'a self) -> Box<Iterator<Item = TrieItem> + 'a>;
@@ -119,7 +130,6 @@ pub trait TrieMut {
fn remove(&mut self, key: &[u8]) -> Result<()>;
}
/// Trie types
#[derive(Debug, PartialEq, Clone)]
pub enum TrieSpec {
@@ -143,6 +153,51 @@ pub struct TrieFactory {
spec: TrieSpec,
}
/// All different kinds of tries.
/// This is used to prevent a heap allocation for every created trie.
pub enum TrieKinds<'db> {
/// A generic trie db.
Generic(TrieDB<'db>),
/// A secure trie db.
Secure(SecTrieDB<'db>),
/// A fat trie db.
Fat(FatDB<'db>),
}
// wrapper macro for making the match easier to deal with.
macro_rules! wrapper {
($me: ident, $f_name: ident, $($param: ident),*) => {
match *$me {
TrieKinds::Generic(ref t) => t.$f_name($($param),*),
TrieKinds::Secure(ref t) => t.$f_name($($param),*),
TrieKinds::Fat(ref t) => t.$f_name($($param),*),
}
}
}
impl<'db> Trie for TrieKinds<'db> {
fn root(&self) -> &H256 {
wrapper!(self, root,)
}
fn is_empty(&self) -> bool {
wrapper!(self, is_empty,)
}
fn contains(&self, key: &[u8]) -> Result<bool> {
wrapper!(self, contains, key)
}
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], r: &'b mut R) -> Result<Option<&'a [u8]>>
where 'a: 'b, R: Recorder {
wrapper!(self, get_recorded, key, r)
}
fn iter<'a>(&'a self) -> Box<Iterator<Item = TrieItem> + 'a> {
wrapper!(self, iter,)
}
}
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
impl TrieFactory {
/// Creates new factory.
@@ -153,11 +208,11 @@ impl TrieFactory {
}
/// Create new immutable instance of Trie.
pub fn readonly<'db>(&self, db: &'db HashDB, root: &'db H256) -> Result<Box<Trie + 'db>> {
pub fn readonly<'db>(&self, db: &'db HashDB, root: &'db H256) -> Result<TrieKinds<'db>> {
match self.spec {
TrieSpec::Generic => Ok(Box::new(try!(TrieDB::new(db, root)))),
TrieSpec::Secure => Ok(Box::new(try!(SecTrieDB::new(db, root)))),
TrieSpec::Fat => Ok(Box::new(try!(FatDB::new(db, root)))),
TrieSpec::Generic => Ok(TrieKinds::Generic(try!(TrieDB::new(db, root)))),
TrieSpec::Secure => Ok(TrieKinds::Secure(try!(SecTrieDB::new(db, root)))),
TrieSpec::Fat => Ok(TrieKinds::Fat(try!(FatDB::new(db, root)))),
}
}

236
util/src/trie/recorder.rs Normal file
View File

@@ -0,0 +1,236 @@
// 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 sha3::Hashable;
use {Bytes, H256};
/// A record of a visited node.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Record {
/// The depth of this node.
pub depth: u32,
/// The raw data of the node.
pub data: Bytes,
/// The hash of the data.
pub hash: H256,
}
/// Trie node recorder.
///
/// These are used to record which nodes are visited during a trie query.
/// Inline nodes are not to be recorded, as they are contained within their parent.
pub trait Recorder {
/// Record that the given node has been visited.
///
/// The depth parameter is the depth of the visited node, with the root node having depth 0.
fn record(&mut self, hash: &H256, data: &[u8], depth: u32);
/// Drain all accepted records from the recorder in ascending order by depth.
fn drain(&mut self) -> Vec<Record> where Self: Sized;
}
/// A no-op trie recorder. This ignores everything which is thrown at it.
pub struct NoOp;
impl Recorder for NoOp {
#[inline]
fn record(&mut self, _hash: &H256, _data: &[u8], _depth: u32) {}
#[inline]
fn drain(&mut self) -> Vec<Record> { Vec::new() }
}
/// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface
/// properly.
pub struct BasicRecorder {
nodes: Vec<Record>,
min_depth: u32,
}
impl BasicRecorder {
/// Create a new `BasicRecorder` which records all given nodes.
#[inline]
pub fn new() -> Self {
BasicRecorder::with_depth(0)
}
/// Create a `BasicRecorder` which only records nodes beyond a given depth.
pub fn with_depth(depth: u32) -> Self {
BasicRecorder {
nodes: Vec::new(),
min_depth: depth,
}
}
}
impl Recorder for BasicRecorder {
fn record(&mut self, hash: &H256, data: &[u8], depth: u32) {
debug_assert_eq!(data.sha3(), *hash);
if depth >= self.min_depth {
self.nodes.push(Record {
depth: depth,
data: data.into(),
hash: *hash,
})
}
}
fn drain(&mut self) -> Vec<Record> {
::std::mem::replace(&mut self.nodes, Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use sha3::Hashable;
use ::H256;
#[test]
fn no_op_does_nothing() {
let mut no_op = NoOp;
let (node1, node2) = (&[1], &[2]);
let (hash1, hash2) = (node1.sha3(), node2.sha3());
no_op.record(&hash1, node1, 1);
no_op.record(&hash2, node2, 2);
assert_eq!(no_op.drain(), Vec::new());
}
#[test]
fn basic_recorder() {
let mut basic = BasicRecorder::new();
let node1 = vec![1, 2, 3, 4];
let node2 = vec![4, 5, 6, 7, 8, 9, 10];
let (hash1, hash2) = (node1.sha3(), node2.sha3());
basic.record(&hash1, &node1, 0);
basic.record(&hash2, &node2, 456);
let record1 = Record {
data: node1,
hash: hash1,
depth: 0,
};
let record2 = Record {
data: node2,
hash: hash2,
depth: 456
};
assert_eq!(basic.drain(), vec![record1, record2]);
}
#[test]
fn basic_recorder_min_depth() {
let mut basic = BasicRecorder::with_depth(400);
let node1 = vec![1, 2, 3, 4];
let node2 = vec![4, 5, 6, 7, 8, 9, 10];
let hash1 = node1.sha3();
let hash2 = node2.sha3();
basic.record(&hash1, &node1, 0);
basic.record(&hash2, &node2, 456);
let records = basic.drain();
assert_eq!(records.len(), 1);
assert_eq!(records[0].clone(), Record {
data: node2,
hash: hash2,
depth: 456,
});
}
#[test]
fn trie_record() {
use trie::{TrieDB, TrieDBMut, Trie, TrieMut};
use memorydb::MemoryDB;
let mut db = MemoryDB::new();
let mut root = H256::default();
{
let mut x = TrieDBMut::new(&mut db, &mut root);
x.insert(b"dog", b"cat").unwrap();
x.insert(b"lunch", b"time").unwrap();
x.insert(b"notdog", b"notcat").unwrap();
x.insert(b"hotdog", b"hotcat").unwrap();
x.insert(b"letter", b"confusion").unwrap();
x.insert(b"insert", b"remove").unwrap();
x.insert(b"pirate", b"aargh!").unwrap();
x.insert(b"yo ho ho", b"and a bottle of rum").unwrap();
}
let trie = TrieDB::new(&db, &root).unwrap();
let mut recorder = BasicRecorder::new();
trie.get_recorded(b"pirate", &mut recorder).unwrap().unwrap();
let nodes: Vec<_> = recorder.drain().into_iter().map(|r| r.data).collect();
assert_eq!(nodes, vec![
vec![
248, 81, 128, 128, 128, 128, 128, 128, 160, 50, 19, 71, 57, 213, 63, 125, 149,
92, 119, 88, 96, 80, 126, 59, 11, 160, 142, 98, 229, 237, 200, 231, 224, 79, 118,
215, 93, 144, 246, 179, 176, 160, 118, 211, 171, 199, 172, 136, 136, 240, 221, 59,
110, 82, 86, 54, 23, 95, 48, 108, 71, 125, 59, 51, 253, 210, 18, 116, 79, 0, 236,
102, 142, 48, 128, 128, 128, 128, 128, 128, 128, 128, 128
],
vec![
248, 60, 206, 134, 32, 105, 114, 97, 116, 101, 134, 97, 97, 114, 103, 104, 33,
128, 128, 128, 128, 128, 128, 128, 128, 221, 136, 32, 111, 32, 104, 111, 32, 104,
111, 147, 97, 110, 100, 32, 97, 32, 98, 111, 116, 116, 108, 101, 32, 111, 102,
32, 114, 117, 109, 128, 128, 128, 128, 128, 128, 128
]
]);
trie.get_recorded(b"letter", &mut recorder).unwrap().unwrap();
let nodes: Vec<_> = recorder.drain().into_iter().map(|r| r.data).collect();
assert_eq!(nodes, vec![
vec![
248, 81, 128, 128, 128, 128, 128, 128, 160, 50, 19, 71, 57, 213, 63, 125, 149,
92, 119, 88, 96, 80, 126, 59, 11, 160, 142, 98, 229, 237, 200, 231, 224, 79, 118,
215, 93, 144, 246, 179, 176, 160, 118, 211, 171, 199, 172, 136, 136, 240, 221,
59, 110, 82, 86, 54, 23, 95, 48, 108, 71, 125, 59, 51, 253, 210, 18, 116, 79,
0, 236, 102, 142, 48, 128, 128, 128, 128, 128, 128, 128, 128, 128
],
vec![
248, 99, 128, 128, 128, 128, 200, 131, 32, 111, 103, 131, 99, 97, 116, 128, 128,
128, 206, 134, 32, 111, 116, 100, 111, 103, 134, 104, 111, 116, 99, 97, 116, 206,
134, 32, 110, 115, 101, 114, 116, 134, 114, 101, 109, 111, 118, 101, 128, 128,
160, 202, 250, 252, 153, 229, 63, 255, 13, 100, 197, 80, 120, 190, 186, 92, 5,
255, 135, 245, 205, 180, 213, 161, 8, 47, 107, 13, 105, 218, 1, 9, 5, 128,
206, 134, 32, 111, 116, 100, 111, 103, 134, 110, 111, 116, 99, 97, 116, 128, 128
],
vec![
235, 128, 128, 128, 128, 128, 128, 208, 133, 53, 116, 116, 101, 114, 137, 99,
111, 110, 102, 117, 115, 105, 111, 110, 202, 132, 53, 110, 99, 104, 132, 116,
105, 109, 101, 128, 128, 128, 128, 128, 128, 128, 128, 128
]
]);
}
}

View File

@@ -18,7 +18,7 @@ use hash::H256;
use sha3::Hashable;
use hashdb::HashDB;
use super::triedb::TrieDB;
use super::{Trie, TrieItem};
use super::{Trie, TrieItem, Recorder};
/// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database.
///
@@ -59,10 +59,10 @@ impl<'db> Trie for SecTrieDB<'db> {
self.raw.contains(&key.sha3())
}
fn get<'a, 'key>(&'a self, key: &'key [u8]) -> super::Result<Option<&'a [u8]>>
where 'a: 'key
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<&'a [u8]>>
where 'a: 'b, R: Recorder
{
self.raw.get(&key.sha3())
self.raw.get_recorded(&key.sha3(), rec)
}
}

View File

@@ -19,6 +19,7 @@ use hashdb::*;
use nibbleslice::*;
use rlp::*;
use super::node::Node;
use super::recorder::{Recorder, NoOp};
use super::{Trie, TrieItem, TrieError};
/// A `Trie` implementation using a generic `HashDB` backing database.
@@ -79,7 +80,7 @@ impl<'db> TrieDB<'db> {
pub fn keys(&self) -> super::Result<Vec<H256>> {
let mut ret: Vec<H256> = Vec::new();
ret.push(self.root.clone());
try!(self.accumulate_keys(try!(self.root_node()), &mut ret));
try!(self.accumulate_keys(try!(self.root_node(&mut NoOp)), &mut ret));
Ok(ret)
}
@@ -114,7 +115,7 @@ impl<'db> TrieDB<'db> {
acc.push(p.as_val());
}
self.accumulate_keys(try!(self.get_node(payload)), acc)
self.accumulate_keys(try!(self.get_node(payload, &mut NoOp, 0)), acc)
};
match node {
@@ -127,18 +128,19 @@ impl<'db> TrieDB<'db> {
}
/// Get the root node's RLP.
fn root_node(&self) -> super::Result<Node> {
self.root_data().map(Node::decoded)
fn root_node<'a, R: 'a + Recorder>(&self, r: &'a mut R) -> super::Result<Node> {
self.root_data(r).map(Node::decoded)
}
/// Get the data of the root node.
fn root_data(&self) -> super::Result<&[u8]> {
fn root_data<'a, R: 'a + Recorder>(&self, r: &'a mut R) -> super::Result<&[u8]> {
self.db.get(self.root).ok_or_else(|| Box::new(TrieError::InvalidStateRoot(*self.root)))
.map(|node| { r.record(self.root, node, 0); node })
}
/// Get the root node as a `Node`.
fn get_node(&'db self, node: &'db [u8]) -> super::Result<Node> {
self.get_raw_or_lookup(node).map(Node::decoded)
fn get_node<'a, R: 'a + Recorder>(&'db self, node: &'db [u8], r: &'a mut R, depth: u32) -> super::Result<Node> {
self.get_raw_or_lookup(node, r, depth).map(Node::decoded)
}
/// Indentation helper for `formal_all`.
@@ -155,7 +157,7 @@ impl<'db> TrieDB<'db> {
Node::Leaf(slice, value) => try!(writeln!(f, "'{:?}: {:?}.", slice, value.pretty())),
Node::Extension(ref slice, item) => {
try!(write!(f, "'{:?} ", slice));
if let Ok(node) = self.get_node(item) {
if let Ok(node) = self.get_node(item, &mut NoOp, 0) {
try!(self.fmt_all(node, f, deepness));
}
},
@@ -166,7 +168,7 @@ impl<'db> TrieDB<'db> {
try!(writeln!(f, "=: {:?}", v.pretty()))
}
for i in 0..16 {
match self.get_node(nodes[i]) {
match self.get_node(nodes[i], &mut NoOp, 0) {
Ok(Node::Empty) => {},
Ok(n) => {
try!(self.fmt_indent(f, deepness + 1));
@@ -188,29 +190,36 @@ impl<'db> TrieDB<'db> {
}
/// Return optional data for a key given as a `NibbleSlice`. Returns `None` if no data exists.
fn do_lookup<'key>(&'db self, key: &NibbleSlice<'key>) -> super::Result<Option<&'db [u8]>>
where 'db: 'key
fn do_lookup<'key, R: 'key>(&'db self, key: &NibbleSlice<'key>, r: &'key mut R) -> super::Result<Option<&'db [u8]>>
where 'db: 'key, R: Recorder
{
let root_rlp = try!(self.root_data());
self.get_from_node(root_rlp, key)
let root_rlp = try!(self.root_data(r));
self.get_from_node(root_rlp, key, r, 1)
}
/// Recursible function to retrieve the value given a `node` and a partial `key`. `None` if no
/// value exists for the key.
///
/// Note: Not a public API; use Trie trait functions.
fn get_from_node<'key>(&'db self, node: &'db [u8], key: &NibbleSlice<'key>) -> super::Result<Option<&'db [u8]>>
where 'db: 'key
{
fn get_from_node<'key, R: 'key>(
&'db self,
node: &'db [u8],
key: &NibbleSlice<'key>,
r: &'key mut R,
d: u32
) -> super::Result<Option<&'db [u8]>> where 'db: 'key, R: Recorder {
match Node::decoded(node) {
Node::Leaf(ref slice, value) if key == slice => Ok(Some(value)),
Node::Extension(ref slice, item) if key.starts_with(slice) => {
let data = try!(self.get_raw_or_lookup(item));
self.get_from_node(data, &key.mid(slice.len()))
let data = try!(self.get_raw_or_lookup(item, r, d));
self.get_from_node(data, &key.mid(slice.len()), r, d + 1)
},
Node::Branch(ref nodes, value) => match key.is_empty() {
true => Ok(value),
false => self.get_from_node(try!(self.get_raw_or_lookup(nodes[key.at(0) as usize])), &key.mid(1))
false => {
let node = try!(self.get_raw_or_lookup(nodes[key.at(0) as usize], r, d));
self.get_from_node(node, &key.mid(1), r, d + 1)
}
},
_ => Ok(None)
}
@@ -219,13 +228,14 @@ impl<'db> TrieDB<'db> {
/// Given some node-describing data `node`, return the actual node RLP.
/// This could be a simple identity operation in the case that the node is sufficiently small, but
/// may require a database lookup.
fn get_raw_or_lookup(&'db self, node: &'db [u8]) -> super::Result<&'db [u8]> {
fn get_raw_or_lookup<R: Recorder>(&'db self, node: &'db [u8], rec: &mut R, d: u32) -> super::Result<&'db [u8]> {
// check if its sha3 + len
let r = Rlp::new(node);
match r.is_data() && r.size() == 32 {
true => {
let key = r.as_val::<H256>();
self.db.get(&key).ok_or_else(|| Box::new(TrieError::IncompleteDatabase(key)))
.map(|raw| { rec.record(&key, raw, d); raw })
}
false => Ok(node)
}
@@ -275,7 +285,7 @@ impl<'a> TrieDBIterator<'a> {
trail: vec![],
key_nibbles: Vec::new(),
};
r.descend(db.root_data().unwrap());
r.descend(db.root_data(&mut NoOp).unwrap());
r
}
@@ -283,7 +293,7 @@ impl<'a> TrieDBIterator<'a> {
fn descend(&mut self, d: &'a [u8]) {
self.trail.push(Crumb {
status: Status::Entering,
node: self.db.get_node(d).unwrap(),
node: self.db.get_node(d, &mut NoOp, 0).unwrap(),
});
match self.trail.last().unwrap().node {
Node::Leaf(n, _) | Node::Extension(n, _) => { self.key_nibbles.extend(n.iter()); },
@@ -341,24 +351,17 @@ impl<'a> Iterator for TrieDBIterator<'a> {
}
}
impl<'db> TrieDB<'db> {
/// Get all keys/values stored in the trie.
pub fn iter(&self) -> TrieDBIterator {
TrieDBIterator::new(self)
}
}
impl<'db> Trie for TrieDB<'db> {
fn iter<'a>(&'a self) -> Box<Iterator<Item = TrieItem> + 'a> {
Box::new(TrieDB::iter(self))
Box::new(TrieDBIterator::new(self))
}
fn root(&self) -> &H256 { self.root }
fn get<'a, 'key>(&'a self, key: &'key [u8]) -> super::Result<Option<&'a [u8]>>
where 'a: 'key
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<&'a [u8]>>
where 'a: 'b, R: Recorder
{
self.do_lookup(&NibbleSlice::new(key))
self.do_lookup(&NibbleSlice::new(key), rec)
}
}
@@ -387,6 +390,8 @@ fn iterator() {
t.insert(x, x).unwrap();
}
}
assert_eq!(d.iter().map(|i|i.to_vec()).collect::<Vec<_>>(), TrieDB::new(&memdb, &root).unwrap().iter().map(|x|x.0).collect::<Vec<_>>());
assert_eq!(d, TrieDB::new(&memdb, &root).unwrap().iter().map(|x|x.1).collect::<Vec<_>>());
let t = TrieDB::new(&memdb, &root).unwrap();
assert_eq!(d.iter().map(|i|i.to_vec()).collect::<Vec<_>>(), t.iter().map(|x|x.0).collect::<Vec<_>>());
assert_eq!(d, t.iter().map(|x|x.1).collect::<Vec<_>>());
}