重构智能合约系列
《重构智能合约》系列文章是Antshares创始人的心血结晶,代表了他们在设计Antshares智能合约过程中获得的深刻见解。该系列文章分为三部分,分别从确定性及资源控制、扩展性与解耦、通用性与生态系统兼容性三个角度,分析现有智能合约系统的设计,并提出一种全新的智能合约设计哲学。
第一部分:不确定性的幽灵
在《重构智能合约第一部分:不确定性的幽灵》中,我们分析了确定性、资源控制以及智能合约隔离的重要性,并得出结论:虚拟机(VM)作为运行时环境,相较于Docker容器平台具有明显优势。在《重构智能合约第二部分》中,我们将继续从扩展性的角度比较不同智能合约系统的优劣,并提出改进建议。
运行时环境的重要性
运行时环境对智能合约的性能至关重要。区块链架构主要有两种设计:虚拟机和Docker。两者都作为沙盒来执行智能合约代码,并隔离或限制执行合约所需的资源。
虚拟机通常指能够像机器一样执行程序的软件。有些虚拟机(如VMware和Hyper-V)会模拟完整的物理计算机,可以安装操作系统和应用程序;而其他虚拟机(如Java虚拟机JVM)仅模拟应用级功能,不涉及底层硬件。智能合约很少设计为模拟完整的物理计算机,因为这种设计会消耗大量资源,且难以兼容不同的硬件结构,从而严重影响智能合约的性能。因此,大多数区块链网络选择更轻量级的虚拟机结构,例如以太坊使用EVM,R3的Corda使用JVM,部分区块链则选择V8(JavaScript引擎)。
分析运行时环境性能的两个关键指标是:(1)指令执行速度;(2)启动时间。对于智能合约而言,启动时间通常比指令执行速度更重要。由于大多数智能合约指令不涉及IO等逻辑判断,优化执行速度相对容易。此外,正如我们在《重构智能合约第一部分》中讨论的,为了系统安全,智能合约必须在隔离的沙盒中运行,这意味着每次使用智能合约时都需要启动一个新的虚拟机或Docker。因此,运行时环境的启动速度是影响智能合约性能的更关键因素。
上述提到的轻量级虚拟机(包括EVM、JVM和V8 JavaScript引擎)在提升智能合约性能方面具有显著优势。这些虚拟机可以快速启动并运行,资源消耗较少,因此非常适合智能合约等轻量级程序。轻量级虚拟机的缺点是执行效率较低,但幸运的是,智能合约本身是轻量级的,更重视启动时间而非指令执行速度。此外,即时编译(JIT)技术可以用于编码和缓存热点智能合约,从而显著提高虚拟机的效率。
Hyperledger的Fabric项目独特地使用Docker作为其智能合约的运行时环境,这与其他主流区块链设计不同。Docker虽然隔离资源,但程度不如虚拟机。由于Docker不应用任何虚拟化技术,程序直接运行在底层操作系统上,因此在代码执行速度上具有优势。然而,与轻量级虚拟机相比,Docker仍然过于笨重,启动时会消耗大量时间和资源,这成为制约智能合约性能的短板。在性能测试中,即使使用IBM的大型机LinusONE,Fabric也未能展现出高性能。
代码执行速度可以比作汽车的最高速度,而运行时环境的启动时间则可比作汽车从0加速到100公里/小时的时间。由于智能合约是轻量级程序,通常处于“启动-停止-启动-停止”的模式,很少需要达到最高速度,因此启动时间应被视为影响其性能的关键因素。
系统扩展性
在系统扩展性方面,扩展(Scaling Up)和扩展(Scaling Out)是常见的方法。单核CPU的发展是扩展的典型案例:为了提高CPU性能,只能增加时钟频率。扩展容易遇到瓶颈,因为进一步提高CPU工程的难度越来越大,因此扩展(即使用多核架构同时处理多个任务)成为提升CPU性能的重要方法。
由于成本和技术限制,扩展很快就会达到瓶颈。因此,无法拆分任务的顺序系统的扩展性非常有限,其性能取决于单台计算设备的处理能力。如果我们将顺序系统重构为并行系统,理论上可以实现无限的扩展性。那么,区块链网络能否实现无限的扩展性呢?换句话说,区块链能否以并行方式处理任务?
区块链作为分布式全球账本,不仅记录状态,还记录改变状态的规则。智能合约正是记录这些规则的载体。因此,区块链并行处理任务的能力取决于多个智能合约能否同时执行,或者合约的执行是否与其执行顺序相关。
例如,两个合约希望编辑一个余额为10元的人民币账户。合约A希望向账户增加5元,合约B希望扣除11元。如果先执行合约A,再执行合约B,账户的最终余额为4元;但如果先执行合约B,再执行合约A,合约B会因资金不足而失败,最终余额为15元。在这种情况下,操作顺序的差异会导致不同的结果,因此并行执行不适用。另一方面,如果两个合约处理两个不同的账户,执行顺序不会影响最终结果,此时并行执行是可行的。从上述例子可以看出,两个智能合约能否并行执行取决于执行结果是否与执行顺序无关,而执行顺序的相关性又取决于两个智能合约是否能够编辑相同的状态记录。
基于上述分析,我们可以轻松设计一个具有无限扩展性的智能合约系统,只需设定以下规则:(1)智能合约只能编辑属于该智能合约的状态记录;(2)在同一区块中,每个智能合约只能运行一次。这样,执行顺序就变得无关紧要。问题似乎解决了!但等等……“智能合约只能编辑属于自己的状态记录”意味着每个合约都成为信息孤岛;“在同一区块中,每个智能合约只能运行一次”意味着基于智能合约的某个数字资产只能处理一笔交易。这与智能合约的常规目标相悖,因为不同合约之间的数据调用以及同一智能合约内的重复数据调用正是设计智能合约的常见目标。
问题变得复杂了。尤其是以太坊的智能合约系统支持动态调用,在执行代码之前无法预测以太坊智能合约的行为和路径,因此也无法知道智能合约将编辑哪些状态记录。以太坊的扩展性因此成为其重大劣势。以太坊当前的架构很难支持其成为全球计算平台的宏伟愿景。事实上,以太坊已经提出了分片(Sharding)作为其扩展性问题的解决方案。
分片实际上类似于中国的户籍制度。通过智能合约对256位值进行哈希建模并将其分配到256个分区,就像为每个分区颁发户籍证书。数据调用只能在上海分区或北京分区内进行,而不能直接在不同分区之间进行。在这种情况下,256个分区中的合约可以直接处理,就像将代码执行效率提高了256倍。然而,在这种模式下,跨区调用需要将调用请求写入全网,并需要另一个分区的确认才能执行调用,这也会写入全网,从而大大降低效率,因为跨区调用无法在一个区块内完成。
在实际场景中,分片的一个可能结果是人们会涌入“繁荣”的分区。在一个分区内调用可以避免跨区调用,从而使调用更高效。无论在外围修建多少道路,市中心的交通拥堵问题都无法解决。
代码加载模式
智能合约代码的加载模式也会影响其扩展性。当前主流区块链结构的智能合约系统要求智能合约代码在链上发布,并在代码加载和执行之前。有些代码只会使用一次,但会永久记录在链上。如果这些过时的节点不断积累,将对网络造成巨大负担,从而对扩展性产生负面影响。
这里提出另一种解决方案:将智能合约的哈希结果记录在链上,并使用基于哈希的分布式存储网络(如IPFS)存储智能合约的完整节点。当需要执行智能合约时,代码将从链下加载。由于智能合约的哈希结果已经记录在链上,即使从链下加载代码,也无需担心合约是否被篡改。这种方法节省了大量节点存储空间,并为智能合约内容的隐私提供了一定保护。
耦合性
耦合性是指两个或多个系统之间的相互依赖程度。以下是两个极端的例子,展示了不同区块链和智能合约设计对耦合性的控制:
- 以太坊:以太坊智能合约系统的设计是高度耦合的典型案例。区块链与EVM之间无处不在的相互依赖关系,例如将区块链的执行逻辑与虚拟机混合并不是一个明智的设计,这种混合实际上会带来更多问题。要修改或升级区块链功能,必须升级EVM,通常是向其添加更多指令。此外,EVM很难转移到其他区块链系统,除非该区块链的架构与以太坊高度相似,或者已经开发出与EVM匹配的机制。以太坊的高度耦合将极大地限制其应用。我们将在《重构智能合约第三部分:兼容性与生态系统》中进一步阐述这一点。
- Fabric:Fabric的设计采用低耦合。区块链账本与Docker之间几乎不存在相互依赖。Docker本身已经在区块链以外的许多其他场景中使用。Docker中的智能合约只能通过gPRV协议与其他节点交换信息,该协议包含账本访问和常量存储功能。当需要改进或升级区块链功能时,只需修改gRPC协议。Fabric的超低耦合模式为其他区块链开发者提供了宝贵的经验。
高内聚和低耦合是设计系统架构的常见目标。Fabric的目标是构建一个通用的区块链技术框架,从一开始就采用模块化结构。而以太坊的最初目标是提供一个具体的公链,而非技术框架。因此,以太坊自然具有高度耦合性,这将阻碍其在联盟链和私有链中的应用。
总结
在本文中,我们讨论了:(1)运行时环境与智能合约性能的关系;(2)并行执行;(3)耦合性;(4)代码加载模式。我们还指出了以太坊因其耦合性和抽象设计而导致的扩展性缺陷。我们提出了一种设计哲学,用于构建具有“无限扩展性”的高度并行智能合约系统。最后,我们解释了为什么我们认为适合创建和执行智能合约的良好网络应具备以下特征:
在《重构智能合约》系列的下一篇也是最后一篇文章中,我们将分析智能合约网络使用的编译语言,并提出一种新模式,使与区块链无关的其他领域的开发者能够快速开发智能合约。