虚拟机是区块链中的一个关键组件,用来执行智能合约,需要满足安全性和一致性,所谓的安全性一般是指合约代码需要在隔离的沙箱环境中运行,以免错误或恶意代码造成对区块链系统的损害。而一致性...
虚拟机是区块链中的一个关键组件,用来执行智能合约,需要满足安全性和一致性,所谓的安全性一般是指合约代码需要在隔离的沙箱环境中运行,以免错误或恶意代码造成对区块链系统的损害。而一致性是指区块链网络中任意诚实的节点执行同一个合约,如果输入参数一致,输出结果都应该是一致的。目前比较主流的虚拟机实现包括EVM、WASM,其他的实现还有如星云链的谷歌V8,Nervos的RISC-V,SOLANA的BPF等,设计如此众多不同虚拟机的目的,主要是对区块链系统互操作性、安全性、执行性能、硬件兼容性等因素的考量。如EVM更注重的是安全性,虽然Solidity设计是图灵完备的语言,但是通过自身限定的最小语法集,来保证合约的安全可控,而星云链采用V8,更多的一方面也是考虑智能合约的互操作性,使用javascript降低了合约的编写门槛。尽管Solidity也是类javascript语言,并且入门门槛也不是很高,但是对于很多想入行区块链的程序员来说,学习一门新的语言还是需要一些成本的。
JVM本身就是一种虚拟机,而且java语言又是最为流行的几大语言之一,为什么区块链中使用的较少呢?个人总结主要包含如下几个原因:
1、语言的可控性,同c++/golang/rust等主流开发语言类似,java也是系统级语言,可以访问操作系统的任意资源,例如读写文件、访问网络等 2、执行性能,java是解释性语言,jvm载入的class文件是一种中间语言,真正执行的时候才会进一步解释成机器码执行,所以主流的jvm都会引入JIT对热点代码进行及时编译来提高执行效率 3、语言的复杂性,java有很多复杂的语法,比如支持OOP的接口、多态、反射和多线程控制等,对于更注重过程化执行的智能合约用处不大,而且容易影响合约执行的一致性。 那如果能解决这些问题,针对jvm做一个自定义的最小集合裁剪,那对于很多java开发者来说,还是比较有吸引力的。
下面我们对如何实现一个区块链中的jvm方案做一个简单的描述,如果使用golang语言,建议参考下这个代码库:https://github.com/zxh0/jvm.go (笔者曾经参考这个代码实现过一个jvm虚拟机,也有很多问题没有很好的解决,这里抛砖引玉,和大家共同探讨)
如何载入java系统类
参考jvm.go代码库,可以在目标节点上预安装一个jdk环境,然后在节点应用启动时把需要用到的java系统类包预加载入内存的一个map中。这里可以根据实际情况对导入进来的系统类包做一些选择裁剪,同时过滤掉容易造成执行结果不一致的包,比如随机数、多线程,IO操作等。 如何模拟离线沙盒执行环境 笔者采用的是如上裁剪系统类包的做法,限制了用户合约直接引用网络、读写、cmd等操作权限,同时不允许用户合约直接引用第三方jar包或native执行第三方库等,保证合约的安全性。这种方式比较简单粗暴并不是特别好的方案,对于恶意代码跳过系统包,自己实现类似的访问资源的操作,就无法检测出来,并不是一个好的方案,如果有好的方案可以一起讨论。 如何屏蔽随机数、多线程等容易造成执行结果不一致的语法执行 同上也是限制引入包的做法屏蔽,对于恶意代码自己实现随机数,这个也同样无法检测出来。
如何启动合约执行
jvm执行是需要找到Main入口,而合约代码可以不提供Main函数,我们可以在生成合约包的工具中自动生成相应的Main函数,添加new合约对象,然后调用对应方法的代码。
如何读写合约状态变量 java存取的指令码主要是load和store系列指令。但是对于合约启动执行时,需要先从数据库中读取到历史的状态,比如合约中的余额变量,如上自动生成的Main函数中会先new一个合约对象,于是我们可以捕获到合约对象的构造函数,注入读取数据库代码,并将合约对象的成员变量全部恢复。这里也可以参考solidity的方法,区分内存和存储变量,可以通过注解来实现。jvm可以解析变量的注解,如果注解是Storge,就存取数据库,否则从当前内存获取。
如何返回执行结果
用户合约代码可能会执行失败,比如余额不足,权限不匹配等,我们可以将交易回执的指针关联到合约执行的主Thread中,这样在执行失败的地方,都可以随时返回,并且自定义相关错误信息。
如何回退交易 可以参考以太坊的方案,利用MPT保存状态变量的历史值,状态变量的修改是实时修改本地存储,如果交易失败回滚,直接修改为上一个历史状态即可。
如何提升执行性能 我们可以用native实现类似以太坊的系统合约的功能或替换掉java中比较耗时的操作,比如hashmap。参考jvm.go的实现,我们可以定制很多native方法来提升执行效率,例如合约中执行sha256,如果用java自带的方法,会增加几千条指令。我们也可以利用native方法来完成自定义的一些功能,比如零知识证明、同态加密库等。
如何解决停机问题
参考以太坊的gas机制,按照指令粒度统计燃烧的gas,对于涉及到存储的指令,根据存储的数据大小增加gas比例。这样实现的好处是gas统计比较精确,但是java指令较多,代码实现繁琐,执行性能也会影响。另外看到业内使用WASM的主流方案都是统计指令注入到了编译生成的机器码中,这个方案也可以尝试,但需要自己实现javac。
以上是笔者对jvm实现方案的一些简单看法,如果对区块链jvm有兴趣的话欢迎一起交流。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!