6个版本

0.1.5 2023年5月25日
0.1.4 2021年12月20日
0.1.3 2020年4月6日

算法 中排名 2062

MIT 许可证

86KB
953

Build Status Crates.io

Monotree

Rust语言实现的优化稀疏默克尔树。这是一种基于位分支的二叉基数树,目前 没有使用任何字节。现在,分支单元只是一个 单个位既不是4位也不是字节字节

功能

  • 非常 简单轻量级,但 快速健壮
  • 功能齐全 的稀疏默克尔树 (SMT) 作为存储
  • 包括:非包含证明,以及 包含证明 和其验证。
  • 再次强调,一点也不冗长

此库主要依赖 Rust标准库,除了 数据库API哈希函数。目前,monotree 支持以下数据库和哈希函数,但设计得非常容易定制和添加

数据库包括:

哈希函数包括:

快速入门

examples/basic.rs

有关 非包含证明包含证明,请参阅以下 更多示例 中的 默克尔证明 部分。


use monotree::{Monotree, Result};
use monotree::utils::random_hash;

fn main() -> Result<()> {
    // Init a monotree instance
    // by default, with 'HashMap' and 'Blake3' hash function
    let mut tree = Monotree::default();

    // It is natural the tree root initially has 'None'
    let root = None;

    // Prepare a random pair of key and leaf.
    // random_hash() gives a fixed length of random array,
    // where Hash -> [u8; HASH_LEN], HASH_LEN = 32
    let key = random_hash();
    let leaf = random_hash();

    // Insert the entry (key, leaf) into tree, yielding a new root of tree
    let root = tree.insert(root.as_ref(), &key, &leaf)?;
    assert_ne!(root, None);

    // Get the leaf inserted just before. Note that the last root was used.
    let found = tree.get(root.as_ref(), &key)?;
    assert_eq!(found, Some(leaf));

    // Remove the entry
    let root = tree.remove(root.as_ref(), &key)?;

    // surely, the tree has nothing and the root back to 'None'
    assert_eq!(tree.get(root.as_ref(), &key)?, None);
    assert_eq!(root, None);
    Ok(())
}

性能

所有基准测试都是累积进行的,即后续操作使用仅在进行操作之前生成的根。它们使用随机生成的字节条目和从空树根开始执行。(删除从最后一个根开始。)

测试环境:Intel Core i5-3570 CPU @ 3.4GHz 和 16 GB RAM / Ubuntu 18.04,使用 rustc stable 1.42.0,作为哈希器,本次基准测试使用了 Blake3

操作。 使用的数据库 条目数 测量时间 标准误差 每个操作
插入 HashMap 10 8.50 us 0.01 us 0.85 us
插入 HashMap 100 165.71 us 0.14 us 1.66 us
插入 HashMap 1000 2.32 ms 0.01 ms 2.32 us
插入 HashMap 10000 37.39 ms 0.02 ms 3.74 us
获取 HashMap 10 7.82 us 0.01 us 0.78 us
获取 HashMap 100 114.51 us 0.14 us 1.15 us
获取 HashMap 1000 1.52 ms 0.01 ms 1.52 us
获取 HashMap 10000 20.40 ms 0.01 ms 2.40 us
删除 HashMap 10 15.65 us 0.01 us 1.57 us
删除 HashMap 100 282.68 us 0.09 us 2.83 us
删除 HashMap 1000 4.23 ms 0.01 ms 4.23 us
删除 HashMap 10000 69.15 ms 0.07 ms 6.92 us
插入 RocksDB 10 34.80 us 0.27 us 3.48 us
插入 RocksDB 100 602.55 us 2.72 us 6.03 us
插入 RocksDB 1000 10.35 ms 0.06 ms 10.35 us
插入 RocksDB 10000 145.83 ms 0.20 ms 14.58 us
获取 RocksDB 10 10.20 us 0.06 us 1.02 us
获取 RocksDB 100 142.20 us 0.79 us 1.42 us
获取 RocksDB 1000 1.74 ms 0.01 ms 1.74 us
获取 RocksDB 10000 22.94 ms 0.03 ms 2.29 us
删除 RocksDB 10 58.33 us 0.50 us 5.83 us
删除 RocksDB 100 1.02 ms 6.40 us 10.20 us
删除 RocksDB 1000 20.22 ms 0.10 ms 20.22 us
删除 RocksDB 10000 394.95 ms 1.46 ms 39.50 us

集成测试和基准测试

执行包含数据库和哈希器的所有操作和树类型的组合的集成测试。

    ## Some tests are time consuming.
    ## --release is optional, but without it, it will take a longer time to complete the tests
    $ cargo test --release --features "db_rocksdb, db_sled"

基于 Criterion 执行微基准测试,包括数据库和哈希器的所有操作和树类型的组合。

    $ cargo bench --features "db_rocksdb, db_sled"

以及所有树类型的宏观时间基准测试(但误差范围较宽),已在 examples/perf.rs 中准备。

    $ cargo run --release --example perf --features "db_rocksdb, db_sled"

以及一些描述如何操作 monotree 的示例。

    $ cargo run --example basic
    $ cargo run --example advanced --features "db_rocksdb"

进一步改进

monotree 是广义二叉基数树中的特殊情况,我将其称为 PoT (Power Of Two) 基数树。如果使用 pow(2, n) 的十六进制数作为分支单元对 monotree 进行泛化,那么将有机会进一步改进性能。

这项泛化正在进行中。

更多示例

来自 examples/advanced.rs


