citahub / cita_trie Goto Github PK
View Code? Open in Web Editor NEWRust implementation of the Modified Patricia Tree (aka Trie).
License: Apache License 2.0
Rust implementation of the Modified Patricia Tree (aka Trie).
License: Apache License 2.0
The repo name is cita-trie
, but the crate name in https://github.com/cryptape/cita-trie/blob/master/Cargo.toml#L2 is cita_trie
, I have written it wrong many times when I writing the Cargo.toml for my new projects.
When get the root hash from an empty trie, cita-trie might returns the wrong hash. See codes bellows:
use cita_trie::codec::RLPNodeCodec;
use cita_trie::db::MemoryDB;
use cita_trie::trie::{PatriciaTrie, Trie};
use hex::FromHex;
use ethereum_types::*;
use keccak_hasher::KeccakHasher;
use kvdb::DBValue;
use memorydb;
use patricia_trie::{TrieFactory, TrieSpec};
use patricia_trie_ethereum::RlpCodec;
fn cita_empty_trie_root() {
let mut memdb = MemoryDB::new();
let mut trie = PatriciaTrie::new(&mut memdb, RLPNodeCodec::default());
println!("{:?}", hex::encode(trie.root().unwrap()));
}
fn parity_empty_trie_root() {
let factory = TrieFactory::<_, RlpCodec>::new(TrieSpec::Generic);
let mut memdb = memorydb::MemoryDB::<KeccakHasher, DBValue>::new();
let mut root = H256::default();
let mut t = factory.create(&mut memdb, &mut root);
println!("{:?}", t.root());
}
fn main() {
cita_empty_trie_root();
parity_empty_trie_root();
}
outputs:
root_hash_by_cita_trie: 0xbc2071a4de846f285702447f2589dd163678e0972a8a1b0d28b04ed5c094547f
root_hash_by_parity: 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421
The hash gives by parity equals with go-Ethereum: https://github.com/ethereum/go-ethereum/blob/master/trie/trie.go#L32, so I thought there are some bugs in cita-trie.
Here, use the return of delete_at()
to replace the tree
:
https://github.com/cryptape/cita-trie/blob/f649eb1fa0068c6871e3a8cdeb0c542e5b1a8714/src/trie.rs#L62-L66
But delete_at()
only return a part of origin tree
:
https://github.com/cryptape/cita-trie/blob/f649eb1fa0068c6871e3a8cdeb0c542e5b1a8714/src/trie.rs#L144-L181
So, can PatriciaTrie
work correctly?
See codes below, I think it should be root1 == root2 == root3
, but (root1 == root2) != root3
in fact.
fn main() {
let root1 = {
let mut db = cita_trie::db::MemoryDB::new();
let mut trie = cita_trie::trie::PatriciaTrie::new(&mut db, cita_trie::codec::RLPNodeCodec::default());
trie.insert(b"a", b"a").unwrap();
trie.root().unwrap()
};
let root2 = {
let mut db = cita_trie::db::MemoryDB::new();
let mut trie = cita_trie::trie::PatriciaTrie::new(&mut db, cita_trie::codec::RLPNodeCodec::default());
trie.insert(b"a", b"a").unwrap();
trie.insert(b"b", b"b").unwrap();
trie.root().unwrap();
trie.remove(b"b").unwrap();
trie.root().unwrap()
};
let root3 = {
let mut db = cita_trie::db::MemoryDB::new();
let mut t1 = cita_trie::trie::PatriciaTrie::new(&mut db, cita_trie::codec::RLPNodeCodec::default());
t1.insert(b"a", b"a").unwrap();
t1.insert(b"b", b"b").unwrap();
let root = t1.root().unwrap();
let mut t2 = cita_trie::trie::PatriciaTrie::from(&mut db, cita_trie::codec::RLPNodeCodec::default(), &root).unwrap();
t2.remove(b"b").unwrap();
t2.root().unwrap()
};
println!("{:?}", hex::encode(root1)); // "668aee9307d4b41714f33b61fd7e0704d31b95e42169292ba56af48929079580"
println!("{:?}", hex::encode(root2)); // "668aee9307d4b41714f33b61fd7e0704d31b95e42169292ba56af48929079580"
println!("{:?}", hex::encode(root3)); // "f3313809b6501771b8a698b8c70979f3f8340b4f7e90dc5db96caf81cf5a25e0"
}
The root hash calculation returns a wrong value when some keys are removed. This does not happen for all keys.
Here is a test that reproduces the bug:
#[test]
fn test_insert_and_revert() {
// Arrange
let db = Arc::new(MemoryDB::new(true));
let mut trie = PatriciaTrie::new(db.clone(), Arc::new(HasherKeccak::new()));
let root_0 = trie.commit().unwrap();
// Insert "do"
let root_1 = {
trie.insert(b"do".to_vec(), b"verb".to_vec()).unwrap();
trie.root().unwrap()
};
// Insert "doge"
let root_2 = {
trie.insert(b"doge".to_vec(), b"coin".to_vec()).unwrap();
trie.root().unwrap()
};
// Insert "dog"
{
trie.insert(b"dog".to_vec(), b"puppy".to_vec()).unwrap();
trie.root().unwrap()
};
// Remove "dog"
{
trie.remove(b"dog").unwrap();
// It fails here, the root hash does not match the expected one
assert_eq!(root_2, trie.root().unwrap());
}
// Remove "doge"
{
trie.remove(b"doge").unwrap();
assert_eq!(root_1, trie.root().unwrap());
}
// Remove "do"
{
trie.remove(b"do").unwrap();
// Now the trie is empty but the hash is not the one of an empty trie
assert_eq!(root_0, trie.root().unwrap());
}
}
warning: method is never used: `encode_raw`
--> src/nibbles.rs:88:5
|
88 | pub fn encode_raw(&self) -> (Vec<u8>, bool) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
Finished dev [unoptimized + debuginfo] target(s) in 1.51s
Delete or Note ?
Add cita_trie = "0.1.3"
in Cargo.toml dependencies
will cause compile problems. I checked this problem in the simple test project.
error: this file contains an un-closed delimiter
--> /home/wpf/.cargo/registry/src/github.com-1ecc6299db9ec823/cita_trie-0.1.3/src/db.rs:93:3
|
8 | pub trait DB: Send + Sync + Debug {
| - un-closed delimiter
9 | type Error: Error;
10 | State {
| - this delimiter might not be properly closed...
...
16 | }
| - ...as it matches this but it has different indentation
...
93 | }
...
...
...
while add cita_trie = { git = "https://github.com/cryptape/cita-trie.git", branch = "master" }
, it's ok.
Is there a problem with the crate posted to crate.io?
First thanks for the great work on this! An easier to understand, Rust based merkle tree is much needed!
I've created a simple RocksDb store implementation. You can find it here:
cita-trie-db .
Couple thoughts on the current cita-trie implementation:
DB
Trait to something else (maybe KvDB
). This might reduce the chance of it clashing with other crates. For example, I had to alias RocksDB::DB
.trie.commit()
is private outside the crate. You can call commit via trie.root()
, but it might be useful to be more explicit from the API.trie.commit()
calls self.db.insert/remove
. It'd be nice to be able to batch these calls with RockDb. Maybe the DB
trait can expose a commit(...)
that would allow this.pub fn from(db: &'db mut D, codec: C, root: &C::Hash) -> TrieResult<Self, C, D> {
match db.get(root.as_ref()).map_err(TrieError::DB)? {
Some(data) => {
let mut trie = PatriciaTrie {
root: Node::Empty,
db,
codec,
cache: HashMap::new(),
deleted_keys: vec![],
};
trie.root = trie.decode_node(&data).map_err(TrieError::NodeCodec)?;
Ok(trie)
}
None => Err(TrieError::InvalidStateRoot),
}
}
root
already passed as a parameter, maybe we should not calculate it again in trie.root = trie.decode_node(&data).map_err(TrieError::NodeCodec)?;
So... Could we just use
pub fn from(db: &'db mut D, codec: C, root: &C::Hash) -> TrieResult<Self, C, D> {
Ok(PatriciaTrie {
root: Node::Hash(HashNode::new(root.as_ref())),
db,
codec,
cache: HashMap::new(),
deleted_keys: vec![],
})
}
stale keys can be introduced by deletion and insertion, #10 only handled the deletion part.
let mut memdb = MemoryDB::new();
let mut trie = PatriciaTrie::new(&mut memdb, RLPNodeCodec::default());
for i in 0..10 {
trie.insert(format!("test{}", i).as_bytes(), b"testvalue");
trie.commit();
}
for i in 0..10 {
trie.remove(format!("test{}", i).as_bytes());
trie.commit();
}
println!("clear all:trie.root={:?}", trie.root);
println!("clear all:{:?}", trie.db);
output:
clear all:trie.root=Empty
clear all:MemoryDB { storage: RwLock { data: {[193, 152, 200, 144, 250, 10, 84, 154, 250, 172, 48, 71, 148, 203, 39, 40, 56, 205, 6, 252, 92, 235, 72, 182, 241, 177, 161, 89, 29, 125, 114, 0]: [231, 133, 23, 70, 87, 55, 67, 160, 36, 48, 168, 10, 138, 13... ]} } }
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.