以太坊源码解读(21)EVM解释器代码分析

之前我们说到EVM解释器是面对Contract对象的,不论是Contract的创建还是调用,都会通过run()函数来调用Interpreter的Run()方法。该方法初始化执行过程中所需要的一些变量,然后进入堆栈操作的主循环。

一、Interpreter.Run()

a. 初始化执行循环中的中间变量

if in.intPool == nil {
    in.intPool = poolOfIntPools.get()
    defer func() {
        poolOfIntPools.put(in.intPool)
        in.intPool = nil
    }()
}

// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()

// 将readOnly设置为true,如果调用方法是静态调用,则设置为只读,一旦不是出现写操作,则退出
if readOnly && !in.readOnly {
    in.readOnly = true
    defer func() { in.readOnly = false }()
}

// 清空之前Call调用的结果
in.returnData = nil

// 如果合约代码为空则直接退出
if len(contract.Code) == 0 {
    return nil, nil
}

var (
    op    OpCode        // 当前的指令集
    mem   = NewMemory() // 新建内存
    stack = newstack()  // 新建堆栈
    pc   = uint64(0) // program counter
    cost uint64

    pcCopy  uint64 // needed for the deferred Tracer
    gasCopy uint64 // for Tracer to log gas remaining before execution
    logged  bool   // deferred Tracer should ignore already logged steps
)
// 设置合约输入参数
contract.Input = input

// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()

b. 进入主循环
1-根据pc获取一条指令
2-检查堆栈上的参数 是否服符合指令函数的要求
3-如果当前解释器是只读的,如果当前指令是写指令,可能导致世界状态改变,直接退出循环
4-计算指令所需要的内存大小
5-获取这个指令需要gas消耗,然后从交易余额中扣除当前指令的消耗,如果余额不足,直接返回
6-内存大小调整到合适大小
7-执行指令
8-处理指令的返回值

for atomic.LoadInt32(&in.evm.abort) == 0 {
    // 第一步:根据pc获取一条指令
    op = contract.GetOp(pc)
    // 第二步:根据指令从JumpTable中获得操作码
    operation := in.cfg.JumpTable[op]
    // 检查-1:操作码是否有效
    if !operation.valid {
        return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
    }
    // 检查-2:检查堆栈上的参数是否符合指令函数的要求
    if err := operation.validateStack(stack); err != nil {
        return nil, err
    }
    // 第三步:如果当前解释器是只读的,若碰到写指令,则直接退出
    if err := in.enforceRestrictions(op, operation, stack); err != nil {
        return nil, err
    }

    var memorySize uint64
    // 第四步:计算指令需要的内存大小
    if operation.memorySize != nil {
        // 先判断内存大小是否足够
        memSize, overflow := bigUint64(operation.memorySize(stack))
        if overflow {
            return nil, errGasUintOverflow
        }
        // 如果足够则以32 bytes为单位进行内存扩充,并计算gas
        if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
            return nil, errGasUintOverflow
        }
    }
    // 第五步:获取指令所需要的gas,然后从交易余额中扣除,如果余额不足,直接返回
    cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
    if err != nil || !contract.UseGas(cost) {
        return nil, ErrOutOfGas
    }
    // 第六步:重新调整刚才获得的SafeSize的内存大小
    if memorySize > 0 {
        mem.Resize(memorySize)
    }
    // 第七步:重要!!!!执行指令!!!!!
    res, err := operation.execute(&pc, in, contract, mem, stack)
    // 检查intPool的完整性
    if verifyPool {
        verifyIntegerPool(in.intPool)
    }
    // 如果指令定义returns为true,将返回值复制给解释器的returnData成员
    if operation.returns {
        in.returnData = res
    }
    // 第八步:处理返回值
    switch {
    case err != nil:  // 返回指令执行错误,直接返回
        return nil, err
    case operation.reverts:9  // revert指令返回
        return res, errExecutionReverted
    case operation.halts:  // 如果指令为终止指令,直接退出(STOP,RETURN,SELFDESTRUCT)
        return res, nil
    case !operation.jumps:  // 如果不是跳转指令,pc自增,进行下一条指令运行,重新循环
        pc++
    }
}

