以太坊虚拟机实现分析
来源:互联网 发布:数控车床螺纹编程公式 编辑:程序博客网 时间:2024/05/09 13:10
a) 以太坊虚拟机(EVM)是以太坊中智能合约的运行环境。它不仅被沙箱封装起来,事实上它被完全隔离,也就是说运行在EVM内部的代码不能接触到网络、文件系统或者其它进程。甚至智能合约与其它智能合约只有有限的接触。
b) 编程语言支持:为了兼容尚未实现的应用程序,虚拟机应该支持编程语言,而不是特定的应用程序,应用程序的业务逻辑可以用这种语言实现
c) 高级语言实现:
i. 开发人员不想在二进制EVM程序中编程,用较高级语言编写代码,编译器编译为EVM代码
ii. 高级语言包括:Serpent,LLL,Solidity(最流行)
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,操作就是压栈一个字节,代码实现就是执行step,pc指针指向02,通过sweep函数读取一个字节的数据,得到02,然后把02压栈;下一个循环中,读取了下一个字节60,含义还是push1,同理读取到数据03并压栈;再下一个循环中,读取了下一个字节01,含义是add,操作就是两个数相加,代码实现是连续出栈两个数,然后相加,然后压栈。因此程序"6002600301"实现的操作就是2+3=5,最终堆栈里面保存了数据5。总体来说就是一个循环,先读取一条指令,根据指令类型,继续读取数据或者操作堆栈数据,然后继续循环读取指令,做新的操作,最终执行完整个程序。
4、 问题
a) Solidity不是常用的高级语言,入门门槛高,相关代码和文档也很少
b) 支持图灵完备就面临死循环,递归调用问题,导致复杂度提升,是否有必要
5、 参考文档:
a) 以太坊(三)
b) 比特币脚本
c) 以太坊虚拟机与执行环境概述(英文).pdf
- 以太坊虚拟机实现分析
- [以太坊源代码分析] I.区块和交易,合约和虚拟机
- [以太坊源代码分析] I.区块和交易,合约和虚拟机
- 基于以太坊实现代币发布
- 以太坊
- 【动态】以太坊上海协定—通过Cosmos网络实现以太坊扩容协议
- 以太坊上海协定—通过Cosmos网络实现以太坊扩容协议
- Merkle Patricia Tree (MPT) 以太坊merkle技术分析
- 以太坊区块和交易的存储结构分析
- [以太坊源代码分析] V. 从钱包到客户端
- 以太坊geth同步自动关闭问题分析
- [以太坊源代码分析] V. 从钱包到客户端
- [以太坊源代码分析] IV. 椭圆曲线密码学和以太坊中的椭圆曲线数字签名算法应用
- 以太坊学习
- 以太坊常用网址
- 以太坊的历史
- 以太坊帮助地址
- 以太坊帮助地址
- 【Unity】SQLite在Unity中的使用-思维导图
- /dev/mem可没那么简单
- php协程(Coroutine)学习笔记
- Drools规则引擎(二)-Drools-Example
- Chrome上面按住Shift+鼠标左键双击会直接弹出“检查”代码的窗口
- 以太坊虚拟机实现分析
- js实现上传多个图片或者多个文件
- centos7.2 lamp环境安装搭建(基于阿里云ECS服务器)
- java缓存框架---spring+ehcache整合
- find / -type f -name *fetion* -exec rm {} \;
- Mahout做协同过滤是的ID类型问题
- 单片机之初步了解
- 求1-1000内所有的完数(一个数如果恰好等于它的因子之和,这个数就称为“完数”。如6就是1个完数: 6=1+2+3,因子数就是所有可以整除这个数的数,但是不包括这个数自身.比如15的
- Drools规则引擎(三)-Drools-Use