1 个不稳定版本

0.2.10 2020年1月15日

22#borsh

Download history 23/week @ 2024-03-14 31/week @ 2024-03-21 58/week @ 2024-03-28 40/week @ 2024-04-04 26/week @ 2024-04-11 25/week @ 2024-04-18 23/week @ 2024-04-25 23/week @ 2024-05-02 27/week @ 2024-05-09 28/week @ 2024-05-16 25/week @ 2024-05-23 24/week @ 2024-05-30 21/week @ 2024-06-06 24/week @ 2024-06-13 37/week @ 2024-06-20 10/week @ 2024-06-27

每月 下载 97
8 个 crate 中使用(通过 oasis-borsh

Apache-2.0

44KB
482

borsh

用于哈希的二进制对象表示序列化器

npm npm Crates.io version Download Join the community on Discord Apache 2.0 License Travis Build

网站 | 示例 | 特性 | 基准测试 | 规范 | 发布

为什么我们还需要另一种序列化格式?Borsh 是第一个优先考虑以下对于安全关键项目至关重要的质量的序列化器

  • 一致的指定二进制表示
    • 一致性意味着对象与其二进制表示之间存在一一对应关系。没有两个二进制表示可以反序列化为同一个对象。这对于使用二进制表示来计算哈希的应用程序非常有用;
    • Borsh 提供了一份完整规范,可用于其他语言的实现;
  • 安全。Borsh 实现使用安全的编码实践。在 Rust 中,Borsh 几乎只使用安全代码,只有一个例外,即使用 unsafe 避免耗尽攻击;
  • 速度。在 Rust 中,Borsh 通过放弃 Serde 来实现高性能,在某些情况下比 bincode 更快;这也减少了代码大小。

示例

use borsh::{BorshSerialize, BorshDeserialize};

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)]
struct A {
    x: u64,
    y: String,
}

#[test]
fn test_simple_struct() {
    let a = A {
        x: 3301,
        y: "liber primus".to_string(),
    };
    let encoded_a = a.try_to_vec().unwrap();
    let decoded_a = A::try_from_slice(&encoded_a).unwrap();
    assert_eq!(a, decoded_a);
}

特性

放弃使用 Serde 允许 borsh 具有一些当前 serde 兼容序列化器不可用的特性。目前我们支持两个特性:borsh_initborsh_skip(前者在 Serde 中不可用)。

borsh_init 允许在反序列化后自动运行一个初始化函数。这为那些被设计成严格不可变的对象增加了很多便利。使用示例

#[derive(BorshSerialize, BorshDeserialize)]
#[borsh_init(init)]
struct Message {
    message: String,
    timestamp: u64,
    public_key: CryptoKey,
    signature: CryptoSignature
    hash: CryptoHash
}

impl Message {
    pub fn init(&mut self) {
        self.hash = CryptoHash::new().write_string(self.message).write_u64(self.timestamp);
        self.signature.verify(self.hash, self.public_key);
    }
}

borsh_skip 允许跳过序列化/反序列化字段,假设它们实现了 Default 特性,类似于 #[serde(skip)]

#[derive(BorshSerialize, BorshDeserialize)]
struct A {
    x: u64,
    #[borsh_skip]
    y: f32,
}

基准测试

我们对以下区块链项目最关心的对象进行了基准测试:区块、区块头、交易、账户。我们采用了来自 nearprotocol 区块链的对象结构。我们使用了 Criterion 来构建以下图表。

基准测试是在 Google Cloud n1-standard-2 (2 vCPUs, 7.5 GB memory) 上运行的。

区块头序列化速度与区块头大小(以字节为单位)的关系(大小仅大致对应序列化复杂度,导致图表不光滑)

ser_header

区块头反序列化速度与区块头大小(以字节为单位)的关系

ser_header

区块序列化速度与区块大小(以字节为单位)的关系

ser_header

区块反序列化速度与区块大小(以字节为单位)的关系

ser_header

查看完整报告这里

规范

简而言之,Borsh 是一种非自描述的二进制序列化格式。它被设计为将任何对象序列化为一组规范且确定的字节。

基本原则

  • 整数采用小端序;
  • 动态容器的大小(如数组、向量)作为 u32 写在值之前;
  • 所有无序容器(如 hashmap、hashset)都按照键的字典序排序(在价值平局的情况下按值排序);
  • 结构体按照结构体中的字段顺序进行序列化;
  • 枚举使用 u8 对枚举序号进行序列化,然后存储枚举值内部的数据(如果存在)。

形式规范

非正式类型Rust EBNF * 伪代码
整数 integer_type: ["u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32" | "i64" | "i128" ] little_endian(x)
浮点数 float_type: ["f32" | "f64" ] err_if_nan(x)
little_endian(x as integer_type)
单元 unit_type: "()"我们不写入任何内容
固定大小数组 array_type: '[' ident ';' literal ']' for el in x
  repr(el as ident)
动态大小数组 vec_type: "Vec<" ident '>" repr(len() as u32)
for el in x
  repr(el as ident)
结构体 struct_type: "struct" ident fieldsrepr(fields)
字段 fields: [named_fields | unnamed_fields]
命名字段 named_fields: '{' ident_field0 ':' ident_type0 ',' ident_field1 ':' ident_type1 ',' ... '}' repr(ident_field0 as ident_type0)
repr(ident_field1 as ident_type1)
...
未命名字段 unnamed_fields: '(' ident_type0 ',' ident_type1 ',' ... ')'repr(x.0 as type0)
repr(x.1 as type1)
...
枚举 enum: 'enum' ident '{' variant0 ',' variant1 ',' ... '}'
variant: ident [ fields ] ?
假设 X 是枚举所采用的变体的数量。
repr(X as u8)
repr(x.X as fieldsX)
HashMaphashmap: "HashMap<" ident0, ident1 ">"repr(x.len() as u32)
for (k, v) in x.sorted_by_key() {
  repr(k as ident0)
  repr(v as ident1)
}
HashSethashset: "HashSet<" ident ">"repr(x.len() as u32)
for el in x.sorted() {
 repr(el as ident)
}
Option option_type: "Option<" ident '>" if x.is_some() {
  repr(1 as u8)
  repr(x.unwrap() as ident)
} else {
  repr(0 as u8)
}
String string_type: "String" encoded = utf8_encoding(x) as Vec
repr(encoded.len() as u32)
repr(encoded as Vec)

注意

  • Rust语法的某些部分尚未正式化,例如枚举和变体。我们从syn类型反向推导出Rust语法的EBNF形式;
  • 我们必须扩展EBNF的重复,而不是将它们定义为[ ident_field ':' ident_type ',' ] *,而是将它们定义为ident_field0 ':' ident_type0 ',' ident_field1 ':' ident_type1 ',' ...,以便我们可以在伪代码中引用单个元素;
  • 我们使用repr()函数来表示我们将给定元素的表示写入一个假想的缓冲区。

发布

在您将更改合并到主分支并提升所有三个crate的版本后,是时候正式发布新版本了。

确保borshborsh-deriveborsh-derive-internal都具有新的crate版本。然后进入每个文件夹并按(给定顺序)运行:

cd ../borsh-derive-internal; cargo publish
cd ../borsh-derive; cargo publish
cd ../borsh; cargo publish

确保您处于主分支,然后对代码进行标记并推送标签

git tag -a v9.9.9 -m "My superawesome change."
git push origin v9.9.9

依赖关系

~1.5MB
~34K SLoC