16 个不稳定版本 (7 个破坏性变更)

0.8.0 2024 年 4 月 27 日
0.7.0 2023 年 11 月 1 日
0.6.2 2023 年 8 月 3 日
0.6.0 2023 年 7 月 28 日
0.4.3 2022 年 10 月 11 日

#1199 in 魔法豆

Download history 454/week @ 2024-04-16 593/week @ 2024-04-23 496/week @ 2024-04-30 437/week @ 2024-05-07 517/week @ 2024-05-14 399/week @ 2024-05-21 260/week @ 2024-05-28 265/week @ 2024-06-04 270/week @ 2024-06-11 210/week @ 2024-06-18 199/week @ 2024-06-25 172/week @ 2024-07-02 267/week @ 2024-07-09 173/week @ 2024-07-16 159/week @ 2024-07-23 171/week @ 2024-07-30

每月 789 次下载
用于 4 crates

MIT 许可证

110KB
2.5K SLoC

MIT license Crates Docs

Bitcoin slices

为 Bitcoin 数据结构(如 bsl::Transactionbsl::Block 等)提供零分配解析库,这些数据结构在 [bsl] 模块中可用。

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

// Calculate the amount of outputs in mainnet block 702861 in satoshi
use bitcoin_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-bitcoin

优点

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

缺点

  • 所有数据必须驻留在内存中,没有流式(读取/写入)API。
  • 数据结构为只读,不可修改。
  • 访问者模式需要用户构建的数据结构进行访问。

功能

哈希

使用功能 sha2bitcoin_hashes 来计算区块和交易哈希。前者更快,后者更有可能在您使用 rust-bitcoin 生态系统 crate 的情况下出现在您的树中。

redb

在激活了 redb 功能的情况下,一些类型可以用作 redb 数据库的值和键。Bitcoin 切片类型非常适合用作数据库中的键和值,因为从切片到切片的转换是立即的。

#[cfg(feature = "redb")]
{
    use bitcoin_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-bitcoin

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

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

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

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

    assert_eq!(tx_out_back, tx_out_bitcoin);
}

测试

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_bitcoin    ... 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_bitcoin    ... 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_bitcoin              ... 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_bitcoin       ... 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_bitcoin    ... bench:       1,510 ns/iter (+/- 222)
test bsl::transaction::bench::tx_deserialize         ... bench:          38 ns/iter (+/- 8)
test bsl::transaction::bench::tx_deserialize_bitcoin ... bench:         219 ns/iter (+/- 30)
test bsl::transaction::bench::txid                   ... bench:       2,185 ns/iter (+/- 166)
test bsl::transaction::bench::txid_bitcoin           ... bench:       2,416 ns/iter (+/- 213)
test bsl::transaction::bench::txid_sha2              ... bench:       2,085 ns/iter (+/- 216)
  • _bitcoin 结尾的基准使用 rust-bitcoin
  • _sha2 结尾的基准使用 sha2 库而不是 bitcoin_hashes

与 rust-bitcoin 的比较

block_deserializeblock_deserialize_bitcoin 快近 10 倍。这种比较可能不公平,因为例如在 block_deserialize 的情况下,您无法迭代结果对象中的交易,但在查看使用访问者访问区块中每个输出的 sum_outputs 示例时,我们发现没有明显的差异。

哈希

block_hashblock_hash_bitcoin 使用相同的代码进行哈希,但是 bitcoin_slice 大约快 7%,因为它使用已提供的切片而不是重新序列化数据。类似的结果适用于 txidtxid_bitcoin。性能提升在 hash_block_txshash_block_txs_bitcoin 之间更为明显(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
~28K SLoC