Canister是啥

Canister 里有一个叫做 WebAssembly(Wasm)字节码的东西,它代表了智能合约的程序;还有一组内存页面,它们代表了合约的状态。Canister 就像一个小型服务器一样,可以存储数据,可以保存状态,可以进行 WebSocket 通信等等。

内存页面就像是合约的笔记本。通过安装或更新计算机,我们可以修改 Wasm 字节码。当我们在合约上执行消息时,合约的状态就会发生变化,就好像是在合约的记事本上做出了修改。


每个 Canister 都运行在一个与其他 Canister 和系统其余部分隔离的沙盒环境中,确保数据的隐私和完整性。Canister 通过 WebAssembly(Wasm)运行时执行,它提供了内存安全和类型安全保证。Wasm 还限制了 Canister 可以访问的系统调用,防止恶意 Canister 执行有害操作。


值得一提的是,目前众多公有链如以太坊、波卡等都考虑转换到 Wasm 虚拟机,而 Dfinity 基金会工程师 Andreas 作为其发明者,参与了大部分 Wasm 虚拟机相关的标准制定。

WebAssembly 则是一个虚拟机层面抽象的运行环境,它具有安全、可移植、高效率,轻量化等特点,可以轻松实现毫秒级冷启动时间和极低的资源消耗。可以将现有编程语言应用,如 C/C++ ,Rust 等编译成为 WebAssembly 字节码,运行在沙箱环境中。


WebAssembly

WebAssembly(简称 Wasm )是一种通用的代码格式,它具有高效、可移植以及开源社区支持等特点。尽管它最初设计是为了实现网页端的高性能应用,但它也非常适合用于通用计算。正在被广泛应用于 Web 应用、客户端 - 服务器应用以及区块链应用等领域。Wasm 作为一种更快速、高效和可移植的代码格式,是在 IC 构建高效应用的关键。Wasm 是一种基于堆栈的虚拟机的二机制指令格式。在 Dapp 项目中,智能合约的 Wasm 代码是通过编译高级语言如 Rust 、Motoko 生成的。

Wasm 的可移植性和高性能使其快速成为互联网最受欢迎的代码格式之一。大多数高级语言都可以编译生成高效、可移植的 Wasm 代码。除了 LLVM 可以生成 Wasm ,像 C/C++ 、Rust 、Go 、Java 、JavaScript 等流行语言也可以高效编译成 Wasm 。

Wasm 代码可以浏览器内部执行,也可以在虚拟机中运行,与本机执行相比效率损耗很小。例如,Cloudflare 使用 Wasm 提供 “ 云函数 ” 服务,高级区块链可以用其运行高性能智能合约。

Wasm 与硬件和软件平台无关,可以在多种环境执行,目的是在堆栈式虚拟机上运行。浏览器和独立沙盒进程都可以通过虚拟机支持 Wasm 执行。互联网计算机使用 Wasmtime 项目来运行用户定义的 Canister 智能合约,获得良好性能。


Wasm 代码执行可以轻易实现沙盒化隔离。互联网计算机利用 OS 进程隔离和沙盒保护免受攻击。每个 Canister 编译和执行在自己的沙盒化进程中,只通过安全审计的 IPC 与主进程通信。沙盒只获得执行所需的最少权限。此外,Wasm 代码可以进行形式化验证。

Wasm 具有许多优点,如形式化规范、近本机性能、广泛语言支持、开源社区演进、确定性执行和内存安全性等。


详细了解Canister

IC 是一个革命性的公共区块链,它让智能合约可以直接在链上安全可靠地运行。在 IC 上,智能合约以 Canister 的形式存在。Canister 是什么?它是将智能合约的代码和状态捆绑在一起的计算单元。每个 Canister 都定义了接口,可以被其他 Canister 或者链外的用户(通过浏览器或 App )调用。