总体来说,解释器执行循环的过程如下图:

二、EVM指令与操作

我们先看下EVM模块的代码结构:

evm.go	        定义了EVM运行环境结构体,并实现 转账处理 这些比较高级的,跟交易本身有关的功能
vm/evm.go	定义了EVM结构体,提供Create和Call方法,作为虚拟机的入口,分别对应创建合约和执行合约代码
vm/interpreter.go   虚拟机的调度器,开始真正的解析执行合约代码
vm/opcodes.go	    定义了虚拟机指令码(操作码)
vm/instructions.go  绝大部分的操作码对应的实现都在这里
vm/gas_table.go	    绝大部分操作码所需的gas都在这里计算
vm/jump_table.go    定义了operation,就是将opcode和gas计算函数、具体实现函数等关联起来
vm/stack.go	evm所需要的栈
vm/memory.go	evm的内存结构
vm/intpool.go	*big.Int的池子,主要是性能上的考虑,跟业务逻辑无关

从上图来看,opcodes中储存的是所有指令码,比如ADD的指令码就是0x01。jump_table定义了每一个指令对应的指令码、gas花费;instructions中是所有的指令执行函数的实现,通过这些函数来对堆栈stack进行操作,比如pop()、push()等。

当一个contract对象传入interpreter模块,首先调用了contract的GetOp(n)方法,其内部调用了GetByte(n)方法,从而从Contract对象的Code中拿到n对应的指令。参数n就是我们上面再Run()函数中定义的pc,是一个程序的计数器。每次指令执行后都会让pc++,从而调用下一个指令,除非指令执行到最后是退出函数,比如return、stop或selfDestruct。

// GetOp returns the n'th element in the contract's byte array
func (c *Contract) GetOp(n uint64) OpCode {
	return OpCode(c.GetByte(n))
}

// GetByte returns the n'th byte in the contract's byte array
func (c *Contract) GetByte(n uint64) byte {
	if n < uint64(len(c.Code)) {
		return c.Code[n]
	}
	return 0
}

三、基于堆栈的虚拟机

虚拟机实际上是从软件层面对物理机器的模拟,但以太坊虚拟机相对于我们日常常见到的狭义的虚拟机如vmware或者v-box不同,仅仅是为了模拟对字节码的取指令、译码、执行和结果储存返回等操作,这些步骤跟真实物理机器上的概念都很类似。当然,不管虚拟机怎么实现,最终都还是要依靠物理资源。

如今虚拟机的实现方式有两种,一种就是基于栈的,另一种是基于寄存器的。基于栈的虚拟机有JVM,CPython等,而基于寄存器的有Dalvik以及Lua5.0。这两种实现方式虽然机制不同,但最终都要实现:
1、从内存中取指令;
2、译码,将指令转义成特定的操作;
3、执行,也就是在栈或者寄存器中进行计算;
4、返回计算结果。

我们这里简单通过一张图回顾上面那个ADD指令的执行,了解一下基于栈的计算如何执行,以便我们能对以太坊EVM的原理有很深的理解。

加入我们栈上先PUSH了3和4在栈顶,现在当收到ADD指令时,调用opAdd()函数。先执行x=stack.pop(),将栈顶的3取出并赋值给x,删除栈顶的3,然后执行y = stack.peek(),取出此时栈顶的4但是不删除。然后执行y.Add(x,y)得到y==7,再讲7压如栈顶。

