#internet-computer #dfinity #data-structures #stable-structures

ic-stable-structures

用于无畏的canister升级的数据结构集合

23个版本

0.6.5 2024年6月10日
0.6.3 2024年3月4日
0.6.1 2023年12月18日
0.6.0 2023年10月13日
0.3.0 2022年11月18日

#1457 in 魔法豆

Download history 15652/week @ 2024-04-16 15409/week @ 2024-04-23 10726/week @ 2024-04-30 10116/week @ 2024-05-07 8788/week @ 2024-05-14 7948/week @ 2024-05-21 10942/week @ 2024-05-28 12805/week @ 2024-06-04 15740/week @ 2024-06-11 9787/week @ 2024-06-18 9018/week @ 2024-06-25 11438/week @ 2024-07-02 9429/week @ 2024-07-09 7890/week @ 2024-07-16 7883/week @ 2024-07-23 7494/week @ 2024-07-30

每月35,709次下载
用于 57 个crate(32个直接)

Apache-2.0

380KB
7.5K SLoC

Crate Info Apache-2.0 API Docs Chat on the Forum

稳定结构

一组可扩展的数据结构,适用于持久跨升级的互联网计算机

稳定结构的设计旨在使用稳定内存作为后端存储,允许它们在不使用 pre_upgrade/post_upgrade 钩子的情况下增长到数吉字节。

背景

传统的canister状态持久化方法是在 pre_upgrade 钩子中将整个状态序列化到稳定内存,并在 post_upgrade 钩子中解码它。这种方法易于实现并且对于相对较小的数据集来说效果不错。遗憾的是,它扩展性不好,可能会使canister无法升级。

这个库的目标是简化直接在稳定内存中管理数据结构。有关库背后的哲学,请参阅Roman的稳定结构教程

可用的数据结构

  • [BTreeMap]: 一个键值存储
  • [Vec]: 一个可增长的数组
  • [Log]: 一个可变大小条目的只追加列表
  • [Cell]: 一个可序列化的值
  • [MinHeap]: 一个优先队列

教程

模式升级

工作原理

稳定结构能够直接在稳定内存中工作,因为每个数据结构都管理自己的内存。初始化稳定结构时,提供一个内存,数据结构可以使用它来存储其数据。

以下是一个基本示例

use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());

map.insert(1, 2);
assert_eq!(map.get(&1), Some(2));

通过[Memory]特质抽象化内存,稳定结构可以与实现此特质的任何存储后端一起工作。这包括稳定内存、向量([VectorMemory])或甚至平面文件([FileMemory])。

上面的示例使用[DefaultMemoryImpl]初始化[BTreeMap],当在canister中使用时映射到稳定内存,否则映射到[VectorMemory]。

请注意,稳定的结构不能共享内存。每个内存只能属于一个稳定的结构。例如,在canister中运行时会失败。

use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());

map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // This assertion fails.

失败的原因是map_1map_2在底层使用相同的稳定内存,因此对map_1的更改最终会改变或损坏map_2

为了解决这个问题,我们使用了MemoryManager,它接受单个内存并为我们创建最多255个虚拟内存。以下是上面失败的示例,但通过使用MemoryManager进行了修复。

use ic_stable_structures::{
   memory_manager::{MemoryId, MemoryManager},
   BTreeMap, DefaultMemoryImpl,
};
let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(0)));
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(1)));

map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // Succeeds, as expected.

示例Canister

这是一个完全工作的canister示例,将所有内容组合在一起。

依赖项

[dependencies]
ic-cdk = "0.6.8"
ic-cdk-macros = "0.6.8"
ic-stable-structures = "0.5.6"

代码

use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};
use std::cell::RefCell;

type Memory = VirtualMemory<DefaultMemoryImpl>;

thread_local! {
    // The memory manager is used for simulating multiple memories. Given a `MemoryId` it can
    // return a memory that can be used by stable structures.
    static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
        RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    // Initialize a `StableBTreeMap` with `MemoryId(0)`.
    static MAP: RefCell<StableBTreeMap<u128, u128, Memory>> = RefCell::new(
        StableBTreeMap::init(
            MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(0))),
        )
    );
}

// Retrieves the value associated with the given key if it exists.
#[ic_cdk_macros::query]
fn get(key: u128) -> Option<u128> {
    MAP.with(|p| p.borrow().get(&key))
}

// Inserts an entry into the map and returns the previous value of the key if it exists.
#[ic_cdk_macros::update]
fn insert(key: u128, value: u128) -> Option<u128> {
    MAP.with(|p| p.borrow_mut().insert(key, value))
}

更多示例

组合持久性

如果您的项目仅依赖于稳定结构,则内存可以扩展其大小,无需pre_upgrade/post_upgrade钩子。

然而,需要注意的是,如果您还打算执行堆数据的序列化/反序列化,使用内存管理器变得必要。为了有效地结合这两种方法,请参考快速入门示例以获取指导。

模糊测试

稳定的结构需要强大的保证才能可靠地工作并扩展到数百万个操作。为此,我们使用模糊测试来模拟在可用的数据结构上执行这些操作。

在本地运行模糊测试,

rustup toolchain install nightly
cargo install cargo-fuzz

# To list available fuzzer targets
cargo +nightly fuzz list

# To run a target 
cargo +nightly fuzz run <TARGET_NAME>

依赖项

~17–650KB
~11K SLoC