查看原文
其他

【必读篇】一文带你深入了解IC Canister

D Plus D Plus Community 2022-06-09

文章来自于|tinywateer

投稿、转载请联系|D Plus小助手

互联网计算机(简称IC)上的DAPP可以以Web速度运行, 并且能够去信任化的调用彼此公开的API。这得益于IC上的智能合约Canister(容器)。本文将从Canister的底层逻辑出发,梳理一些关于IC Canister的细节,以便大家和各位技术小伙伴们对Canister有深入的理解。


Canister的基本结构


首先看一下 Canister 的基本结构:


  • Canister ID:标识符,全局唯一

  • 元数据:包括控制者名单(Controller)、Cycle 余额、当前状态、资源配置

  • Wasm代码

  • 运行时数据:堆内存、稳定内存、消息队列、调用相关


Canister 基本结构


Canister的生命周期


然后 Canister 也有自己的生命周期,就像 Kubernetes 的 Pod 一样。


Canister 有四种状态:


  • Created

  • Running

  • Stopping

  • Stopped


Canister的生命周期


Cycles


Canister占用了网络中的计算和存储资源,所以需要付费,而付费的Gas Token是Cycle。


Canister自身可以持有Cycle,在Canister 运行的过程中,Cycle会自动被扣除。


随着时间的推移,Cycle会被逐渐消耗。目前,当Cycle余额不足30天时,Canister会被冻结,余额为 0 时Canister会被删除。


如果有大量Canister,如何保证每个Canister Cycle够用呢,可以在Call Canister的时候顺便发送Cycle,封装一个函数即可。


Cycle的价值与SDR绑定:


  • 1 SDR = 1 Trillion Cycle = 10 的 12 次方 Cycle


那么什么是SDR?


SDR的全称是 Special Drawing Rights,即特别提款权,它本质上是一种记账单位,不是真正的货币,使用时需要兑换成一揽子货币。


目前一揽子货币由 美元、欧元、日元、英镑和人民币 组成,SDR 可以兑换为任何一种,而且汇率会变化,按照 2018 年汇率,1 SDR =~ 1.4 美元。


因为Cycle和SDR 绑定,而SDR和美元可以按汇率兑换,所以可以理解为 Cycle 的价值是和美元绑定的,这就保证了 ICP 代币价格无论涨跌,都不会影响开发者在 IC 中部署的成本,这是很重要的。


Canister的调用方式


继续说一下Canister的两种调用方式。


第一种:是Update Call,Update call 能够改变链上状态,需要 2/3 节点共识,顺序执行,大概 2-3s 能够得到结果。


第二种:是Query Call 属于只读模式,不能改变链上状态,不需要共识,可以并行执行,大概 100 毫秒得到结果。


 Canister的两种调用方式


需要注意的是这里的Query Call 和Update Call 是 IC 内的概念,和传统 Web 开发 CRUD 中的查询和更新不是一回事。


可以简单理解为,Query Call是指不触发共识的调用,Update Call是指触发共识的调用。


如果想使用Query Call,需要在函数定义中加上query 关键字,否则都是Update Call。

 

Query Call 需要指定 query 关键字


不过使用Query Call,它会从子网中的其中一个节点返回结果,比较快,但数据不一定是最新的。


这里再解释一下什么是子网(Subnet),在 IC 中,一个子网就是一个 Blockchain,它的节点是由多个 IDC 中的机器组成,子网可以增加,由 NNS治理系统投票决定(目前大多数子网都是 13 个节点,单个子网的节点数越多容错能力越强)。


Canister的消息格式


下面看一下Canister 消息的具体格式。


给 Canister 发送消息,字段里有如下数据:


  • 1. 请求类型(Update、Query)

  • 2. 发送方(Sender Id):发送方的「用户身份」

  • 3. Nonce(随机数)

  • 4. 有效期

  • 5. 接收方(Canister Id)

  • 6. 方法

  • 7. 参数

  • 8. 1-7 进行 Hash 得到的 RequestID 进行签名


给 Canister 发送消息的格式


发送消息只是发送过去并执行,读取结果还需要发起一个 read_state 的消息。

里面包含:


  • 请求类型:read_state

  • 发送方

  • Nonce(随机数)

  • 有效期

  • 路径(Path)


读取消息的格式


下面是消息处理的步骤:


