简述如何实现区块链中的JVM

  • xuzhitong
  • 更新于 2021-01-08 17:46
  • 阅读 3140

虚拟机是区块链中的一个关键组件,用来执行智能合约,需要满足安全性和一致性,所谓的安全性一般是指合约代码需要在隔离的沙箱环境中运行,以免错误或恶意代码造成对区块链系统的损害。而一致性...

虚拟机是区块链中的一个关键组件,用来执行智能合约,需要满足安全性和一致性,所谓的安全性一般是指合约代码需要在隔离的沙箱环境中运行,以免错误或恶意代码造成对区块链系统的损害。而一致性是指区块链网络中任意诚实的节点执行同一个合约,如果输入参数一致,输出结果都应该是一致的。目前比较主流的虚拟机实现包括EVM、WASM,其他的实现还有如星云链的谷歌V8,Nervos的RISC-V,SOLANA的BPF等,设计如此众多不同虚拟机的目的,主要是对区块链系统互操作性、安全性、执行性能、硬件兼容性等因素的考量。如EVM更注重的是安全性,虽然Solidity设计是图灵完备的语言,但是通过自身限定的最小语法集,来保证合约的安全可控,而星云链采用V8,更多的一方面也是考虑智能合约的互操作性,使用javascript降低了合约的编写门槛。尽管Solidity也是类javascript语言,并且入门门槛也不是很高,但是对于很多想入行区块链的程序员来说,学习一门新的语言还是需要一些成本的。

JVM 为何在区块链中使用的较少

JVM本身就是一种虚拟机,而且java语言又是最为流行的几大语言之一,为什么区块链中使用的较少呢?个人总结主要包含如下几个原因:

1、语言的可控性,同c++/golang/rust等主流开发语言类似,java也是系统级语言,可以访问操作系统的任意资源,例如读写文件、访问网络等 2、执行性能,java是解释性语言,jvm载入的class文件是一种中间语言,真正执行的时候才会进一步解释成机器码执行,所以主流的jvm都会引入JIT对热点代码进行及时编译来提高执行效率 3、语言的复杂性,java有很多复杂的语法,比如支持OOP的接口、多态、反射和多线程控制等,对于更注重过程化执行的智能合约用处不大,而且容易影响合约执行的一致性。 那如果能解决这些问题,针对jvm做一个自定义的最小集合裁剪,那对于很多java开发者来说,还是比较有吸引力的。

区块链中的jvm方案应如何实现?

下面我们对如何实现一个区块链中的jvm方案做一个简单的描述,如果使用golang语言,建议参考下这个代码库:https://github.com/zxh0/jvm.go (笔者曾经参考这个代码实现过一个jvm虚拟机,也有很多问题没有很好的解决,这里抛砖引玉,和大家共同探讨)

  1. 如何传递参数 java是OOP的,如果编写合约,需要先定义一个合约类,合约类中定义的类方法就是合约能够执行的方法。区块链上合约执行都是通过交易触发的,而合约地址、输入参数需要通过交易携带,首先要解决的问题就是如何传递这些信息给合约,对于基本类型处理比较简单,可以都编码成字符串进行传递,合约执行的时候根据方法的参数类型解析成对应的结果,而对于自定义类的对象如何处理呢?可以使用json、xml、proto等编码方式对类对象进行逐字段编码,如果字段也是个自定义类对象,那就递归进行编码。这几种编码方式的有点是利于阅读,但是缺点就是空间占用较大,对于区块链这种冗余分布式存储机制来讲,占用空间越少越好,所以可以采用以太坊中使用的RLP(递归长度前缀)编码方式。

如何载入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有兴趣的话欢迎一起交流。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
xuzhitong
xuzhitong
江湖只有他的大名,没有他的介绍。