黄先森

西二旗民工

分享一些与编程、分布式系统、区块链技术相关的内容


欢迎访问个人github

Java智能合约实现探索

Java智能合约实现探索

Note. “智能合约是一种特殊协议,旨在提供、验证及执行合约。具体来说,智能合约是区块链被称之为“去中心化的”重要原因,它允许我们在不需要第三方的情况下,执行可追溯、不可逆转和安全的交易。“[1]Java作为企业级应用开发最流行的开发语言,却比较少地能够用于在各类公链以及联盟链开发智能合约。本篇介绍一种基于Wasm虚拟机的Java智能合约的实现,重点介绍Java字节码转化为Wasm字节码过程,希望能够抛砖引玉,帮助大家继续发掘更多Java智能合约的实现方案。

1. Java智能合约可选实现方案

纵观当前的联盟链和公链,智能合约的实现方案大致可划分为3种[2]

  • native方式
  • 容器方式
  • 虚拟机方式

但是不管哪种方式,设计都需要满足以下2点基本要求:

  • 智能合约的整个执行过程每一步都需要是一个确定状态转移,也就是说实现智能合约不能依赖如随机数等不确定算法、类库。否则,在不同节点上运行结果不一致导致链的分叉。
  • 智能合约的执行一定要可终止,可以使用诸如可执行命令数量有限、gas计费模型、资源控制、访问限制等方式实现。

native方式:就是在区块链节点上分别运行原生的可执行文件,比如在节点上安装Jdk,直接运行编译构建好的Jar包。这种方式一般只适合内部试验,因为JVM在运行Java字节码过程中能够直接跟节点进行交互,比如执行读写文件、访问网络等系统调用,安全性很差。一旦不注意,有可能会直接导致账本被修改,甚至整个系统崩溃。

容器方式:典型例子是Hyperledger Fabric,通过把链码部署到节点上,所有相关节点都启动一个独立运行在Docker容器上链码进程,而在容器里运行的链码进程通过外部gRPC接口与外部链节点进行通信完成相关交易。每次发送调用智能合约的时候,都会启动一个容器在容器内运行合约代码。而容器启动到运行这段时间相对合约代码的运行比较长,另外容器也能够直接访问宿主机资源,安全性方面会有隐患。所以如果打算使用容器方式支持智能合约,需要考虑快速启动类型的安全容器,如kata container。

虚拟机方式:通过虚拟机方式支持智能合约也是最常见的方式,提供了一个相对透明的执行环境。可以选择发明一种新的编程语言和支持其运行的虚拟机,如EVM&solidity(以太坊)。其中WebAssembly(Wasm)又是其中虚拟机方向的一个大方向,支持的前端语言有C/C++/RUST/Go。Wasm虚拟机能够提供一个沙盒环境,在安全性方面有了很大保障,另外其又具备接近native的运行性能,也就成了区块链领域内比较流行的方案。本篇将介绍基于Wasm虚拟机的Java智能合约实现,接下来对Wasm做简单介绍。

图 1 容器和基于Wasm虚拟机的两种实现方案

2. Java字节码到Wasm字节码转化过程

2.1 WebAssembly(Wasm)简介

WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。

上面是关于Wasm的定义,更详情的介绍请参考[3],有比较详尽的介绍。这里就不详细展开描述,简单介绍一些与后面章节关联比较密切的内容。

Wasm字节码看是一个后缀为wasm格式的二进制文件,逻辑上看是由一个模块组成,而模块又由一系列具有特定功能的“段”结构组成。读者可以尝试使用WasmFiddle进行在线编译,体验一下。以下C程序片段:

int main() { 
  return 42;
}

int add(int a,int b) {
  return a+b;
}

通过编译生成Wasm,及对应可读格式(wat)如下:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "main" (func $main))
 (export "add" (func $add))
 (func $main (; 0 ;) (result i32)
  (i32.const 42)
 )
 (func $add (; 1 ;) (param $0 i32) (param $1 i32) (result i32)
  (i32.add
   (get_local $1)
   (get_local $0)
  )
 )
)

上面是一段表示树形结构的文本(S-表达式),根节点为module,其子节点由tablememoryexport、…、func等段组成。

memory段用于定义模块实例化后的可用线性内存段,(memory $0 1),memory表示线性内存段,而接下来的数的$0,表示使用$0作为初始容量大小,而1表示表示最大可用内存大小,这里单位为页(64KB),所以最大内存空间为64KB。

export段表示把数据从模块内导出到模块外的宿主环境,当Wasm虚拟机在执行过程中遇到export段,可以使用自身的数据或者函数替换。

func段主要用于存储在模块中定义的所有函数类型数据,func表示一个函数段,接着是函数名,如$main,而 (; 0 ;) 表示索引位置,result i32表示函数结果是一个32的有符号整数,下面是函数体,由若干指令组成,如i32.const 42表示一个32位有符号整数,值为42并压入数据栈中。

除了上述的几个段,Wasm还有importstartglobaldataelements等类型的段,感兴趣的读者可以阅读理解WebAssembly文本格式了解更多的内容。

总体来说,Wasm模块的二进制数据内容由众多不同类型的“段”组成,每一种类型的段结构都有特定的信息含义,这些不同的段结构之间又组成了Wasm模块完整的功能和逻辑。

2.2 Java字节码转化为Wasm字节码流程

与C++程序类似,Java源码先通过编译生成Java字节码,再转化为Wasm字节码。

通过调研发现,目前已有的开源方案(teavmCheerpJBytecoderJWebAssembly)将Java源码转化为Wasm字节码的流程图2前半部分所示。

图 2 Java源码到Wasm字节码主要转化流程

  • 源码先编译成Java字节码,再使用asm[4]字节码操作库读取主函数所在的类的字节码(对于智能合约来说,指定为运行入口所在的类)。但是即便是Java虚拟机,在运行的过程也不是一次性的把jar包里所有的class加载到内存里。而是需要通过主动或者被动地把主类依赖的其他类加载到Java虚拟机。但是要翻译成对应的Wasm字节码,就必须转化所有从入口函数可达的Java字节码。因此,需要作依赖分析。

  • 另外可以通过消除不可达代码,减少Wasm字节码体积。

  • 经过上面步骤,Java源码就变成一棵比较大的抽象语法树,再将这颗语法树每一条Statement一一翻译成对应的Wasm指令即可得到整个Wasm模块。

下面将一个具体的例子来说明整个过程。

2.3 示例说明

while(k<7){
  foo(k);
  k=k+1;
  Object obj = new Object();
}
程序1 Java代码示例片段

上面程序1是一个Java代码示例片段,在一个while循环里调用函数foo以及创建对象obj。对应的抽象语法树如图3所示:

图 3 程序1对应的抽象语法树

实际中,AST对应的每个节点几乎都能够一一翻译成Wasm的指令,如图4所示。比如Wasm通过loopbr即可实现while语句。

图 4 程序1对应的Wasm S-表达式

2.4 关于内存

我们知道,Java是本身具备自动垃圾回收机制的,像上面的例子中,即便不断循环创建新对象,背后也会有线程定期地回收可释放的内存空间。但是Wasm的MVP标准里面是没有自动垃圾回收机制的,所以在处理Java的构造函数(new函数)的时候,会涉及到Wasm线性内存空间的指令。如图5所示,Wasm中调用new函数的时候,可能会对Wasm的线性内存空间使用标记清除垃圾回收算法。

图 5 Wasm线程内存空间的"标记清除"过程

3. 基于Wasm虚拟机的Java智能合约实现

关于这部分的内容,需要设计到Wasm虚拟机,以及虚拟机和区块链节点间的通讯,后续将通过实现一个Wasm虚拟机原型同时再进行更详细的介绍,整体流程如图6所示。

图 6 Java智能合约的实现方案

  • 可以通过低代码、DSL、合约模板等方式降低智能合约开发门槛,生成Java源码。

  • 再通过maven插件,解析Java源码的抽象语法树,做预处理。生成满足区块链Wasm虚拟机执行规范的Java源码。

  • 再编译生成Java字节码。

  • 按上面内容,将Java字节码转化为Wasm字节码。

  • 将合约接口信息与Wasm字节码进行整合,再部署到区块链上,最后进行调用生成交易。

最后,关于Wasm虚拟机部分的内容将再后续单独进行介绍,到时候会实现一个Wasm虚拟机来加深对Wasm的理解,希望以上内容能给到大家关于Java智能合约方面的一些启发。

4. 参考资料

[1] 什么是区块链的“智能合约”

[2] Smart Contract and Virtual Machine

[3] WebAssembly完全入门——了解wasm的前世今身

[4] 史上最通俗易懂的ASM教程

最近的文章

6.824分布式系统课程系列(一):开篇&MapReduce介绍及框架实现

开篇&MapReduce介绍及框架实现Note. 现在分布式系统的架构设计已经成为后台开发者的必备技能,而Golang在并发方面引入了协程的方式,相对于其他语言极大地提高了并发处理能力。综合这些因素,麻省理工大学开设了用Golang实现分布式系统的课程,即6.824。在学习这门课程之前,最好对Golang有一定使用,不然在实现课程实验中会遇到较大的阻碍。而学习这门课程我们将涉及到存储、通信、计算等三方面的基础架构,目的是隐藏分布式复杂性的抽象。这些架构设计经验对于面试、工作也是非常...…

分布式系统 MapReduce 6.824继续阅读
更早的文章

基于数据流分析的程序漏洞分析技术方案(一)

数据流分析Note. 程序安全愈来愈受到重视,特别是在区块链领域,数据上链后无法篡改是把双刃剑,一方面是不用担心数据能够被恶意删改,但另外一方面如果当初上传的是错误的数据就无法修复。把程序缺陷扼杀在摇篮之中,是最有效也是成本最低的质量保障手段。另外像区块链领域涉及到资产的智能合约,安全性保障更应该得到保障。程序静态分析在程序缺陷分析已经发挥了巨大作用,本章将介绍静态分析领域的数据流分析技术。内容翻译自Dataflow Analysis,后面将进一步介绍开源的数据流分析工具Phasar,以及...…

llvm IR 数据流分析 程序漏洞分析 智能合约继续阅读