消息处理的步骤


稳定内存


什么是稳定内存(Stable Memory),它有什么作用。


IC 的一个重要创新是正交持久性,可以让开发者使用内存就可以做到数据的持久性,再也不用考虑使用和维护复杂的中间件。


关于正交持久性的更多详情参见:

https://twitter.com/dominic_w/status/1278480417679499264


在 Motoko 中,直接使用 stable 关键字即可定义需要持久保存的变量,升级之后这个变量数据不会丢失。


下面是一个例子:


稳定内存使用示例


需要注意的是,使用 stable 声明的变量是保存在 Wasm 运行时内存中,不是直接保存在稳定内存。


升级前,把 stable 变量拷贝到 稳定内存 中,升级后,会把 stable 变量从 稳定内存 拷贝回 Wasm 运行时内存。


另外,stable 只支持「稳定类型」,比如 Trie、Array、List ,不支持 HashMap、Buffer 等非稳定类型。


那么如果使用 HashMap,怎么保证持久性呢?


IC 提供了两个系统级别函数:preupgrade 和 postupgrade,可以在两个函数中把 HashMap 和 List 互相做转换。


preupgrade 在升级之前执行,postupgrade 在升级之后执行。


在 preupgrade 中把 HashMap 转换成 List,在 postupgrade 中把 List 转换成 HashMap。


下面是一个例子。


preupgrade / postupgrade 使用示例


另外,社区有人写了一个开源项目 Bucket ,可以在代码中直接读写稳定内存,这样就不需要使用 preupgrade / postupgrade 了。


Bucket项目详情:

https://github.com/PrimLabs/Bucket


GC算法


最后说一下 Canister 的存储限制和 GC 算法。


Canister 的内存包含 Wasm 运行时内存(Wasm RunTime Memory) 和 稳定内存(Stable Memory)。


目前 Canister 的 Wasm 运行时内存最大为 4G,稳定内存最大为 8G,稳定内存在未来扩展到 300G。


但实际上 Wasm 运行时内存 不能使用到 4G,和 GC 算法有关。


如果使用 Coping GC(Minor GC)算法(默认),Wasm 运行时最多可以使用一半,即 2G 内存。


Coping GC 算法描述:


  • 1. 4G 的 Canister 被划分为两个区域 :from space & to space, 分别占用 2G,数据写入时, 所有的数据都在 to space 中

  • 2. 在执行 dfx canister install xxx(default : --all) --mode upgrade, 会进行 GC,即:将活动对象(正在使用的对象,可以是引用也可以是元数据)所在空间换为 from space, 原 from space 换为 to space, 然后对 from space(数据所在的内存空间)进行遍历, 将所有的活动对象迁移到新的 to space 中, 剩下的就是非活动对象, 也就是内存垃圾, Canister 会将这部分数据清理掉


Coping GC


Compacting GC 算法描述:


  • 1.不划分Wasm运行时内存, 即Canister除RTS占用的内存外, 都是heap 内存,4G

  • 2. 首先,遍历heap, 找出所有的活体对象(正在活动/被活动的对象引用), 标记活体对象,标记完后剩下的就是不被引用或者不再使用的对象, 即内存垃圾

  • 3. 然后,清理 heap 内存并将活体对象前移

 

Compacting GC


绿色是未使用内存, 蓝色是存活对象, 灰色是内存垃圾, 白色是可用内存。


那么,如何选择使用哪个算法呢?


  • 1.对执行效率要求高, 但是不存储数据的 Canister, 建议选用 Coping GC,即默认的 GC

  • 2. 对执行效率要求不高, 存储数据(或存储索引数据),或者 Canister 中保存的活体对象比较多的情况下, 建议用 Compacting GC


此外,Compacting GC 消耗的 Cycle 比 Coping GC 多,两者比较参见下方链接。

https://github.com/dfinity/motoko/issues/2033#issuecomment-842379468


从 Coping GC 切换为 Compacting GC 的方法,参见下方链接。

https://github.com/C-B-Elite/Internet-Computer-Research/blob/main/Storage/Storage%20Management.md



每周必看



历史AMA



联系我们

 电报 

       t.me/DFINITY_ZH

 英文推特 

       twitter.com/D_PlusCommunity

 中文推特 

       twitter.com/D_PlusCN

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存