以太坊虚拟机实现分析
1、  原理

a)     以太坊虚拟机(EVM)是以太坊中智能合约的运行环境。它不仅被沙箱封装起来,事实上它被完全隔离,也就是说运行在EVM内部的代码不能接触到网络、文件系统或者其它进程。甚至智能合约与其它智能合约只有有限的接触。

b)     编程语言支持:为了兼容尚未实现的应用程序,虚拟机应该支持编程语言,而不是特定的应用程序,应用程序的业务逻辑可以用这种语言实现

c)      高级语言实现:

                 i.          开发人员不想在二进制EVM程序中编程,用较高级语言编写代码,编译器编译为EVM代码

                ii.          高级语言包括:SerpentLLLSolidity(最流行)

d)     EVM要求:

                 i.          代码量较小(使得许多用户的很多合同可以由一个节点存储);

                ii.          禁止无限循环(必需确定完成不能超时);

               iii.          多种语言实现,缓解公共链中的开发人员集中化

2、 虚拟机实现

a)     指令集

EVM的指令集被刻意保持在最小规模,以尽可能避免可能导致共识问题的错误实现。所有的指令都是针对256比特这个基本的数据类型的操作。具备常用的算术,位,逻辑和比较操作。也可以做到条件和无条件跳转。此外,合约可以访问当前区块的相关属性,比如它的编号和时间戳。

                 i.          暂停执行

关键字

操作码

输入

输出

描述

STOP

0x00

0

0

停止执行

 

                ii.          算术运算

关键字

操作码

输入

输出

描述

ADD

0x01

2

1

加法操作

MUL

0x02

2

1

乘法操作

SUB

0x03

2

1

减法操作

DIV

0x04

2

1

除法操作

SDIV

0x05

2

1

有符号除法

MOD

0x06

2

1

求模操作

SMOD

0x07

2

1

有符号求模操作

ADDMOD

0x08

3

1

先加再求模

MULMOD

0x09

3

1

先乘再求模

EXP

0x0a

2

1

指数运算

SIGNEXTEND

0x0b

2

1

扩展有符号整数的长度

 

               iii.          按位逻辑与比较运算

关键字

操作码

输入

输出

描述

LT

0X10

2

1

小于操作

GT

0X11

2

1

大于操作

SLT

0X12

2

1

有符号小于操作

SGT

0X13

2

1

有符号大于操作

EQ

0X14

2

1

等于操作

ISZERO

0x15

1

1

否定操作

AND

0x16

2

1

按位与运算

OR

0x17

2

1

按位或运算

XOR

0x18

2

1

按位异或运算

NOT

0x19

1

1

按位非运算

BYTE

0x1a

2

1

从字检索单个字节

 

              iv.          加密操作

关键字

操作码

输入

输出

描述

SHA3

0x20

2

1

计算SHA3-256散列

 

               v.          环境信息

关键字

操作码

输入

输出

描述

ADDRESS

0x30

0

1

获取当前执行帐户的地址

BALANCE

0x31

1

1

获取给定帐户的余额

ORIGIN

0x32

0

1

获取执行起始地址

CALLER

0x33

0

1

获取调用者地址

CALLVALUE

0x34

0

1

通过负责此执行的指令/事务获取存储值

CALLDATALOAD

0x35

1

1

获取当前环境的输入数据

CALLDATASIZE

0x36

0

1

获取当前环境中的输入数据的大小

CALLDATACOPY

0x37

3

0

将当前环境中的输入数据复制到内存

CODESIZE

0x38

0

1

获取在当前环境中运行的代码的大小

CODECOPY

0x39

3

0

将当前环境中运行的代码复制到内存

GASPRICE

0x3a

0

1

获取当前环境中的气体价格

EXTCODESIZE

0x3b

1

1

获取在当前环境中使用给定偏移量运行的代码大小

EXTCODECOPY

0x3c

4

0

将在当前环境中运行的代码复制到具有给定偏移量的内存中

 

              vi.          块信息

关键字

操作码

输入

输出

描述

BLOCKHASH

0x40

1

1

获取最近完成块的哈希值

COINBASE

0x41

0

1

获取块的硬币基地址

TIMESTAMP

0x42

0

1

获取块的时间戳

NUMBER

0x43

0