use monotree::database::*;
use monotree::hasher::*;
use monotree::utils::*;
use monotree::*;

fn main() -> Result<()> {
    // Init a monotree instance:
    // manually select a db and a hasher as your preference
    // Monotree::<DATABASE, HASHER>::new(DB_PATH)
    // where DATABASE = {MemoryDB, RocksDB, Sled}
    //         HASHER = {Blake3, Blake2s, Blake2b, Sha2, Sha3}
    let mut tree = Monotree::<RocksDB, Blake2b>::new("/tmp/monotree");

    // It is natural the tree root initially has 'None'
    let root = None;

    // Prepare 500 random pairs of key and leaf.
    // random_hashes() gives Vec<Hash>
    // where Hash is a fixed length of random array or [u8; HASH_LEN]
    let n = 500;
    let keys = random_hashes(n);
    let leaves = random_hashes(n);

    // Insert a bunch of entries of (key, leaf) into tree.
    // looks quite similar with 'monotree::insert()', but for insertion using batch.
    // 'inserts()' is much faster than 'insert()' since it's based on the following:
    // (1) DB batch-write, (2) sorting keys before insertion, and (3) mem-cache.
    let root = tree.inserts(root.as_ref(), &keys, &leaves)?;
    assert_ne!(root, None);

    // Similarly, there are methods 'gets()' and 'removes()' for batch use of
    // 'get()' and 'remove()', respectively.
    let result = tree.gets(root.as_ref(), &keys)?;
    assert_eq!(result.len(), keys.len());

    let root = tree.removes(root.as_ref(), &keys)?;
    // surely, the tree has nothing nad the root back to 'None'
    assert_eq!(root, None);

    /////////////////////////////////////////////////////////////////////
    // `Merkle proof` secion: verifying inclusion of data (inclusion proof)

    // `Monotree` has compressed representation, but it fully retains
    // the properties of the Sparse Merkle Tree (SMT).
    // Thus, `non-inclusion proof` is quite straightforward. Just go walk down
    // the tree with a key (or a path) given. If we cannot successfully get a leaf,
    // we can assure that the leaf is not a part of the tree.
    // The process of inclusion proof is below:

    // random pre-insertion for Merkle proof test
    let root = tree.inserts(root.as_ref(), &keys, &leaves)?;

    // pick a random key from keys among inserted just before
    let key = keys[99];

    // generate the Merkle proof for the root and the key
    let proof = tree.get_merkle_proof(root.as_ref(), &key)?;

    // To verify the proof correctly, you need to provide a hasher matched
    // Previously the tree was initialized with `Blake2b`
    let hasher = Blake2b::new();

    // get a leaf matched with the key: where the Merkle proof verification starts off
    let leaf = leaves[99];

    // verify the Merkle proof using all those above
    let verified = verify_proof(&hasher, root.as_ref(), &leaf, proof.as_ref());
    assert_eq!(verified, true);

    /////////////////////////////////////////////////////////////////////
    // Usage examples with some functional tests
    // Carefully trace the variable `root` as they are frequently shadowed.

    let mut tree = Monotree::default();
    let mut root = None;
    let hasher = Blake3::new();

    //--- insert/get and gen_proof/verify_proof over iterator
    for (i, (key, value)) in keys.iter().zip(leaves.iter()).enumerate() {
        // insert a key into tree
        root = tree.insert(root.as_ref(), key, value)?;

        // inserted a key and yields a root, where cumulative check-up goes on
        for (k, v) in keys.iter().zip(leaves.iter()).take(i + 1) {
            // check if the key-value pair was correctly inserted so far
            assert_eq!(tree.get(root.as_ref(), k)?, Some(*v));

            // generates a Merkle proof with all keys so far
            let proof = tree.get_merkle_proof(root.as_ref(), k)?;

            // verify the Merkle proof with all keys so far
            assert_eq!(
                verify_proof(&hasher, root.as_ref(), v, proof.as_ref()),
                true
            );
        }
    }
    assert_ne!(root, None);

    //--- insert/get and gen_proof/verify_proof after each deletion of entry
    for (i, (key, _)) in keys.iter().zip(leaves.iter()).enumerate() {
        assert_ne!(root, None);

        // assert that all other values are fine after each deletion
        for (k, v) in keys.iter().zip(leaves.iter()).skip(i) {
            // check in the same way as the previous example
            assert_eq!(tree.get(root.as_ref(), k)?, Some(*v));
            let proof = tree.get_merkle_proof(root.as_ref(), k)?;
            assert_eq!(
                verify_proof(&hasher, root.as_ref(), v, proof.as_ref()),
                true
            );
        }
        // delete a key and check if it was correctly removed
        root = tree.remove(root.as_ref(), key)?;
        assert_eq!(tree.get(root.as_ref(), key)?, None);
    }
    // must be back to inital state of tree
    assert_eq!(root, None);

    //--- faster way to insert/remove entries
    // Now tree is empty, and root is back to `None` again
    // Redo all those above using methods supporting batch operations
    root = tree.inserts(root.as_ref(), &keys, &leaves)?;
    assert_ne!(root, None);

    // Even if we shuffle the keys when removing,
    shuffle(&mut keys);

    // there's no difference. Back to `None` of root and empty tree again.
    // that's why the `root` plays a role as "state index of tree"
    root = tree.removes(root.as_ref(), &keys)?;
    assert_eq!(root, None);

    Ok(())
}

依赖项

~4.5–9.5MB
~170K SLoC