Pre:
每处理一笔交易,就要新建一个 EVM对象
来处理交易。
EVM 对象内部主要依赖三个对象:
- 解释器
Interpreter
- 虚拟机相关配置对象
vm.Config
- 以太坊状态数据库
StateDB
这次先看解释器对象
的源码
EVM解释器对象:
解释器对象EVMInterpreter
用来解释执行指定的合约指令。
不过需要说明一点的是,实际的指令解释执行并不真正由解释器对象完成的,而是由 vm.Config.JumpTable
中的 operation 对象完成的,
解释器对象只是负责逐条解析指令码,然后获取相应的 operation
对象,并在调用真正解释指令的 operation.execute
函数之前检查堆栈等对象。
也可以说,解释器对象只是负责解释的调度工作。
创建EVM解释器对象:
NewEVMInterpreter()
函数的主要操作:
-
根据不同的以太坊版本,使用不同对象填充
cfg.JumpTable
字段 -
填充
cfg.ExtraEips
字段 -
生成一个
EVMInterpreter
对象并返回
1 | // NewEVMInterpreter returns a new instance of the Interpreter. |
EVMInterpreter
关键方法是 Run
方法
Interpreter.Run():
初始化执行循环中的中间变量:
1 | // Increment the call depth which is restricted to 1024 |
进入主循环:
-
根据
pc
获取一条指令 -
根据指令从
JumpTable
中获得操作码 -
检查堆栈上的参数 是否服符合操作码函数的要求
-
计算指令所需要的内存大小
-
获取这个指令需要gas消耗,然后从交易余额中扣除当前指令的消耗,如果余额不足,直接返回
ErrOutOfGas
-
计算新的内存大小以动态调整内存大小,必要时进行扩容(按32字节)
-
所有使用动态内存的操作码都有动态的gas成本,扣除动态gas成本,如果不够,就返回
ErrOutOfGas
错误 -
执行操作指令
-
处理操作指令的返回值
1 | // The Interpreter main run loop (contextual). This loop runs until either an |
总体来说,解释器执行循环的过程如下图:
EVM指令与操作:
我们先看下EVM模块的代码结构:
1 | evm.go // 定义了EVM运行环境结构体,并实现 转账处理 这些比较高级的,跟交易本身有关的功能 |
从上图来看:
-
opcodes
中储存的是所有指令码,比如ADD
的指令码就是0x01 -
jump_table
定义了每一个指令对应的指令码、gas花费 -
instructions
中是所有的指令执行函数的实现,通过这些函数来对堆栈stack
进行操作,比如pop()
、push()
等。
当一个contract对象传入interpreter
模块,首先调用了contract的GetOp(n)
方法,从Contract对象的Code中拿到n
对应的指令。
参数n就是我们上面在Run()
函数中定义的pc,是一个程序的计数器。
每次指令执行后都会让pc++
,从而调用下一个指令,除非指令执行到最后是退出函数,比如return
、stop
或selfDestruct
。
1 | // GetOp returns the n'th element in the contract's byte array |
基于堆栈的虚拟机:
虚拟机实际上是从软件层面对物理机器的模拟,但以太坊虚拟机相对于我们日常常见到的狭义的虚拟机如vmware或者v-box不同,
仅仅是为了模拟对字节码的取指令、译码、执行和结果储存返回等操作,这些步骤跟真实物理机器上的概念都很类似。
当然,不管虚拟机怎么实现,最终都还是要依靠物理资源。
如今虚拟机的实现方式有两种,一种就是基于栈的,另一种是基于寄存器的。
基于栈的虚拟机有JVM
,CPython
等,而基于寄存器的有Dalvik
以及Lua5.0
。
这两种实现方式虽然机制不同,但最终都要实现:
-
从内存中取指令;
-
译码,将指令转义成特定的操作;
-
执行,也就是在栈或者寄存器中进行计算;
-
返回计算结果。
我们这里简单通过一张图回顾上面那个ADD指令的执行,了解一下基于栈的计算如何执行,以便我们能对以太坊EVM的原理有更深的理解。
我们栈上先PUSH了3和4在栈顶,现在当收到ADD指令时,调用opAdd()
函数。
先执行x = stack.pop()
,将栈顶的3取出并赋值给x,删除栈顶的3,
然后执行y = stack.peek()
,取出此时栈顶的4但是不删除。
然后执行y.Add(x,y)
得到y==7,再讲7压如栈顶。