【虚拟机专栏】熟悉的新朋友 - 链上 JVM
—— 导读 ——
前文,我们介绍了对虚拟机的历史、特点、发展以及 Solidity 和 EVM 进行详细介绍。Solidity 和 EVM 的出现为区块链的应用场景打开了新的大门,但是合约开发者使用 Solidity 进行智能合约的开发,不可避免地存在着新语言的学习成本问题。
那么,是否有这样一位老朋友,能让「合约开发者」和「区块链」快速打成一片呢?
众所周知,Java 是一种被广泛使用的、面向对象的编程语言,具有“一次编写,处处执行”的跨平台特性。于是,我们将 Java 请到了我们的区块链平台,自研了一套可以执行 Java 智能合约的执行引擎 HVM。将 Java 智能合约引入区块链,主要有以下目的:
降低智能合约开发的成本,让合约开发者能专注于合约逻辑本身而不是语法细节。
为开发者提供熟悉的、适合区块链场景的工具类和方法,避免重复“造轮子”。
解决传统智能合约与账本交互模式单一的问题,提供多种更方便、更灵活的账本交互的数据结构和方法,更好地满足业务场景的需要。
本文将主要讲解如何让 Java 编写的智能合约运行在区块链上,不会涉及大量 JVM 细节。从 Java 合约使用、虚拟机适配、账本交互机制三个方面进行介绍。
—— Java 智能合约的使用 ——
从合约开发者的角度来看,Java 智能合约的使用流程通常包括以下三个步骤:合约开发、合约部署、合约调用。
▲Java 合约开发
相比传统智能合约,Java 智能合约的开发和使用更为简单方便,主要体现在:
1)项目搭建快:开发者只需要在本地 IDE 中新建 Java 项目,引入合约开发依赖包,便可以开发合约。完成编码后,将代码打包成合约 Jar 文件即可用于部署上链。
2)工具方法多:开发者可以使用 JDK 中的类和方法,避免重复”造轮子“的麻烦。
3)学习成本低:Java 语言使用广泛,大部分开发者只需要了解合约开发依赖包的接口,便能熟练使用 Java 智能合约。
▲Java 合约部署
对于 Java 智能合约的部署,开发者通过一笔交易将合约 Jar 包上传到链上,区块链会对合约进行初始化,生成一个唯一的合约地址,并通过交易回执将合约地址给开发者。
▲Java 合约调用
开发者可以通过指定合约地址,并输入合约方法名和参数,构造并发送一笔合约调用交易。区块链平台收到交易以后,获取一个 JVM 实例,将合约地址对应的合约 Jar 中的类文件加载 JVM 中,创建一个合约类的实例并调用指定方法,得到执行结果并通过交易回执返回给开发者。
—— HVM 详解 ——
▲JVM 接入区块链
要实现一个 Java 智能合约执行引擎,一定绕不开将 JVM 接入区块链的问题。目前大部分区块链系统使用 Golang 开发,而大部分开源的 JVM 通常是 C++编写。如果想要快速地将 JVM 接入到区块链系统中,可以通过 CGO 将 Golang 和 C++打通。但考虑到在区块链系统中对 JVM 内部优化的需要,HVM 选择了通过 Golang 实现了 JVM。虽然自己实现 JVM 会引入大量的开发成本,但是极大地方便了后续针对区块链场景进行性能优化和功能拓展工作的开展。
“当区块链中接入 JVM 后,还需要做些什么让 JVM 成为区块链中的 Java 合约执行引擎呢?”
▲虚拟机安全适配
前文中提到,我们在区块链的 Java 合约引擎中支持用户使用 JDK 中的类和方法。考虑到区块链上的合约执行引擎需要满足执行环境的隔离以及执行结果的确定性,我们需要对 JDK 和 JVM 进行安全适配。其中包括以下几点:
1)禁用”不安全”类和方法:在智能合约引擎中,可能引起执行结果不一致的方法是”不安全“的。比如 Java 中生成的随机数方法,其执行结果是不确定的,区块链中的 Java 合约引擎会禁用这些”不安全”的类和方法。
2)隔离合约的执行环境:区块链平台中的 Java 智能合约需要一个隔离的执行环境,即 Java 智能合约无法像普通的 Java 程序使用线程、网络、访问系统时间等功能。此外,我们在 JDK 中实现了一部分与区块链相关的方法,部分方法不允许被 Java 合约调用。因此,我们在 HVM 内部实现了方法调用过滤器,拦截不被允许的方法调用。
3)确定逻辑执行顺序:同 EVM 一样,我们在 HVM 内部实现了一套 Gas 机制,对合约执行进行代价计算。指令执行的不同,会引起不同节点计算的 Gas 值不同。在原始的 JDK 中,部分方法在两次调用时,虽然其结果一致,其逻辑执行的代码路径不同。以使用单例模式的类为例,首次调用这个类的实例方法时,需要创建这个类的实例;之后调用其方法时,不再需要创建实例。这种逻辑的差异,会导致新启动的节点与其他节点的执行的 Gas 值不一致。因此,我们需要对 JDK 中这类逻辑进行适配,保证逻辑执行顺序始终一致。
▲账本交互机制
将 JVM 接入区块链,还需要保证合约与账本数据交互的功能。EVM 中存在账本交互的指令,但是在 JVM 规范中不存在用于账本交互的指令,所以我们需要提供一套账本数据交互机制,让 Java 智能合约能够操作区块链上的账本数据。
实现账本交互机制可以有两种方案:
1)在 JVM 中实现一套账本交互的自定义指令集。同时提供一种 Java 合约的编译器或插件,在合约字节码中生成专用于区块链中账本交互的自定义指令。
2)在 JDK 中实现一套读写账本数据的工具类和方法,在合约执行过程中,由合约执行引擎来调用这些方法,负责合约持久化字段的读写操作。
HVM 在实现的过程中,选择了第二种方案。在合约执行的过程中,如果使用到合约的持久化字段,合约执行引擎会调用账本读取的方法从账本中获取其数据。对于账本写入操作,执行引擎会先进行缓存,待合约执行结束后,扫描合约中有数据更新的持久化字段,将字段更新的数据统一刷入到账本中。
相比指令的方法,使用 Java 方法来实现账本数据交互的功能虽然会有更多的指令开销,但是能够为用户提供更友好地方式操作持久化字段。以 Map 为例,我们在 Java 智能合约中为 Map 提供了除 Get 和 Put 以外的方法,允许用户使用迭代器等方法方便地操作 Map。考虑到读写 Map 的复杂场景,维护一个可靠的迭代器逻辑较为复杂。而以指令的方式操作账本数据,那么势必要实现一套复杂的账本交互指令集。显然工具类和方法更适合完成这些复杂的逻辑操作,并更容易支持合约数据结构功能的拓展。
通过这种方案,用户在编写 Java 智能合约时,能够选用功能强大的数据结构类操作账本。这些数据结构类,将账本交互的 Java 方法进行封装,使用户无法感知,并尽可能实现 JDK 中的接口。如 HVMMap、HVMList 等数据结构,分别实现了 JDK 中的 Map 和 List 接口,使用起来与 JDK 提供的其他 Map、List 几乎一致。
▲虚拟机对比分析
除了 HVM 合约以外,常见的合约还有 EVM 的 Solidity 合约、Fabric 的 Chaincode 等等。
EVM 提供了沙盒化的、完全隔离的合约执行环境。Solidity 从设计初就作为智能合约语言来考虑,其在账本操作上有较大优势。
Fabric 的 Chaincode 支持多种语言编写。Chaincode 运行在一个受保护的 Docker 容器中,在接收到客户端发送的调用请求后,会在容器中模拟执行出对账本的读写集并返回给客户端,最后由客户端再次发起将模拟交易产生读写集写入账本的请求。
HVM 相比与其他的执行引擎,主要以下特性:
HVM 合约是在安全的封闭式沙箱环境执行,安全性高
执行引擎内嵌于平台,无网络依赖
HVM 提供完整的合约生命周期管理机制,只需通过 sdk、api 调用就可进行合约的升级
提供丰富的内置功能,例如日志输出、密码套件、多样化调用合约
除了 Java 语言 JDK 本身提供的功能外,HVM 提供多种基于区块链账本数据操作的数据结构
—— 小结 ——
本文首先从开发者的角度,介绍 Java 智能合约的开发及使用流程,再讲解了在区块链中接入 JVM 的技术方案,探讨了对 JDK 的代码改造以及账本交互机制的实现。HVM 始终向着更好的性能和更友好的使用体验目标摸索前进。与此同时,行业内的合约执行引擎正处于百花齐放的状态,接下来我们还会对支持 Rust 等语言编写智能合约的 FVM 以及支持区块链上 SQL 执行的 KVSQL 进行详细介绍,敬请期待!
对于虚拟机感兴趣的小伙伴,可以添加小助手桔子(18458407117)加入技术交流群,欢迎您和我们共享观点,共论区块链的无限未来~
作者简介
卢益铭、姚兵
趣链科技基础平台部区块链虚拟机研究小组
参考文献
[1] Java 虚拟机规范.
版权声明: 本文为 InfoQ 作者【趣链科技】的原创文章。
原文链接:【http://xie.infoq.cn/article/d84d15c4505e3e2e595d25fb3】。文章转载请联系作者。
评论