20个版本 (10个主要更新)
10.1.0 | 2024年4月4日 |
---|---|
10.0.0 | 2024年2月22日 |
9.0.2 | 2024年2月7日 |
8.1.0 | 2023年10月18日 |
0.3.1 |
|
#1008 in 魔法豆
每月1,222次下载
用于concordium-cis2
460KB
7K SLoC
为在Rust编程语言中编写Concordium区块链智能合约提供高级接口的标准库。
链接
维护者使用
库的高级设计
库旨在提供易于使用的API来使用链API,同时确保用户不会出错。库暴露了两种类型的API。高级API牺牲了一些性能和代码大小,但几乎允许用户假装合约状态是内存状态,而不是实际的外部数据库。低级API以Rust-like方式暴露了链提供的API,这样用户就不必直接处理指针。
高级API的设计广泛使用了Rust功能来防止错误,并假设不会同时使用低级API。也就是说,高级API维护了可能会被低级API破坏的不变量。这些不变量通过封装(即隐藏实现细节)和类型系统来维护,包括广泛使用生命周期。
低级API暴露了一个键值存储,这也是链暴露的接口。键是任意字节数组,值也是字节数组。该API以条目为中心构建。当查找键时,返回一个条目。然后可以使用Read和Write特性来读取和写入此条目。
高级API以“分配器”为中心构建。它暴露了三个高级集合
- 一个映射
- 一个集合
- 一个“盒子”,即一个间接值。
支持嵌套映射,以及集合作为映射中的值。
高级API不是直接与字节数组交互,而是与正常的Rust结构化类型交互。这些类型到字节数组的序列化(对于键和值)是自动处理的。API保持一个不变性,即如果存储了类型T的值,则可以在以后检索并反序列化。由于低级API可以用于在任意键上写入任意数据,这可能会违反这一不变性。
高级API维护的第二个不变性是,在映射上有活动迭代器时,不能修改映射的结构(即不允许插入或删除)。这是通过生命周期和Rust中可变引用的唯一性实现的,可变引用不能与不可变引用(指向同一对象,有关生命周期传递的详细信息见)共存,类似于标准Rust集合,例如BTreeMap。由于节点API暴露的API不允许修改正在迭代的树的部分,因此这种限制是必要的。
这些不变性使得在代码的许多地方,如果在操作可能会失败但由不变性保证的情况下,我们中断程序。例如,当在映射中查找值时,我们假设反序列化不会失败。
另一个值得注意的项目是使用Drop
来确保数据写入合约状态。具体案例是条目的修改。例如,当我们查找映射中的条目时,我们可能得到一个对条目内部存储的值的&mut
引用。然后可以使用它来修改值。但是所有这些都是在内存中发生的,并在某个时候必须写入实际的合约状态。一个选项是要求用户使用诸如commit
之类的函数来完成此操作,但这非常容易出错,尤其是在API看起来非常接近正常集合API的情况下。因此,我们使用Drop
实现来将更改提交到合约状态。有一些优化,以避免在明确没有更改的情况下写入合约状态。但这不是非常精确(它是一个过度近似,即它经常将其视为已修改),因此,如果不想写入状态,通常不应该获取&mut
引用。
这种使用Drop的做法依赖于上面列出的第二个不变性,即由于生命周期限制,不允许重叠修改映射。如果不是这样,我们可能会面临不一致的状态视图,内存中的值与查找的值不同。
高级API布局
高级状态的根本存储在空键中,即 &[]
。这通常是一个Rust结构体或其它某种结构化类型。在其内部可能有映射、集合或盒子。这些通过 分配器 分配。分配器决定存储映射的下一个空闲位置。分配器自身必须存储在合约状态中,并存储在位置 [0,0,0,0,0,0,0]
。该位置的存储值是一个64位整数,被解释为位置(以小端字节计)。创建新的映射会查找分配器值,并更新它。
高级API依赖于它是唯一一个更新此分配器的API。 通过其他方法修改值将导致不可预测的结果。
因此,每个映射(和集合)都与一个位置 ℓ
相关联,这是一个64位整数。映射中的键值对(K,V)随后存储在由 ℓ
(小端)与 K
的序列化组合而成的键中(在合约状态中)。
依赖关系
~1.6–3.5MB
~58K SLoC