Ethereum Virtual Machine

简介

以太坊Ethereum) 是一个开源的有智能合约功能的公共区块链平台,通过其专用加密货币以太币(Ether,简称“ETH”)提供去中心化的以太虚拟机(Ethereum Virtual Machine)来处理点对点合约。

以太坊虚拟机(EVM),作用是将智能合约代码编译成可在以太坊上执行的机器码,并提供智能合约的运行环境。它是一个对外完全隔离的沙盒环境,在运行期间不能访问网络、文件,即使不同合约之间也有有限的访问权限。

以太坊底层通过EVM模块支持合约的执行与调用,调用时根据合约地址获取到代码,生成环境后载入到EVM中运行。通常智能合约的开发流程是用solidlity编写逻辑代码,再通过编译器编译元数据,最后再发布到以太坊上。

在这里插入图片描述

尽管区块链是可编程的,但是由于其脚本系统支持的功能有限,基于区块链做应用开发是一件很有难度的事情。而以太坊是基于区块链底层技术进行封装,完善,其中很重要的一个革新就是以太坊虚拟机及面向合约的高级编程语言solidity,这使得开发者可以专注于应用本身,更方便、快捷的开发去中心化应用程序,同时也大大降低了开发难度。

特点

•EVM是图灵完备的
•EVM是一种基于栈的虚拟机(区别于基于寄存器的虚拟机),用于编译、执行智能合约
•EVM是一个完全隔离的环境,在运行期间不能访问网络、文件,即使不同合约之间也有有限的访问权限
•操作数栈调用深度为1024
•机器码长度一个字节,最多可以有256个操作码

图灵完备:
有无限存储能力的通用物理机器或编程语言,简单来说就是可以解决一切可计算的问题

基于栈的虚拟机:
虚拟机需要实现的功能有:
•取指令,其中指令来源于内存
•译码,决定指令类型(执行何种操作)。另外译码的过程要包括从内存中取操作数
•执行。指令译码后,被虚拟机执行(其实最终都会借助于物理机资源)

虚拟机分为两种:基于栈的虚拟机和基于寄存器的虚拟机。基于栈的虚拟机有几个重要的特性:实现简单、可移植,这也是为什么以太坊选择了基于栈的虚拟机。

在基于栈的虚拟机中,有个重要的概念:操作数栈,数据存取为先进先出。所有的操作都是直接与操作数栈直接交互,例如:取数据、存数据、执行操作等。这样有一个好处:可以无视具体的物理机器架构,特别是寄存器,但是缺点也很明显,速度慢,无论什么操作都需要经过操作数栈。

举个简单的例子来说明基于栈的虚拟机是如何执行操作的,例如我们需要执行a = b + c的运算,那么在基于栈的虚拟机上会编译生成类似于下面的字节码指令:

I2: LOAD b
I1: LOAD c
I3: ADD
I4: STORE a

其具体的执行流程为:
1.从内存中加载变量b到操作数栈
2.从内存中加载变量c到操作数栈
3.从操作数栈弹出前两个元素,执行累加
4.将计算后的数值压入栈顶
5.将栈顶的数值取出放入内存中

原理与实现

代码结构


.
├── analysis.go            //跳转目标判定
├── common.go
├── contract.go            //合约数据结构
├── contracts.go           //预编译好的合约
├── errors.go
├── evm.go                 //执行器 对外提供一些外部接口   
├── gas.go                 //call gas花费计算 一级指令耗费gas级别
├── gas_table.go           //指令耗费计算函数表
├── gen_structlog.go       
├── instructions.go        //指令操作
├── interface.go           
├── interpreter.go         //解释器 调用核心
├── intpool.go             //int值池
├── int_pool_verifier_empty.go
├── int_pool_verifier.go
├── jump_table.go           //指令和指令操作(操作,花费,验证)对应表
├── logger.go               //状态日志
├── memory.go               //EVM 内存
├── memory_table.go         //EVM 内存操作表 主要衡量操作所需内存大小
├── noop.go
├── opcodes.go              //Op指令 以及一些对应关系     
├── runtime
│   ├── env.go              //执行环境 
│   ├── fuzz.go
│   └── runtime.go          //运行接口 测试使用
├── stack.go                //栈
└── stack_table.go          //栈验证

指令
OpCode
文件opcodes.go中定义了所有的OpCode,该值是一个byte,合约编译出来的bytecode中,一个OpCode就是上面的一位。opcodes按功能分为9组(运算相关,块操作,加密相关等)。