当然,上述过程任然是抽象的,是经过译码后的内容。原代码是[]bytes类型的数据,这里就暂时不做介绍了。这涉及到了solidity的原理,后面我会抽个时间总结一下,solidty如何将合约代码转成字节码,而字节码又是如何在EVM中进行译码的。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
国密即国家密码局认定的国产密码算法,即商用密码。国密算法是国家密码局制定标准的一系列算法。其中包括了对称加密算法,椭圆曲线非对称加密算法,杂凑算法。具体包括SM1,SM2,SM3等,其中:SM2为国家密码管理局公布的公钥算法,其加密强度为256位。其它几个重要的商用密码算法包括:SM1,对称加密算法,加密强度为128位,采用硬件实现;SM3,密码杂凑算法,杂凑值长度为32字节,和SM2算法同期公布,参见《国家密码管理局公告(第 22 号)》;SMS4,对称加密算法,随WAPI标准一起公布,可使用软件实现,加密强度为128位。商用密码,是指能够实现商用密码算法的加密、解密和认证等功能的技术。(包括密码算法编程技术和密码算法芯片、加密卡等的实现技术)。商用密码技术是商用密码的核心,国家将商用密码技术列入国家秘密,任何单位和个人都有责任和义务保护商用密码技术的秘密。商用密码的应用领域十分广泛,主要用于对不涉及国家秘密内容但又具有敏感性的内部信息、行政事务信息、经济信息等进行加密保护。比如:商用密码可用于企业门禁管理、企业内部的各类敏感信息的传输加密、存储加密,防止非法第三方获取信息内容;也可用于各种安全认证、网上银行、数字签名等。例如:在门禁应用中,采用SM1算法进行身份鉴别和数据加密通讯,实现卡片合法性的验证,保证身份识别的真实性。 安全是关系国家、城市信息、行业用户、百姓利益的关键问题。国家密码管理局针对现有重要门禁系统建设和升级改造应用也提出指导意见,加强芯片、卡片、系统的标准化建设。截止目前,国密门禁系统的升级的案例也逐渐增多,基于自主国产知识产权的CPU卡、CPU卡读写设备及密钥管理系统广泛受到关注。一些厂商如同方锐安在2009年推出CPU卡安全门禁系列产品,在2010年北京安博会上,该公司再次向业界展示出“御”系列CPU卡门禁系统、TF-DF6000系列安全门禁读卡器以及基于CPU卡技术的一卡通系统等主流产品和系统。这些厂商是全国推广的国密门禁产品的先驱者,使“御”系列CPU卡门禁系统广泛应用于政府、监狱、司法、军工企业和大型公共智能建筑等高安全领域。以太坊是互联网新时代的基础:内建货币与支付。用户拥有个人数据主权,且不会被各类应用监听或窃取数据。人人都有权使用开放金融系统。基于中立且开源的基础架构,不受任何组织或个人控制。以太坊主网于 2015 年上线,是世界头部的可编程区块链。和其它区块链一样,以太坊也拥有原生加密货币,叫作 ether (ETH)。 ETH 是一种数字货币, 和比特币有许多相同的功能。 它是一种纯数字货币,可以即时发送给世界上任何地方的任何人。 ETH 的供应不受任何政府或组织控制,它是去中心化且具稀缺性的。 全世界的人们都在使用 ETH 进行支付,或将其作为价值存储和抵押品。但与其它区块链不同的是,以太坊可以做更多的工作。 以太坊是可编程的,开发者可以用它来构建不同于以往的应用程序。这些去中心化的应用程序(或称“dapps”)基于加密货币与区块链技术, 因而值得信任,也就是说 dapps 一旦被“上传”到以太坊,它们将始终按照编好的程序运行。 这些应用程序可以控制数字资产,以便创造新的金融应用; 同时还是去中心化的,这意味着没有任何单一实体或个人可以控制它们。目前,全世界有成千上万名开发者正在以太坊上构建应用程序、发明新的应用程序,其中有许多现在已经可以使用:加密货币钱包:让你可以使用 ETH 或其他数字资产进行低成本的即时支付金融应用程序:让你可以借贷、投资数字资产去中心化市场:让你可以交易数字资产,甚至就现实世界事件的“预测”进行交易游戏:你可以拥有游戏内的资产,甚至可以由此获得现实收益以及更多,更多。以太坊社区是世界上最大最活跃的区块链社区。它包括核心协议开发者、加密经济研究员、密码朋克、挖矿组织、ETH 持有者、应用开发者、普通用户、无政府主义者、财富 500 强公司,以及现在的你。没有公司或中心化的组织能够控制以太坊。 一直以来,以太坊由多元化的全球性社区贡献者来协同进行维护和改善,社区成员耕耘于以太坊的方方面面,从核心协议到应用程序。 这个网站,就像以太坊的其他部分一样,是由一群人共同构建的,并将持续构建下去。本课程定制符合国家标准的以太坊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值