Author

Topic: 智能合约的进化:化解以太坊弊端实现智能合约无限扩展 (Read 238 times)

newbie
Activity: 41
Merit: 0
 1、前言

  本文是小蚁的两位创始人过去两年中在设计小蚁智能合约时所做的深度思考和技术探索的结果。《重构智能合约》系列文章将分为上、中、下三篇,分别从确定性和资源控制、扩展性和耦合度、通用性和生态兼容三个方面来剖析现有智能合约系统的优缺点,并提出新的智能合约体系的设计思路。


  2、执行环境的性能

  智能合约的执行环境会对合约的性能起到非常重要的作用。目前主流的区块链架构对智能合约执行环境的设计主要分为两种:虚拟机和容器(Docker)。无论是虚拟机还是容器,它们的作用都是在一个沙盒中执行合约代码,并对合约所使用的资源进行隔离和限制。

  1) 虚拟机

  虚拟机通常是指能够像真实机器一样执行程序的计算机的软件实现。有些虚拟机会模拟出一个完整的物理计算机,比如VMware、Hyper-V等,可以在这些虚拟机上安装操作系统和应用程序;另一些虚拟机则只提供了硬件的抽象层,而与具体的底层硬件无关,例如Java虚拟机。

  区块链智能合约系统的设计中,很少会采用模拟完整物理计算机的模式,因为这种方式会消耗大量的资源并严重影响性能,且很难兼容不同的硬件架构。所以绝大多数的区块链会采用更加轻量级的虚拟机架构,例如以太坊开发了EVM,R3 Corda则直接采用了JVM,还有一些区块链采用了V8引擎——Google的JavaScript引擎(虚拟机)。

  当我们分析执行环境的性能时,有两个指标是非常关键的:(1)指令的执行速度(2)执行环境本身的启动速度。对于智能合约而言,执行环境的启动速度往往要比指令的执行速度更为重要。智能合约中较多是一些甚少涉及IO操作的逻辑判断指令,这些指令的执行速度很容易得到优化。上一篇《重构智能合约(上):非确定性的幽灵》中,我们提到了出于安全性考虑,智能合约必须在相互隔离的沙盒执行环境中运行。每个智能合约每次被调用,都必须启动一个新的虚拟机/容器。因此执行环境本身的启动速度(启动一个虚拟机/容器)对智能合约系统的性能影响更大。

  上述的EVM、JVM、V8引擎这些轻量级的虚拟机架构对智能合约的性能提升有显著的优势。它们的启动速度非常快,占用资源也很小,适合像智能合约这样短小的程序。缺点是,这类虚拟机的执行效率会相对略低,好在智能合约一般都比较短小,会更加注重环境加载的速度而非代码执行的速度。另外,通过JIT(即时编译器)技术对热点智能合约进行静态编译和缓存可以显著提升虚拟机的执行效率。

  3 、并发、分片与无限扩展

  当谈及一个系统的扩展性时,总会涉及到两个词Scale Up(垂直扩展)和Scale Out(水平扩展)。最典型的垂直扩展案例是单核时代的CPU——主要靠提高主频达到性能的提升。垂直扩展很容易就碰上天花板,当CPU制程工艺的提升越来越困难后,通过多核实现水平扩展,对指令进行并行处理,成为了提升CPU性能的重要手段。

  正因为垂直扩展会很快触及造价、技术的极限,一个不可拆分业务的串行系统的扩展性(或曰性能提升能力)就会很弱——它取决于单台设备的最大处理能力。当我们需要对系统进行扩展时,如果有办法将串行系统改造成并行系统,那么理论上我们将可以获得近乎无限的扩展性。我们在考虑对区块链系统进行扩展时,是否有无限扩展的可能?换言之,区块链能否并行地对业务进行处理?

  区块链是一个分布式的大账本,里面记录了各式各样的状态数据,同时也记录了这些状态如何变化的规则,智能合约正是用来记录这些规则的载体。区块链能否并行地对业务进行处理,就取决于多个智能合约能否并发执行——即合约的执行是否是顺序无关的。

  举个例子,某账户中有10元的余额,现有两个合约对该账户进行修改,第一个合约在账户中增加5元,第二个合约在账户中扣除11元。如果先执行前者,则最终账户的余额为4元;如果先执行后者,由于余额不足,第二个合约将会执行失败,而第一笔合约会执行成功,最终账户余额为15元。像这样的两个合约由于执行的顺序不同而导致不同的结果,那么它们是不可以并发执行的,只能串行处理。

  反过来,如果两个合约分别对两个不同的账户进行修改,那么它们无论哪一个先执行,结果都不会不同,所以它们是可以并发执行的。

  从上面的例子可以看出,两个合约是否可以并行处理,取决于这两个合约是否是顺序无关的;而是否顺序无关,则取决于他们是否能够对同一条状态记录进行修改。

  基于上面的分析,我们可以设计出一个具备“无限扩展”能力的智能合约系统。只需要简单地规定:(1)一个智能合约只能修改属于该合约自己的状态记录;(2)同一个事务批次(区块)中,一个合约只能被运行一次。这样一来,所有的智能合约之间都是顺序无关可以平行处理了。

  但是,等等……如果“一个智能合约只能修改属于该合约自己的状态记录”,就意味着合约间无法相互调用,每个合约都是一个孤岛;如果“一个区块中,一个合约只能被运行一次”,就意味着用智能合约发行的某种数字资产在一个区块里只能处理一笔交易。这显然和“智能”二字的设计初衷大相径庭。毕竟合约间的相互调用,同一区块中多次调用同一个合约,都是我们想要的设计目标。

  这样一来情况就变得复杂多了,特别是像以太坊这种支持动态调用(通过CALL指令)的智能合约系统,不可能在运行前就判断出合约的行为和调用路径,也就无法判断合约会修改哪些状态记录。因此,以太坊的扩展性一直是其设计上的一大弊病 ,其目前的架构设计难以支撑以太坊成为“全球计算平台”的远大愿景。为了解决扩展性问题,以太坊提出了分片(Sharding)方案:

  打个比方,分片就类似于户籍制度。计算一个合约的散列值再对256取模,就可以把合约分配到256个片区中去,这相当于给每个合约分配了一个该片区的“户口”。江苏户口的合约只能调用江苏的合约,上海户口的合约就只能调用上海的,不能直接这样一来,江苏、上海等256个片区的合约就可以按片区进行并行处理了,看起来执行效率可以得到256倍的提升。但是在这种设计下,想要跨片区调用,就必须向一个全局账本(区块链)写入调用请求,另一片区的合约收到请求后再执行操作,并再次写入全局账本来返回调用结果。这导致了跨片区调用无法在同一个业务批次(区块)中完成,效率显著降低。在真实的应用场景中,分片的结果很可能是大家都挤到一个“繁华片区”中去 ,因为这样才能最高效的进行相互调用,避免跨区操作。在城市郊区修建再多的干道,也无法解决市中心的拥堵问题。

  另外,智能合约代码的加载方式也会影响到扩展性。目前 以太坊 的区块链智能合约系统都会要求将智能合约代码发布到链上,然后再从链上加载代码执行。有些合约代码可能只被使用一次就废弃了,但在区块链中永久性地存在,占用节点的存储资源,久而久之这些废弃代码会成为区块链的巨大负担,影响扩展性。

  我们的 方案,是将智能合约的散列值记录在链上,用IPFS等以散列值为索引的新型分布式存储网络来存储完整合约代码。在执行合约的时候,再从链外加载代码。由于合约的散列值已经在链上记录,即使从链外加载代码也不用担心合约的内容被篡改,这样可以为节点节省大量的存储空间。同时也能对智能合约的内容进行一定程度的隐私保护。

  4 、耦合度

  耦合是指两个或两个以上的实体相互依赖于对方的一个量度。在区块链与智能合约系统的设计中,对于耦合度的控制有两个非常极端的例子:

  1) 以太坊

  以太坊在智能合约系统的设计中是高耦合的典型,区块链与EVM之间到处充斥着相互依赖的关系,例如:

  费用的计算混杂在虚拟机的实现逻辑中;

  虚拟机指令集中包含大量用于访问账本数据的指令;

  虚拟机直接提供以区块链账本作为载体的持久化存储指令。

  将区块链的业务逻辑与虚拟机混在一起,并不是一个良好的设计。这会造成一系列的问题,一旦区块链的功能需要改进或者升级,势必就要对EVM也进行相应的修改,这种修改多数情况都会体现在增加新的指令上;而EVM几乎没有办法移植到其它区块链系统中,除非另一个链的底层架构与以太坊高度一致,或者专门针对EVM开发一个对接层。这种模式会对以太坊的生态应用造成很大的局限性 。

  2) Fabric

  与以太坊的设计模式相反,Fabric的智能合约系统采用了低耦合的设计,区块链账本与Docker之间几乎没有任何依赖关系,因为Docker本身就被广泛应用于区块链以外的大量场景之中。在Docker中运行的智能合约程序只能通过gRPC协议与节点进行通信,协议中包含了访问账本和持久化存储的功能。当区块链的功能需要改进或者升级时,只需要对gRPC协议进行改动即可。这种超低耦合度的设计模式值得其它区块链的开发者学习参考。

  高内聚、低耦合是设计系统架构时所常常追求的目标。Fabric的设计目标是打造通用的许可型区块链的技术框架,因此一开始就采用了高度模块化的设计思想;而以太坊最初的设计目标是一个具体的公有链实例,而非技术框架。因此以太坊中存在着系统耦合度过高的问题。这将会妨碍以太坊作为一种通用技术被使用在联盟链、私有链上。

  4 、小结

  在本篇中,我们分析了智能合约系统理想的执行环境性能、并发处理能力、耦合度和代码加载方式,发现了以太坊的一些高度抽象,无灰度的设计带来的扩展性问题,提出了可以做到理论上“无限扩展”的高度并行化智能合约系统的设计思路。我们认为一个可以良好进行并发执行的智能合约系统 ,应该具有以下特征:

  轻量级的执行环境:快速的启动时间和较高的执行效率。

  可插拔的执行环境架构:默认的执行环境应该不提供持久化存储,从而让合约默认是一种类似于微服务的无状态函数,从而可以直接并发处理。仅在需要存储状态时,才提供可插拔的持久化存储模块。这样的虚拟机默认只有一个CPU和栈,仅在需要时才提供“硬盘”和其他IO设备。

  明示化的调用关系:即只提供静态调用的功能,从而使得程序的调用关系可以在运行它之前就整理清楚。一旦调用路径明确了,那么合约可能会修改到的状态数据也就明确了,依据这些明示的调用路径就可以进行即时的动态分片提高合约执行的并行能力。

  可链外存储的合约代码:通过链上存储散列值,链外存储合约代码实现存储空间的扩展性。

  低耦合度的设计:合约语言、执行环境、区块链之间的低耦合度,提高智能合约系统的通用性。
Jump to: