智能合约和虚拟机设计
区块链的智能合约需要跑在一个资源隔离的环境中,不管是像Docker通过命名空间的隔离,还是通过虚拟机的方案,都是需要一个沙盒执行环境,保证合约和合约之间,合约和宿主系统之间进行了有效的资源隔离。智能合约是用户编写的,需要防备智能合约恶意或故障合约的不良影响。
要设计一款优秀的区块链智能合约的虚拟机,需要从以下几个角度衡量:
- 结果确定性和资源控制
- 扩展性和耦合度
- 通用性和生态兼容性
智能合约
智能合约早已有之,随着互联网的兴起,就有人想能不能在计算机上实现合约的功能,所谓智能合约,无非就是各方协商好,然后各方签名,最后按照条款执行这样三部曲,但在互联网刚刚兴起的阶段,这个智能合约只能在单个机器节点上进行运行,如果多个节点运行,根本没法保证各方签名和按照条款执行不出现作弊,所以多方参与的智能合约无法保证其公正。
比特币和区块链的出现,带来了技术上的革新,区块链能将数据分布式多方存储,计算也是各个节点进行,规则完全透明,且数据不可篡改,这样智能合约就能真正落地了。
因为区块链是多个节点分布式运行的系统,运行在上面的智能合约就必须具备两个特性:确定性和可终止性。
如果一个智能合约是非确定性的,那么不同节点运行的结果就不一致,从而导致智能合约的共识就无法达成,网络陷入停滞。
如果一个智能合约是永不停止的,那么节点将耗费有限的资源去执行无限的合约代码,网络同样陷入停滞。
智能合约的非确定性来源
- 调用非确定性的系统函数
比如随机数生成,获取系统时间等。节点调用非确定性函数,各个节点输出结果不统一而导致网络陷入停滞。
-
使用非确定性的数据
-
动态调用
动态调用:一个程序调用另外一个程序时,需要在运行时才能确定被调用的目标。这种方式就决定了其行为是非确定性的。
避免智能合约的非确定性方案
- 比特币方案
比特币是内置一套脚本引擎,可以将这些脚本组成简单的智能合约。其指令集是非常简单且非图灵完备,不提供任何系统函数,不提供访问数据的能力,没动态调用功能,静态调用都没有。比特币的智能合约方案是完全确定性的,但功能和性能就是渣渣。
- 以太坊方案
以太坊的智能合约是图灵完备的,有自己的高端大气虚拟机EVM,甚至还不用人见人爱的JavaScript,还自己开发高级语言Solidity,又为广大程序猿们创造搬砖的好工具。其智能合约也不提供非确定性系统函数,数据访问也仅限于链内数据,外部数据需要通过交易发送到合约。这样就可以将先知机结合进来了,以太坊智能合约的先行者还是很厉害的。其智能合约可以动态调用,合约的调用路径是非确定性的,但调用的数据是确定性的,使得所有节点在动态调用后获得相同的目标地址,保证系统的一致性。
- Fabric方案
智能合约采用Docker作为执行环境。Docker的特性,智能合约是图灵完备的,可以访问所有系统函数,底层机制上无法避免非确定性,最后只能寄希望于开发者不要写出渣渣代码,一运行就让各个节点陷入共识奔溃。
终止问题和资源控制
终止问题(halting problem)简单理解,能否判断一个程序在有限的时间内能够结束。也就是不要陷入死循环。
区块链上的智能合约是要可终止的,不能陷入死循环而消耗有限的资源。区块链在设计智能合约时,都不得不考虑每个智能合约都是会陷入死循环的,然后根据一系列条件来判断终止,然后采用异常终止方式结束。通常的策略如下:
- 非图灵完备
智能合约系统只提供有限的指令集,那些可能导致死循环的跳转,循环指令都不用,大不了复杂一点用其他方式等效实现类似功能的指令。
- 计步器
智能合约每运行一步就计数一次,每执行一条指令将计数器加一,当超过限制后,强推断为进入了死循环,强制终止退出。
- 计价器
类似的策略,只不过不是统计指令执行次数,而是按照每一个指令进行收费,统计gas费用,gas消耗完了,还能执行完该死的智能合约,那就直接终止退出,消耗的gas也不退。
- 计时器
使用时间标准来衡量一个合约是否陷入了死循环。Fabric采用这种方案。
智能合约的执行环境
主流有两种:虚拟机和容器(Docker)。都是提供一个沙盒来执行智能合约,对其使用的资源进行隔离和限制。
- 1.虚拟机
有以太坊这样自己开发的EVM,也有很多直接使用V8引擎(JavaScript引擎)。
对于不同的虚拟机,有两个性能指标比较重要:
- 指令的执行速度
- 执行环境本身的启动速度
很明显,智能合约大部分都是非IO的逻辑操作,指令执行速度不是问题,关键是每一个智能合约都是要新开一个虚拟机,因此其本身的启动速度对智能合约系统的性能影响更大。
对于选用这些短小而精悍的引擎,是出于性能和功能综合考虑的,智能合约对于功能不是那么高,反而是性能要求高,加载速度要快,可以通过JIT(即时编译器)技术对热点智能合约进行静态编译和缓存可以更加显著提升虚拟机的执行效率。
- 2.容器(Docker)
Docker相对于V8就是高大威猛死胖子,执行代码飞快,但启动起来就要消耗大量时间和资源。性能是其缺点。
扩展性思考
如果对智能合约进行抽象:区块链是一个分布式账本,记录各种状态数据,同时记录状态转移函数,智能合约用来记录这些函数的载体。区块链能否并行对业务进行处理,取决于多个智能合约能否并发执行,也就是说智能合约的执行是否是顺序无关的。
两个合约是否能并行处理,取决于两个合约是否是顺序无关的,取决于是否对同一条状态记录进行修改。
- 并发方案
如果走极端,想要一个具备无限扩展能力的智能合约系统,那就要对功能进行很大的阉割:
1.一个智能合约只能修改属于该合约自己的状态记录
2.同一个事物批次(区块)中,一个智能合约只能被运行一次
这样得到一个极端的结果,所有的智能合约都是顺序无关可以并行处理。
但在功能上阉割很彻底:合约之间不能相互调用,一个区块只能处理一笔交易。
- 分片(Sharding)方案
将所有智能合约的hash对256取模,然后分配到256个片区中,各个片区的智能合约能彼此调用,但跨片区就复杂了。
按照片区进行并行处理,执行效率提升256倍。跨片区就要向全局账本写入调用请求,另一片区监听收到后执行操作,写回全局账本并返回调用结果。这样导致跨片区调用无法在同一个业务批次(区块)中完成,效率降低显著。
其实这样也会有问题,分256个片区,大家挤到几个繁忙片区中,避免跨片区低效调用,这样就会出现市中心拥堵现象。
- 剥离方案
现在智能合约都是写入链,加载代码执行。其实可以轻量化区块链主链,经智能合约hash后,只存hash到链上,其他代码存IPFS,大大轻量化主链,也提供隐私保护。
耦合度思考
先学习一下两个极端例子:
- 1.以太坊
以太坊在智能合约设计中是高耦合的代表。区块链和EVM之间相互依赖的关系很多:
- 费用的计算混杂在虚拟机的实现逻辑中
- 虚拟机指令集中包含大量用于访问账本数据的指令
- 虚拟机直接提供以区块链账本作为载体的持久化存储指令
高耦合度的问题是很多的,比如要对区块链功能进行升级,就要对EVM进行相应的修改,基本就是要增加新的指令上。高耦合度带来的一个问题就是兼容性差,对于生态建设就很不利。
- 2.Fabric
采用Docker的超级账本,就是低耦合的代表。在Docker中运行的智能合约程序只通过gRPC协议与节点进行通信,协议中包含了访问账本和持久化存储的功能。
兼容性和生态思考
兼容性需要从以下几个层面去思考:
- 智能合约编写代码的通用性
- 跨区块链的智能合约兼容性
- 运行智能合约的虚拟机能兼容其他区块链的虚拟机
智能合约的生态,可以从以下几个方面去思考:
- 跨合约之间访问数据
- 跨链之间访问智能合约和数据
- 访问链外数据
- 先知机的引入