币妈妈

下载
币妈妈>资讯>正文

解析 DFINITY 网络架构、原子性与编程范式

DfinityFun2021-06-11 16:45:47

撰文:blockpunk

5 月 27 号,ICP League 联合社区开发者举办了第一期的开发者电话会,探讨了 DFINITY 的整体架构,并聊到了 DFINITY 最重要的原子性问题。

本期亮点:

ETH 是状态更新机器,DFINITY 是消息与请求的处理机器;

DFINITY 架构上是一种多子网结构,并且没有总线 / 主链 / 中继链 / 信标链的结构;

DFINITY 底层为 actor 编程模型,容器(即智能合约)间互相调用是异步操作,需要互相等待,子网间的调用也是异步的;

DFINITY 损失了一部分原子性,这对 DeFi 的开发者来说是一个挑战,借鉴传统互联网对事务的处理方式会有所帮助。

DFINITY 网络如何组成?

在我之前的文章《「代码即服务」:DFINITY 互联网计算机的架构解析》里我曾介绍过 DFINITY 的整体架构,在主网上线阅读了核心代码后,我们得以更详细的窥探其内部结构。

解析 DFINITY 网络架构、原子性与编程范式

DFINITY 基于区块链计算协议 ICP 运行,采用了分层结构,从底层到顶层依次为:数据中心、节点、子网、软件容器。

解析 DFINITY 网络架构、原子性与编程范式

DFINITY 的最底层是托管专用硬件的独立数据中心,这些数据中心运行在全球各地,目前全球已经有 53 个数据中心,年底将超过 500 个。数据中心对硬件与网络状态的要求较高,硬件也具备特殊要求,需要大量 RAM 而无需硬盘。数据中心想要进入 DFINITY 网络,需要获得去中心化治理系统 NNS 的投票许可,而这种投票往往发生在需要扩容时。

解析 DFINITY 网络架构、原子性与编程范式

数据中心的机器运行 DFINITY 节点程序,这个程序会根据性能虚拟化出一系列计算资源标准化的节点,这些节点会组成 DFINITY 的区块链,处理计算、达成共识、出块并同区块。

这些节点随机地(跨多个数据中心)被 NNS 结合到一起,形成一个个子网。子网数量也不是固定的,而是随着网络的资源使用情况动态地增加、合并。这个子网类似于 ETH2.0 中的分片,或者是平行链,一个节点组只能组成一个子网,每一个子网内部都运行着自己的共识。子网的出块速度为一秒一个区块,且不同的子网同步并行出块。

而在不同的子网上承载着「容器」,容器中运行着业务逻辑。可以把容器看作智能合约的升级版本,容器也具备内存,目前上限为 4G,可以存储业务相关数据,也可以存放前端资源。

与从传统区块链的架构差别

ETH 是一台同步的状态更新机器,由交易触发智能合约,对状态进行共识;而 DFINITY 是一台异步的消息处理机,容器同时进行消息的接收、计算、返回,对消息的处理顺序共识。容器间不会共享状态,只会通过通讯来处理事务。

ETH 2.0、波卡、Layer2 其实都属于分层架构,这种架构依然存在着瓶颈,无法无限扩容,并不是终极方案。即使我们可以把不同的应用分配在不同的 layer2/ 平行链 / 分片上扩展性能,但 layer2 间 / 片间 / 平行链间的通讯依然要通过总线,而总线本身的性能上限决定了系统的性能上线,是确定的。

但与分片 / 多链架构不同的是,DFINITY 没有总线 / 主链 / 中继链 / 信标链的设计,子网之间是完全对等的,任意两个子网都能通过自己的链签名事务,通过事务直接交互,而不需要经过某个上层调度链,因此子网的数量不是固定,可以按需无上限增加,从而最终实现无限扩容。

解析 DFINITY 网络架构、原子性与编程范式

原子性问题

一切即 actor,DFINITY 使用的编程模型与传统区块链有一定差异,反而与传统互联网的 akka 类似。

DFINITY 中的智能合约(即容器)之间的交互也与以太坊不同,DFINITY 的智能合约内部是向存在一个唯一线程,但是容器之间的交互是通过发送异步消息进行,因此容器间可以进行异步操作。如果一个容器向另一个容器发送请求后,需要等待另一个容器的处理结果,才能进行下一步操作。