多个 Canister 之间可以通过异步消息进行通信。每条消息的执行都完全隔离,支持海量并发。另外,Canister 这个智能合约是可以一直升级的,控制者(开发者或其他 Canister)可以给 Canister 更新代码。

那控制者(Controller)是啥?它负责把 Canister 部署到 IC 上,也可以控制 Canister 的启动 / 停止,更新 Canister 的代码,删除 Canister 等。

一个开发者身份(dfx 可以生成多个身份,即 principal id )可以作为一个控制者,控制多个 Canister 。Canister 的控制者还可以给 Canister 添加其他控制者。

为保证 Canister 顺利运行,控制者还需要为 Canister 添加足够的 Cycles 。Cycles 是 IC 上的 Gas ,用于 Canister 执行所需的计算和存储资源。子网会监控每个 Canister 的资源消耗情况,从 Canister 的 Cycles 余额中扣费。


虽然 Canister 相较智能合约代表了巨大飞跃,但这个不是最终目标。Canister 的存在是为了实现新一代大规模可扩展、互操作的互联网服务。我们今天能在互联网上构建的一切都可以使用 Canister 重构到区块链上。随着潜在数百万个 Canister 的紧密协作,可以构建巨大的去中心化网络。

拥有数亿用户的服务可能需要成千上万个 Canister 来协作存储和管理用户数据、提供内容、执行交易等。但这正是互联网计算机为之设计的未来。它的网络架构和协议被设计为互联网规模。


用户访问

用户可以直接用浏览器或手机 App 访问 Canister ,不需要服务器或者命令行。因为 IC 本身就支持运行的 Web 服务。而在 IC 上,代币、智能合约、数据库、网站都可以直接在 Canister 内运行。去中心化应用从此真正去中心化,不再依赖其他中心化的服务器。

为保证 Canister 的高性能,IC 的服务器配备了大量 SSD 存储和内存。Canister 状态和代码都复制在每个副本里,访问时基本无延迟。状态修改会被节点签名验证。为实现隔离,每个 Canister 在独立的沙箱中运行。每条消息都会启动 Canister 进程,编译执行代码,更新状态,生成响应等。执行信息保存在调用上下文中。这样,Canister 就能安全可靠地运行了。

可以说,Canister 机制让智能合约真正实现了在公共区块链上的去中心化、可扩展、安全的运行。这将开启区块链应用的新纪元。IC 正是通过Canister ,才成为第一个也是唯一一个可以大规模运行 Web 内容和服务的公共区块链。


外部用户验证

IC 中没有针对用户统一的注册列表识别用户。用户向子网发送请求之前,需要先生成一对公私钥。用户公钥的哈希作为用户通用标识(又称 principal id )来向 Canister 来标识自己。

向子网发消息时,用私钥对消息签名。然后把签名和公钥一起发送给边缘节点。验证签名之后,传递 principal id 给到对应的 Canister 。随后 Canister 根据 principal id 和消息中指定操作的其他参数,批准请求的操作。

新用户在首次与 IC 交互时会生成一对密钥对并从公钥中衍生出他们的用户标识。老用户根据存储在用户代理中的私钥完成验证。用户还可以用签名委托的方式,将多个密钥对关联到一个用户身份上。这个特性非常有用,因为它允许了一个用户在多个设备上通过相同的用户身份证明来访问 IC 。


总的来说,IC 让用户通过公钥哈希来注册身份,自己保存对应的私钥。每次互动时使用私钥签名,系统自动验证签名和公钥,这样就知道消息来自谁了。新用户生成密钥对注册身份,老用户通过私钥认证。这样无需中心化服务,去中心化系统也能确认用户身份。


Actor

Canister 为 Actor 模型。容器之间不共享状态、也没有状态锁,通过通讯来处理事务。Canister 间互相调用也是异步的。Canister 之间可以互相通信,但是数据是隔离的,不能保证原子性。原子性只存在于执行 Canister 的一个方法里。