//算数相关
    const (
        // 0x0 range - arithmetic ops
        STOP OpCode = iota
        ADD
        MUL
        SUB
        DIV
        SDIV
        MOD
        SMOD
        ADDMOD
        MULMOD
        EXP
        SIGNEXTEND
    )

Instruction
文件jump.table.go定义了四种指令集合,每个集合实质上是个256长度的数组,名字翻译过来是(荒地,农庄,拜占庭,君士坦丁堡)估计是对应了EVM的四个发展阶段。指令集向前兼容。

frontierInstructionSet       = NewFrontierInstructionSet()
homesteadInstructionSet      = NewHomesteadInstructionSet()
byzantiumInstructionSet      = NewByzantiumInstructionSet()
constantinopleInstructionSet = NewConstantinopleInstructionSet()

具体每条指令结构如下:

type operation struct {
   
	//对应的操作函数
	execute executionFunc
	// 操作对应的gas消耗
	gasCost gasFunc
	// 栈深度验证
	validateStack stackValidationFunc
	// 操作所需空间
	memorySize memorySizeFunc

	halts   bool // 运算中止
	jumps   bool // 跳转(for)
	writes  bool // 是否写入
	valid   bool // 操作是否有效
	reverts bool // 出错回滚
	returns bool // 返回
}

按下面的ADD指令为例:

ADD: {
   
        execute:       opAdd,
        gasCost:       constGasFunc(GasFastestStep),
        validateStack: makeStackFunc(2, 1),
        valid:         true,
    },

操作
不同的操作有所不同,操作对象根据指令不同可能影响栈,内存,statedb。

func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
   
        //弹出一个值,取出一个值(这个值依旧保存在栈上面,运算结束后这个值就改变成结果值)
        x, y := stack.pop(), stack.peek()
        //加运算
        math.U256(y.Add(x, y))
        //数值缓存
        evm.interpreter.intPool.put(x)
        return nil, nil
    }

gas花费
不同的运算有不同的初始值和对应的运算方法,具体的方法都定义在gas_table里面。 按加法的为例,一次加操作固定耗费为3。

//固定耗费
    func constGasFunc(gas uint64) gasFunc {
   
        return func(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
   
            return gas, nil
        }
    }

除此之外还有两个定义会影响gas的计算,通常作为量化的一个单位。

//file go-ethereum/core/vm/gas.go
    const (
        GasQuickStep   uint64 = 2
        GasFastestStep uint64 = 3
        GasFastStep    uint64 = 5
        GasMidStep     uint64 = 8
        GasSlowStep    uint64 = 10
        GasExtStep     uint64 = 20

        GasReturn       uint64 = 0
        GasStop         uint64 = 0
        GasContractByte uint64 = 200
    )

    //file go-ethereum/params/gas_table.go
    type GasTable struct {
   
        ExtcodeSize uint64
        ExtcodeCopy uint64
        Balance     uint64
        SLoad       uint64
        Calls       uint64
        Suicide     uint64

        ExpByte uint64

        // CreateBySuicide occurs when the
        // refunded account is one that does
        // not exist. This logic is similar
        // to call. May be left nil. Nil means
        // not charged.
        CreateBySuicide uint64
    }

memorySize
因为加操作不需要申请内存因而memorySize为默认值0。

栈验证
先验证栈上的操作数够不够,再验证栈是否超出最大限制,加法在这里仅需验证其参数够不够,运算之后栈是要减一的。

func makeStackFunc(pop, push int) stackValidationFunc {
   
        return func(stack *Stack) error {
   
            //深度验证
            if err := stack.require(pop); err != nil {
   
                return err
            }
            //最大值验证
            //StackLimit       uint64 = 1024 
            if stack.len()+push-pop > int(params.StackLimit) {
   
                return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit)
            }
            return nil
        }
    }

智能合约
合约是EVM智能合约的存储单位也是解释器执行的基本单位,包含了代码,调用人,所有人,gas相关的信息。

type Contract struct {
   
        // CallerAddress is the result of the caller which initialised this
        // contract. However when the "call method" is delegated this value
        // needs to be initialised to that of the caller's caller.
        CallerAddress common.Address
        caller        ContractRef
        self          ContractRef

        jumpdests destinations // result of JUMPDEST analysis.

        Code     []byte
        CodeHash common.Hash
        CodeAddr *common.Address
        Input    []byte

        Gas   uint64
        value *big.Int

        Args []byte

        DelegateCall bool
    }

EVM原生预编译了一批合约,定义在contracts.go里面。主要用于加密操作。

var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
   
	common.BytesToAddress([]byte{
   1<
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值