DFINITY 中存在很多个对等的子网,智能合约会随机的部署上子网。对于部署合约的开发者与使用合约的用户来说,他们并不知道这个合约运行在哪一个子网中,因为片间通讯是无感的。在同一个子网的两个容器的交互,与在不同子网上两个容器的交互,在延迟、安全性、复杂度上都没有差别,因此无法感知到子网之间的差别。这也是异步消息系统的好处。

解析 DFINITY 网络架构、原子性与编程范式

这样 DFINITY 获得了近乎无限的扩容能力,大大提升了通用计算能力。但这也导致了合约间的交互失去了原子性,而在以太坊上利用原子性特点实现了很多独特的服务,比如 DeFi 的闪电贷清算机制。

在失去了原子性后,开发者面临着新的挑战,特别是在实现 DeFi 应用上,如果依然按照过去以太坊上的编程范式,这导致交易失败后状态无法恢复的问题。

比如,如果在 DFINITY 上实现了设计多个容器交互的复杂应用,比如一个借贷应用,需要与稳定币、借出代币、流动性凭证三个合约交互,三个合约都成功调用才能完成借贷。但如果强行在 DFINITY 实现以太坊范式的调用,如果其中两个合约成功,而最后一个调用没有响应,那么借贷交易会失败,并且前两个合约的状态却已经完成,就会卡住,而不会退回去。表现在用户这边,就是钱已经质押上去,代币已经扣走,却没法完成借贷,也没有收到退款。

探索新的编程范式

显然想要在 DFINITY 保证安全性地实现复杂应用,我们需要探索新的范式。

最终一致性与确定性在传统互联网的分布式架构下有一些解决方案,这是值得我们借鉴的。DFINITY 上的智能合约需要关注的是数据的最终一致性,从写入和读取入手。

首先当我们需要很强安全性保证时,可能把整个所有的逻辑放在一个容器里面。主要在一个容器中,所有的交易都是原子性的,这里确保了事务与数据两个层面的一致性。但这种方式面临着扩容的噩梦,显然是偷懒的做法。

传统互联网其实关注的是数据库中数据的一致性,而在 DFINITY 中其实分为两个部分:一个是业务层面的,这部分是可以通过更新合约变化的,我们其实不太需要保证这里的原子性;而还有一部分是数据层面的,也就是进入正交持久性的数据,落盘的数据,这才是我们需要保证一致性与原子性的地方。

在 DFINITY 中有使用了名为 stable 的变量类型来定义落盘的数据,这其实类似于传统的数据库,目前也有多个团队在做 DFINITY 的数据库引擎,有了这个底层落盘数据的一致性与原子性,上层的业务的安全性就依靠数据来保证一致性。

如果是借鉴原来分布式事务的概念,我们有四种方式实现这个能力:

tcc 两端事务提交,这是目前银行转账使用的机制,在交易发生时我们先直接更新数据库的 stable 的最终一致数据,等大家都确认清楚后,再去提交。

saga 的事务处理机制,首先建立一个事务协调程序,当某个容器需要发起一次跨合约的调用时可以向事务协调程序申请一个 ID,并通过这个 ID 向事务的终结程序汇报,最后大家都提交成功后,再进行整体的提交。

使用事务观察者模式,对 stable 的状态进行包装,每当发生状态的更新操作时,观察者都去记录更新前后的两个值,如果发现某一个事务失败,观察者会就使用之前的值回滚操作。

关于容器数据扩容

DFINITY 的优势在于大规模高性能的去中心化数据库存取,DFINITY 的程序以容器为单位运行,容器中会存储业务相关的数据库,且容器之间不会共享状态数据。而目前 DFINITY 容器存储上限是 4G,如果一个业务容器的 4G 存满之后,容器就需要面临扩容的问题,该如何解决?

目前 DFINITY 能允许容器在存储与带宽等资源即将耗尽时,自动 Fork 出一个新容器进行扩容。新的容器中只保存了最近的状态数据,会丢弃历史。两个容器间依然通过异步调用来实现交互。

同时,在设计容器时,需要把各种级别的数据分开存放。举个例子,如果直接在 DFINITY 上建立一个钱包容器,交易记录的数据量会比用户地址的数据大很多。如果这时候把这两类数据放入一个容器,就会影响后续的扩容能力。

因此我们需要把对最终一致性要求相同的数据放在一起。比如在将钱包的余额与地址放在一起,而交易记录就可以通过分片存放在不同的容器中。从而实现系统的自动弹性扩容。

声明:该文观点仅代表作者本人,币妈妈信息发布平台,币妈妈仅提供信息存储空间服务。

相关阅读