在 Actor 模型中,一个 Actor 可以修改自己的私有状态,向其他 Actor 发送消息,创建新的 Actor 等等。Actor 和 Canister 之间的相似之处非常明显:

  • Canister 拥有只能被它自己修改的私有状态。
  • 它拥有单线程执行,无需锁。
  • Canister 通过异步消息传递进行通信。
  • Canister 可以创建其他 Canister 。

核心区别在于 Canister 之间是双向通信,请求 - 响应模式。IC 处理响应回调给调用者。

在 Actor 术语中,每个 Actor 都有一个用于接收消息的邮件地址。类似地,每个 Canister 都有一个 Canister id (本质上也是一种 principal id)。这使得 Canister 之间以及 Canister 与用户界面之间可以交换消息。

尽管单个 Canister 对状态更新只有一个线程,但 IC 可以在潜在数百万个 Canister 之间大规模地并行执行。这克服了早期智能合约平台的局限。此外,只读查询可以在 Canister 内部并行化,实现巨大的可扩展性。

Motoko 直接受 Actor 模型启发,提供了一种自然的方式来开发 Canister 逻辑。


横向扩展性

在以太坊等平台上,智能合约的计算和存储捆绑在单个单元中 —— 每个智能合约自行处理数据存储和逻辑。这使得水平扩展很困难。相比之下,IC 将计算(在 Canister 里计算)与存储(在子网的所有副本里)分离开来。存储和计算可以独立扩展。

这意味着,与单体智能合约不同,Canister 可以完全关注计算逻辑,同时依赖独立的持久性链进行数据存储。一个 Canister 存储空间不够,可以再创建一个 Canister 存。或者也可以部署一个 “ 母 Canister ” ,专门用来创建 Canister ,这里有个 demo 项目体验。

Canister 也可以根据需求动态实例化和负载平衡。互联网计算机服务可以弹性扩展到互联网规模,这在捆绑存储和计算的平台上是不可能的。

另一个关键区别是查询是只读的,允许并行执行。Canister 可以处理每秒潜在数千个查询 —— 这会使智能合约架构过载。这开启了依赖于高查询吞吐量和低延迟的用例,例如提供交互式 Web 体验。


查询调用和更新调用

区块链系统中,每个副本都要达成共识才能更新状态,这保证了数据的一致性,但不可避免带来了延迟。如果只是查询不改状态,就可以跳过共识流程了。

每个查询调用可以像读取数据库一样,由单个副本自主响应,不需要经过共识流程,从而大大减少延迟。用户查看账户,获取游戏排名等操作都可以利用查询调用完成。查询调用就像你去餐馆点菜,服务员立刻告诉你有什么菜,不需要请全餐馆的人来开会决定。(笑死)

Canister 对外提供 2 种调用方法:update callquery call

更新调用 update call :增、删、改。会对内存数据进行修改。因为要改数据,所以得在子网里达成共识。因为要达成公识,所以处理消息是单线程的,不支持并发。

查询调用 query call :查。每次调用 query call 时,都对节点内当前数据进行一次快速查询。因为不修改数据,所以不用达成共识,可以多线程,支持并发处理。一个 Canister 能同时处理很多个 query 方法。

更新调用 update call查询调用 query call
是否需要共识需要不要
安全性
响应时间2 ~ 3 秒小于 100 毫秒
状态改变持久化不改变状态
执行方式顺序并行

认证变量

但直接用查询调用响应也存在安全性问题,因为查询时是直接找了子网里的一个副本。如果这个副本正好是被黑客控制的,那就不好办了,可能查到的结果都是错的。查询结果也不写入共识,某个副本返回错误数据也无法检验。

为此,Dfinity 设计了认证变量(Certified Variable)机制。写入时会自动获得子网签名的证书。任何人都可以用子网的公钥验证这些关键数据的真实性。如果查询调用返回的是认证变量的值,那么其可信度就与更新调用等同。