1

获取块的编号

DIFFICULTY

0x44

0

1

获得块的难度

GASLIMIT

0x45

0

1

获取块的气体限制

 

             vii.          内存,存储和流操作

关键字

操作码

输入

输出

描述

POP

0x50

1

0

出栈

MLOAD

0x51

1

1

从内存加载字

MSTORE

0x52

2

0

word保存到内存

MSTORE8

0x53

2

0

byte保存到存储器

SLOAD

0x54

1

1

从存储器加载word

SSTORE

0x55

2

0

word保存到存储

JUMP

0x56

1

0

跳转

JUMPI

0x57

2

0

有条件跳转

PC

0x58

0

1

获取程序计数器

MSIZE

0x59

0

1

获取活动内存的大小

GAS

0x5a

0

1

获取可用气体的量

JUMPDEST

0x5b

0

0

无操作

 

            viii.          Push操作

关键字

操作码

输入

输出

描述

PUSH1

0x60

0

1

1字节项放在堆栈上

PUSH2

0x61

0

1

2字节项放在堆栈上

PUSH3

0x62

0

1

3字节项放在堆栈上

PUSH4

0x63

0

1

4字节项放在堆栈上

PUSH5

0x64

0

1

5字节项放在堆栈上

PUSH6

0x65

0

1

6字节项放在堆栈上

PUSH7

0x66

0

1

7字节项放在堆栈上

PUSH8

0x67

0

1

8字节项放在堆栈上

PUSH9

0x68

0

1

9字节项放在堆栈上

PUSH10

0x69

0

1

10字节项放在堆栈上

PUSH11

0x6a

0

1

11字节项放在堆栈上

PUSH12

0x6b

0

1

12字节项放在堆栈上

PUSH13

0x6c

0

1

13字节项放在堆栈上

PUSH14

0x6d

0

1

14字节项放在堆栈上

PUSH15

0x6e

0

1

15字节项放在堆栈上

PUSH16

0x6f

0

1

16字节项放在堆栈上

PUSH17

0x70

0

1

17字节项放在堆栈上

PUSH18

0x71

0

1

18字节项放在堆栈上

PUSH19

0x72

0

1

19字节项放在堆栈上

PUSH20

0x73

0

1

20字节项放在堆栈上

PUSH21

0x74

0

1

21字节项放在堆栈上

PUSH22

0x75

0

1

22字节项放在堆栈上

PUSH23

0x76

0

1

23字节项放在堆栈上

PUSH24

0x77

0

1

24字节项放在堆栈上

PUSH25

0x78

0

1

25字节项放在堆栈上

PUSH26

0x79

0

1

26字节项放在堆栈上

PUSH27

0x7a

0

1

27字节项放在堆栈上

PUSH28

0x7b

0

1

28字节项放在堆栈上

PUSH29

0x7c

0

1

29字节项放在堆栈上

PUSH30

0x7d

0

1

30字节项放在堆栈上

PUSH31

0x7e

0

1

31字节项放在堆栈上

PUSH32

0x7f

0

1

32字节项放在堆栈上

 

              ix.          从堆栈复制第N个项目

关键字

操作码

输入

输出

描述

DUP1

0x80

1

2

在堆栈上复制第1条数据

DUP2

0x81

2

3

在堆栈上复制第2条数据

DUP3

0x82

3

4

在堆栈上复制第3条数据

DUP4

0x83

4

5

在堆栈上复制第4条数据

DUP5

0x84

5

6

在堆栈上复制第5条数据

DUP6

0x85

6

7

在堆栈上复制第6条数据

DUP7

0x86

7

8

在堆栈上复制第7条数据

DUP8

0x87

8

9

在堆栈上复制第8条数据

DUP9

0x88

9

10

在堆栈上复制第9条数据

DUP10

0x89

10

11

在堆栈上复制第10条数据

DUP11

0x8a

11

12

在堆栈上复制第11条数据

DUP12

0x8b

12

13

在堆栈上复制第12条数据

DUP13

0x8c

13

14

在堆栈上复制第13条数据

DUP14

0x8d

14

15

在堆栈上复制第14条数据

DUP15

0x8e

15

16

在堆栈上复制第15条数据

DUP16

0x8f

16

17

