1. 前言
本文是小蚁的两位创始人过去两年中在设计小蚁智能合约时所做的深度思考和技术探索的结果。《重构智能合约》系列文章将分为上、中、下三篇,分别从确定性和资源控制、扩展性和耦合度、通用性和生态兼容三个方面来剖析现有智能合约系统的优缺点,并提出新的智能合约体系的设计思路。
2. 智能合约与区块链
自从比特币、以太坊的相继诞生,以及区块链技术的逐步升温,智能合约一词便开始频繁的出现在金融和科技媒体之中。智能合约是1994年由密码学家尼克萨博(Nick Szabo)***提出的理念,几乎与互联网同龄。根据Nick Szabo的定义:智能合约是指能够自动执行合约条款的计算机程序。
传统意义上的合约的生命周期一般包含:各方协商、签名记录、条款执行三个阶段,智能合约也类似。但是在区块链技术出现之前,单台计算机难以提供安全可靠的签名记录和条款执行服务,所以智能合约也一直仅仅停留在理论阶段。比特币及区块链技术出现后,其多方存储、多方计算、规则透明、不可篡改等特性,恰好为智能合约提供了安全可靠的记录载体和执行环境。
与普通的计算机程序不同,区块链上的智能合约必须同时具备两种性质:确定性和可终止性。
区块链是一个通过多方存储、多方计算的方式来实现数据不可篡改、计算结果可信的分布式系统。智能合约会在区块链网络的多个节点中运行。如果一个智能合约是非确定性的,那么不同节点运行的结果就可能不一致,从而导致共识无法达成,网络陷入停滞;如果智能合约是永不停止的,那么节点将耗费无穷多的时间和资源去运行合约,同样导致网络进入停滞状态。这就涉及到了两个有趣的话题,下面我们分别来探讨一下确定性和可终止性问题(停机问题)。
3. 智能合约的确定性
如果一个程序在不同的计算机、或者在同一台计算机上的不同时刻多次运行,对于相同的输入能够保证产生相同的输出,则称该程序的行为是确定性的,反之则称该程序的行为是非确定性的。使程序产生非确定性的因素有很多,总结起来有以下几种:
1) 调用了非确定性的系统函数
一般在编写程序的时候,开发者或多或少会调用一些系统提供的函数和功能以减少开发的工作量。这些系统函数中可能会存在一些非确定性的函数,比如生成随机数、获取系统时间等。一旦程序调用了另一个非确定性的程序并使用了它们输出的内容,那么该程序自身的行为也可能会变为非确定性的。
2) 使用了非确定性的数据来源
如果一个程序在运行时获取数据,而数据源提供的是非确定性的数据,那么该程序也可能会变成非确定性的程序。例如,通过搜索引擎来获取某个关键词的前10条搜索结果——搜索引擎针对不同的IP地址来源可能会返回不同的排序结果。
3) 动态调用
动态调用是指,一个程序在调用另一个程序时,如果必须在运行时才能确定被调用的目标,则称该调用为动态调用;反之,如果在运行前即可确定被调用的目标,且在运行时无法变更该目标,则称该调用为静态调用。由于动态调用的目标在运行时决定,因此其行为是非确定的。
对于区块链上的智能合约,我们一般要求它的行为必须是确定性的,因为非确定性的合约可能会破坏系统的一致性。区块链的作者必须考虑到这个问题,并在设计智能合约系统的时候,就想办法把非确定性因素排除在外。那么我们来看看现有的各个区块链是如何解决这个问题的。
1) 比特币
比特币内置了一套脚本引擎,用于执行鉴权脚本,它是区块链智能合约的雏形。开发者可以基于这套脚本系统来开发一些简单的应用,但由于其指令集非常简单且非图灵完备,能够实现功能相当有限。这套系统既没有提供任何系统函数,也没有提供任何访问数据的能力,更没有动态调用的功能,甚至连静态调用也没有提供,因此比特币的智能合约一定是确定性的。
2) 以太坊
以太坊的主要设计思想,就是提供一个图灵完备的智能合约平台,让用户可以编写任意逻辑的程序。它专门开发了一个用于执行合约代码的虚拟机EVM,并设计了一种类似于Java的高级语言Solidity,以方便用户进行开发。以太坊智能合约没有提供任何非确定性的系统函数,可访问的数据也仅限于链内数据,外部数据需要通过交易来发送到合约。但是,以太坊的CALL和CALLCODE指令的目标地址通过栈来传递,使得合约可以在运行时动态调用其它的合约代码,使合约的调用路径变为非确定性。好在合约可以访问到的数据都是确定性的,使得所有节点在动态调用目标代码时一定会获得相同的目标地址,保证了系统的一致性。但是调用路径的非确定性,会导致一个可扩展性上的重要性能损失,具体会在《重构智能合约(中):平行宇宙与***扩展》中详述。
3) Fabric
Fabric是超级账本中的一个子项目,它的智能合约采用了重量级的Docker作为执行环境。这可能跟大家的印象有点矛盾——“Docker不是一直被认为是一种轻量级的容器技术吗?”。实际上,Docker的“轻”是相对于模拟物理机架构的重量级虚拟化技术而言。在区块链应用场景下,Docker是一个比较“重”的执行环境,这也是Fabric的性能瓶颈所在,目前只能达到每秒几百TPS。由于Docker的特性,智能合约几乎可以使用物理计算机上的所有功能,因此具有极高的非确定性。所以Fabric要求智能合约的开发者在编写代码的时候尽量避免使用到具有非确定性的功能,并计划提供一套专门开发的确定性系统函数库供开发者使用。 然而,由于无法从底层机制上避免非确定性的产生,寄托于开发者遵守良好的开发规范难免有些一厢情愿。非确定性就像幽灵一般,平时似乎并不存在,在一些边缘案例(corner case)上就可能会突然冒出来造成难以判断的故障。
4. 停机问题与资源控制
停机问题(halting problem)是逻辑数学中可计算性理论的一个问题。通俗地说,停机问题就是判断任意程序是否能在有限的时间之内结束运行的问题。该问题等价于如下的判定问题:是否存在一个程序P,对于任意输入的程序w,能够判断w会在有限时间内结束或者死循环。
艾伦·图灵在1936年用对角论证法证明了我们无法编写出程序P——即不存在解决停机问题的通用算法。这个证明的关键在于对计算机和程序的数学定义,这被称为图灵机。停机问题在图灵机上是不可判定问题。这是最早提出的决定性问题之一。
区块链上的智能合约必须是可终止的,否则将会消耗***的时间和资源。从上面的论证已经可以看出,停机问题是不可解的,我们无法在不运行一个程序的情况下,提前判定该程序是否会停机。因此,区块链的设计者不得不假设所有的智能合约都可能会进入死循环,并对于可能已经进入死循环的合约采用异常终止的方式来结束它。通常会有以下几种策略:
1) 非图灵完备
如果一个区块链的智能合约系统,只提供了有限的指令集,而不提供诸如跳转、循环等指令,以及可以等效实现类似功能的指令,那么基于这个系统的智能合约就不可能进入死循环,因此它们总是可停机的,比特币就是其中的一个例子。值得一提的是,比特币改进计划BIP12的提案中,有过在比特币指令集中增加一条OP_EVAL指令的计划,这条指令可以加载计算栈中的脚本并动态执行,从而解决多重签名算法的问题。但由于该指令会间接地使得比特币的脚本系统变为图灵完备,因此最终被废弃了。
2) 计价器
既然无法在智能合约运行之前就判定其是否会停机,那么也可以在合约运行中进行计步——每执行一条指令就将计步器加一。当计步器的数值超过一定的限制之后,就认为合约已经进入死循环,从而强行终止它的运行。这种方法需要区块链对智能合约的执行有较大的控制能力,能够精确的计算出合约执行的步数且在各个节点间保持一致。
计价器的方案和计步器基本上是一致的,但是它采用经济手段来解决停机问题:对合约执行的每一个指令进行收费,当合约的手续费全部用完之后,如果合约还没有终止,那么就强行终止退出。以太坊采用的就是这个模式,它通过消耗一种被称为燃料的代币来对智能合约进行计价。一旦燃料耗尽,合约就会执行失败,并且不会退回消耗掉的费用。
3) 计时器
计时器和计价器的方案比较类似,但它使用时间作为标准来衡量一个合约是否已经进入死循环:如果合约在超时时间到达之前还没有正常终止,那么就认为它已进入死循环并强行终止它。Fabric采用了计时器的方案,原因是Fabric使用Docker作为其智能合约的执行环境,而Docker中运行的程序是无法计步或计价的。虽然计时器可以部分地解决停机问题,但是在分布式系统中,每个节点的执行时长未必能够保证一致,且每个节点的性能和负载都不一样,导致对合约运行是否超时这一判断会出现不一致的情况,从而使得共识算法的失败率大大的增加,这是计时器方案的重要缺点。
上述的非图灵完备、计价器、计时器方案,实际上都是一种资源控制手段,即通过对代码量设定上限,对运算资源计价,或对执行时间设定上限等方法来将智能合约占用的资源控制在合理的范围。
5. 资源隔离
虚拟化的执行环境除了实现资源控制外这一目的外,更重要的是通过资源隔离来保障系统的安全。在开放的区块链上,任何参与者都可以编写并上传智能合约,图灵完备的智能合约就意味着可以编写并执行任意的逻辑——包括病毒或故障合约如果智能合约直接在区块链节点的宿主系统上运行,病毒就能够自我复制,故障合约就可能破坏宿主系统的自身数据。因此智能合约必须放在一个隔离的沙盒环境中运行——虚拟机或者容器。
通过沙盒执行环境,合约和合约之间,合约和宿主系统之间进行了有效的资源隔离,也就控制住了恶意或故障合约的影响范围。但在资源隔离度上,Docker所依赖的基于命名空间的隔离要弱于虚拟机的隔离。正因为此,在公有云上两个不同用户的Docker镜像一般不会被放在一个宿主操作系统中运行。因此,基于Docker的方案在资源隔离度的安全性上要弱于虚拟机方案。
6. 小结
通过对确定性、可终止性、资源控制、资源隔离的分析,不难发现区块链上的智能合约需要具备强确定性,可终止性、精确的资源控制和资源隔离。在这一维度上,为区块链量身定制虚拟机的方案相比借用现有容器的方案有着较大的优势。而容器方案的优点在于编写语言的灵活,不要求合约开发者学习新的编程语言,从而更容易融入现有的开发者生态。那么是否有办法兼顾二者呢?我们会在《重构智能合约(下):兼容性与生态》中给出方案。
在此之前,我们会先通过《重构智能合约(中):平行宇宙与***扩展》来分析一下现有公有链、联盟链的性能为何如此不堪。
关于作者
本文两位作者均系小蚁区块链(www.antshares.org)的创始人。小蚁区块链是一个探索可编程智能经济的开源公有链。小蚁起源于2014年,是***个区块链项目。2017年,小蚁的前期技术积累将进入兑现期,会在智能合约、跨链互操作、共识机制、密码学等多个方向实现技术突破。