因为 Canister 的认证变量是被哈希之后,记录在每轮认证状态里。而每轮认证状态是经过共识的。

认证变量作为每轮认证状态中的一部分,每个子网的系统状态都分配了一小段字节,用来记录认证变量,认证变量的值可通过更新调用写入,代表了确定的状态,也可被每轮认证状态机制来进行验证。

另外,Canister 也可使用其认证变量来存储默克尔树的的根节点。通过这种方式,只要查询调用的响应是该 Canister 中以认证变量为根的默克尔树的叶节点,即可被验证。

这就好比菜单上印着菜品原价,服务员告诉你打折后的价格。而菜单上有原价印证,你就能验证服务员的回复了。这样既享受了查询调用的低延迟,也保证了结果的安全可信。


Canister的内存管理

Canister 利用两种不同类型的内存。第一种是堆内存,它用作临时的数据存储空间。第二种是稳定内存,这是一个更大的内存池,用于永久的数据存储。

一种是堆内存,这是 WebAssembly 暴露出来的内存堆空间,类似于计算机程序运行时使用的堆。用高级语言定义的所有变量和数据结构都存储在 Wasm 堆中,这是一个 4 GiB 的 32 位地址空间。但是堆内存其实只适合作为暂存空间使用,因为每次智能合约升级后,堆内存都有可能被清除。数据结构的布局也可能发生改变。因此不建议将重要数据长期存放在堆内存中。

另一种是稳定内存,这是 Canister 提供的额外的 64 位可寻址内存空间,目前其大小为 400 GiB 。开发者需要通过相关 API 显式地使用稳定内存。稳定内存可以看作是持久化的存储空间,开发者可以将需要长期存储的数据放入这里。一种常见的用法模式是,开发者会在升级前后,将堆内存中的状态序列化成字节流,保存到稳定内存中,在恢复时再反序列化加载回来。

为了实现内存的正交持久化,使程序在升级后可以恢复运行,Canister 使用了一些特殊的机制:

  1. 页面保护机制 - 将内存分成 4 KiB 大小的页面,通过操作系统的页面保护,实现内存访问时的缺页中断,从而将持久化数据映射到内存,并跟踪内存写操作。
  2. 堆增量(Heap Delta) - 使用一个持久化树状数据结构来记录内存中被修改的页面。
  3. 检查点文件 - 每 N 个区块周期(一个时期 epoch ),会生成检查点文件,将当前内存状态保存下来。

同时,IC 还做了一些优化来提高性能:

  1. 内存映射检查点文件,减少内存使用和读访问开销。
  2. 只对更新调用跟踪页面修改,查询调用不跟踪,加速查询。
  3. 页面预取,每次缺页中断时,预取多个页面,减少中断次数,加速内存密集型操作。

通过这种设计,IC 对开发者提供了一个复杂的内存管理系统,它结合正交持久性、堆内存和稳定内存以实现无缝执行和数据存储。开发者可以 Not-care 地编写程序,系统会自动持久化内存并在故障时恢复。同时多种优化机制也确保了高性能。这种可靠的内存抽象为开发者构建安全可靠的区块链应用提供了基础。

在引擎盖下,IC 系统采用页面保护、版本控制和性能优化来确保高效的内存处理。

也可以看看这里了解更多。


反向 Gas 机制

我们知道以太坊的高 Gas 费一直被用户诟病。但在 IC 中,运行智能合约所需的 Gas 费用,是由开发者预先支付的,与用户无关。

用户访问互联网本应该就是免费的,Web2 都是这样的,这样才能极大地降低用户的门槛,使应用更加易用。


IC 使用反向 Gas 模型,运行 Canister 的费用(Gas fee)默认由部署应用的开发团队支付。当然,如果是每个用户需要创建一个自己的 Canister ,也可以添加让用户自己充值 Cycles 的功能。

IC 消耗的 Gas 叫 Cycles ,这是通过燃烧 ICP 兑换而来的,ICP 可以单向转化为 Cycles 。Cycles 在算法调节下处于稳定,与 1 SDR 锚定( SDR 可以看作综合多国法币计算后的稳定单位)。Cycles 不仅仅作为 Gas ,它在 IC 未来的生态中扮演稳定币、交易计价符号等重要角色。


Cycles 价格稳定: $$ 1\times\ 10^{12}\ cycles\ =\ 1\ SDR $$ (1 T Cycles 即 1 Trillion Cycles 。1 TC 是 1 T Cycles ,C 是 Cycles 的缩写。)

在系统子网有一个 Canister 通过 http 外调获取链下交易所的价格数据,然后计算出 1 SDR 能兑换多少 Cycles 。

每个容器有自己的 Cycles 余额,Canister 之间可以互相发送 Cycles 。

在第一次部署 Canister 时需要大量 Cycles (需要至少 0.1 T Cycles ,默认是 3 T Cycles),之后更新代码消耗的 Cycles 非常少。


当 Cycles 不足维持 Canister 运行 30 天时,Canister 进入冻结状态。30 天后如果没有充值 Canister 就会被删除。冻结后 Canister 无法正常工作。拒绝执行任务。


提供给 Canister 的随机数

随机数对许多区块链应用来说是非常关键的,像博弈和抽奖这类应用需要生成不可预测的随机数。但是对一个确定性执行的分布式系统来说,如何产生真正安全的随机数一直是一个巨大的挑战。

IC 通过一个独特的技术机制解决了这个难题。这个机制的核心是一个叫做 “ 随机磁带 ” 的组件。每个子网在共识协议的每一轮中都会生成一个随机磁带,它是一个特殊的数字签名,可以作为确定性伪随机数生成器的种子。

随机磁带具有以下两个关键特征:

  1. 在生成之前是不可预测的。这保证了随机性。

  2. 生成过程不会引入额外的时间延迟。这样既保证了效率又保证了安全性。

当一个智能合约需要随机数时,它可以向系统发起调用请求。如果下一个区块的随机磁带已经生成,系统会直接使用这个随机磁带作为种子来响应请求。由于随机磁带是在共识过程中同步生成的,所以调用响应很快,性能不会受到影响。

根据第一个特征,我们可以确保在请求发起时,这个随机数是不可预测的。根据第二个特征,获取随机数一般不会造成额外的时间延迟。

这种机制从根本上解决了确定性执行和安全随机数生成这两个矛盾的要求。它巧妙地兼顾了分布式状态的一致性和应用的随机性需求。

具体来看,随机磁带机制在以下几个方面发挥着关键作用:

  1. 保证了随机性:每个随机磁带在生成前是不可预测的,为智能合约提供了真正安全的随机数源。

  2. 高效响应:随机数的生成没有引入额外的时间延迟,满足了对效率的要求。

  3. 简单访问:智能合约只需简单调用即可获得随机数,使用很方便。

  4. 自洽一致:确定性算法使用随机磁带种子产生的随机数在每个节点都是一致的,不会破坏状态一致性。

  5. 加密安全:随机磁带通过 BLS 阈值签名生成,可以抵御预测和操纵。


Motoko

另外,IC 还需要一种简单易用安全的语言。原因很简单,虽然 IC 使用 Wasm 做为智能合约、非常开放,但是对 Wasm 支持相对较好的编程语言都是 C++ 、Rust 这种比较难入门的语言。

IC 专门提供了一门编程语言 Motoko ,它支持 IC 的编程模型,利用了区块链的独特功能。Motoko 有强大的类型系统、Actor 模型、持久性支持和异步消息传递等特性,同时还具备自动内存管理、泛型、模式匹配等现代语言功能。这样,开发者就可以安全高效地编写 Canister 智能合约了。

详细了解 Motoko