在堆栈上复制第16条数据

 

               x.          使用顶部数据交换堆栈中的第N项数据

关键字

操作码

输入

输出

描述

SWAP1

0x90

2

2

堆栈的顶部数据和第2项数据交换

SWAP2

0x91

3

3

堆栈的顶部数据和第3项数据交换

SWAP3

0x92

4

4

堆栈的顶部数据和第4项数据交换

SWAP4

0x93

5

5

堆栈的顶部数据和第5项数据交换

SWAP5

0x94

6

6

堆栈的顶部数据和第6项数据交换

SWAP6

0x95

7

7

堆栈的顶部数据和第7项数据交换

SWAP7

0x96

8

8

堆栈的顶部数据和第8项数据交换

SWAP8

0x97

9

9

堆栈的顶部数据和第9项数据交换

SWAP9

0x98

10

10

堆栈的顶部数据和第10项数据交换

SWAP10

0x99

11

11

堆栈的顶部数据和第11项数据交换

SWAP11

0x9a

12

12

堆栈的顶部数据和第12项数据交换

SWAP12

0x9b

13

13

堆栈的顶部数据和第13项数据交换

SWAP13

0x9c

14

14

堆栈的顶部数据和第14项数据交换

SWAP14

0x9d

15

15

堆栈的顶部数据和第15项数据交换

SWAP15

0x9e

16

16

堆栈的顶部数据和第16项数据交换

SWAP16

0x9f

17

17

堆栈的顶部数据和第17项数据交换

 

                 i.          使用0..n标记记录一些地址的一些数据

关键字

操作码

输入

输出

描述

LOG0

0xa0

2

0

写日志

LOG1

0xa1

3

0

写日志

LOG2

0xa2

4

0

写日志

LOG3

0xa3

5

0

写日志

LOG4

0xa4

6

0

写日志

 

                ii.          系统操作

关键字

操作码

输入

输出

描述

CREATE

0xf0

3

1

创建具有关联代码的新帐户

CALL

0xf1

7

1

消息呼叫到帐户

CALLCODE

0xf2

7

1

调用自己,但是从TO参数而不是从自己的地址获取代码

RETURN

0xf3

2

0

暂停执行返回输出数据

DELEGATECALL

0xf4

6

1

在理念上类似于CALLCODE,除了它将发送者和值从父作用域传播到子作用域

SUICIDE

0xff

1

0

暂停执行并注册帐户以便稍后删除

 

 

3、 执行流程

a)     总体技术架构,借用李赫的图片

钱包客户端可以编写智能合约代码,通过本地solc程序编译成evm字节码,然后通过rpc接口发送到以太坊节点

各个以太坊节点通过本地evm虚拟机执行智能合约的二进制代码,得到运算结果后,就可以写入区块链数据。

b)     Evm解析原理

解析指令使用的方法是译码分派(decode-and-dispatch)方式,它是围绕一个主循环来组织的,要解析一条指令,就将其分配到属于该指令类型的解析程序。其流程如下:

先创建个虚拟机vm,然后创建个程序program,举个例子,对应evm代码"6002600201"program指向的就是这段16进制数据,虚拟机执行的pc指针对应字符串"6002600301"的第一个字节60,在while循环里面,先读取一个操作码op,这里是60,含义是push1,操作就是压栈一个字节,代码实现就是执行steppc指针指向02,通过sweep函数读取一个字节的数据,得到02,然后把02压栈;下一个循环中,读取了下一个字节60,含义还是push1,同理读取到数据03并压栈;再下一个循环中,读取了下一个字节01,含义是add,操作就是两个数相加,代码实现是连续出栈两个数,然后相加,然后压栈。因此程序"6002600301"实现的操作就是2+3=5,最终堆栈里面保存了数据5。总体来说就是一个循环,先读取一条指令,根据指令类型,继续读取数据或者操作堆栈数据,然后继续循环读取指令,做新的操作,最终执行完整个程序。

 

4、 问题

a)     Solidity不是常用的高级语言,入门门槛高,相关代码和文档也很少

b)     支持图灵完备就面临死循环,递归调用问题,导致复杂度提升,是否有必要

5、 参考文档:

a)     以太坊(三)

b)     比特币脚本

c)      以太坊虚拟机与执行环境概述(英文).pdf

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