#slice #parse #transaction #blocks #groestlcoin #block #parser

无std groestlcoin_slices

不进行分配解析Groestlcoin对象

1个不稳定版本

0.7.0 2024年2月21日

#2437 in 神奇豆子

MIT 许可证

115KB
2.5K SLoC

MIT license Crates Docs

Groestlcoin切片

一个针对Groestlcoin数据结构(如bsl::Transactionbsl::Block等)的零分配解析库,这些数据结构在[bsl]模块中可用。

通过提供用户感兴趣的数据的Visitor结构来访问数据。

// Calculate the amount of outputs in mainnet block 702861 in satoshi
use groestlcoin_slices::{bsl, Visit, Visitor};
struct Sum(pub u64);
impl Visitor for Sum {
    fn visit_tx_out(&mut self, _vout: usize, tx_out: &bsl::TxOut) -> core::ops::ControlFlow<()>  {
        self.0 += tx_out.value();
        core::ops::ControlFlow::Continue(())
    }
}
let mut sum = Sum(0);
let block_bytes: &[u8] = bitcoin_test_data::blocks::mainnet_702861();
let block = bsl::Block::visit(block_bytes, &mut sum).unwrap();
assert_eq!(sum.0, 2_883_682_728_990)

数据结构是只读的,解析的数据必须在内存中,没有流式API。

权衡

在使用此库之前检查CON,如果它们对您的案例过于限制,请使用rust-groestlcoin

优点

  • 由于解析过程中没有进行分配,反序列化速度非常快。
  • 由于结构中保留了序列化数据的一个切片,序列化是瞬间的。
  • [bsl] 类型适合用作数据库键和值,实际上有一个特定的 redb 特性
  • 因为不需要重新序列化数据,散列速度略快。
  • 没有强制依赖。
  • 没有标准。
  • 通过可选依赖groestlcoin_hashessha2计算txid和区块哈希。
  • 访问者模式仅访问您感兴趣的内容。

缺点

  • 必须将全部数据放在内存中,没有流式(读/写)API。
  • 数据结构是只读的,不能修改。
  • 访问者模式需要用户自定义数据结构进行访问。

特性

散列

使用功能 sha2groestlcoin_hashes 来计算区块和交易哈希。前者更快,后者更有可能在您使用 rust-groestlcoin 生态系统 crates 的情况下存在于您的树中。

redb

当激活了 redb 功能时,某些类型允许在 redb 数据库中用作值和键。groestlcoin 切片类型非常适合用作数据库中的键和值,因为从/到切片的转换是立即的。

#[cfg(feature = "redb")]
{
    use groestlcoin_slices::{bsl, redb, Parse, redb::ReadableTable};
    const UTXOS_TABLE: redb::TableDefinition<bsl::OutPoint, bsl::TxOut> = redb::TableDefinition::new("utxos");
    let path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
    let db = redb::Database::create(path).unwrap();
    let write_txn = db.begin_write().unwrap();
    let tx_out_bytes = hex_lit::hex!("ffffffffffffffff0100");
    let out_point_bytes = [0u8; 36];
    let tx_out = bsl::TxOut::parse(&tx_out_bytes).unwrap().parsed_owned();
    let out_point = bsl::OutPoint::parse(&out_point_bytes).unwrap().parsed_owned();
    {
        let mut table = write_txn.open_table(UTXOS_TABLE).unwrap();
        table.insert(&out_point, &tx_out).unwrap();
    }
    write_txn.commit().unwrap();

    let read_txn = db.begin_read().unwrap();
    let table = read_txn.open_table(UTXOS_TABLE).unwrap();
    assert_eq!(table.get(&out_point).unwrap().unwrap().value(), tx_out);
}

rust-groestlcoin

当激活了 groestlcoin 功能时,某些类型可以转换为 rust-groestlcoin 的对应类型:例如,bsl::TxOut 可以转换为 groestlcoin::TxOut。您可能认为如果您需要 groestlcoin::TxOut,您可以直接将其解码为字节而不使用此库,这通常是正确的,但有时使用这两种类型可能更方便,例如使用 groestlcoin 切片与数据库一起,但您可能需要比编写访问器更方便地访问字段,因此需要将其转换为 rust-groestlcoin 类型。此外,转换可以利用类型不变性和比从通用字节流开始更快。

#[cfg(feature = "groestlcoin")]
{
    use groestlcoin_slices::{bsl, groestlcoin, Parse};

    let tx_out_bytes = hex_lit::hex!("ffffffffffffffff0100");
    let tx_out = bsl::TxOut::parse(&tx_out_bytes).unwrap().parsed_owned();
    let tx_out_groestlcoin: groestlcoin::TxOut =
        groestlcoin::consensus::deserialize(&tx_out_bytes[..]).unwrap();

    let tx_out_back: groestlcoin::TxOut = tx_out.into();

    assert_eq!(tx_out_back, tx_out_groestlcoin);
}

测试

cargo test

基准测试

RUSTFLAGS='--cfg=bench' cargo +nightly bench --all-features
test bsl::block::bench::block_deserialize            ... bench:     289,421 ns/iter (+/- 46,179)
test bsl::block::bench::block_deserialize_groestlcoin    ... bench:   2,719,666 ns/iter (+/- 459,186)
test bsl::block::bench::block_sum_outputs            ... bench:     288,248 ns/iter (+/- 39,013)
test bsl::block::bench::block_sum_outputs_groestlcoin    ... bench:   2,607,791 ns/iter (+/- 321,212)
test bsl::block::bench::find_tx                      ... bench:   1,012,297 ns/iter (+/- 6,278)
test bsl::block::bench::find_tx_groestlcoin              ... bench:   8,632,416 ns/iter (+/- 89,751)
test bsl::block::bench::hash_block_txs               ... bench:   8,406,341 ns/iter (+/- 938,119)
test bsl::block::bench::hash_block_txs_groestlcoin       ... bench:  11,843,590 ns/iter (+/- 1,052,109)
test bsl::block::bench::hash_block_txs_sha2          ... bench:   7,891,956 ns/iter (+/- 1,047,439)
test bsl::block_header::bench::block_hash            ... bench:       1,399 ns/iter (+/- 205)
test bsl::block_header::bench::block_hash_groestlcoin    ... bench:       1,510 ns/iter (+/- 222)
test bsl::transaction::bench::tx_deserialize         ... bench:          38 ns/iter (+/- 8)
test bsl::transaction::bench::tx_deserialize_groestlcoin ... bench:         219 ns/iter (+/- 30)
test bsl::transaction::bench::txid                   ... bench:       2,185 ns/iter (+/- 166)
test bsl::transaction::bench::txid_groestlcoin           ... bench:       2,416 ns/iter (+/- 213)
test bsl::transaction::bench::txid_sha2              ... bench:       2,085 ns/iter (+/- 216)
  • _groestlcoin 结尾的基准测试使用 rust-groestlcoin
  • _sha2 结尾的基准测试使用 sha2 库而不是 groestlcoin_hashes

与 rust-groestlcoin 的比较

block_deserialize 的速度几乎是 block_deserialize_groestlcoin 的 10 倍。这种比较可能不公平,因为例如,在 block_deserialize 的情况下,您不能从结果对象中迭代交易,但在查看使用访问器访问区块中每个输出的 sum_outputs 示例时,我们看到没有明显的差异。

散列

block_hashblock_hash_groestlcoin 使用相同的代码进行哈希,但是因为使用已经可用的切片而不是重新序列化数据,所以 groestlcoin_slice 大约快 7%。在 txidtxid_groestlcoin 之间也有类似的结果。性能提升在 hash_block_txshash_block_txs_groestlcoin 之间更为明显(30%)。

*_sha2 在虚拟 CI 机器上并不真正具有代表性,因为它们不是硬件加速的。

模糊测试

使用 cargo fuzz 将交易作为目标运行模糊测试。

cargo +nightly fuzz run transaction

fuzz/fuzz_targets 中可用的其他目标

最小化语料库

cargo +nightly fuzz cmin transaction

文档

要构建文档

RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --open

MSRV

此 crate 的最小支持 Rust 版本是 1.60.0,没有 redbslice_cache 功能(请与 CI 中运行的版本进行双检查)。带有 slice_cache 功能的 MSRV 是 1.64.0。带有 redb 功能的 MSRV 是 1.66.0。

以前的工作和致谢

  • Bitiodine 使用类似的访问器模式(解析归功于 Mathias Svensson)
  • PR 中有关于在解析时减少分配的一些先前工作
  • Matt Corallo 在那个 PR 中的一个 评论 中提到了类似的内容

依赖项

~0–1.9MB
~27K SLoC