以太坊EVM源码注释之数据结构

本文详细介绍了以太坊EVM( Ethereum Virtual Machine )的相关数据结构,包括EVM的整体结构、合约的构造函数与方法、EVM的构造函数、Context、StateDB以及Input数据结构等。重点讲解了函数选择子、参数编码、栈、内存和intPool的工作原理,同时提到了合约在EVM中的执行过程以及如何与智能合约交互。
摘要由CSDN通过智能技术生成

以太坊EVM源码分析之数据结构

EVM代码整体结构

EVM相关的源码目录结构:

~/go-ethereum-master/core/vm# tree
.
├── analysis.go                     // 分析合约字节码,标记是否是跳转目标(jumpdest)
├── analysis_test.go
├── common.go                       // 一些公用的方法
├── contract.go                     // 智能合约数据结构
├── contracts.go                    // 预编译合约集
├── contracts_test.go
├── doc.go
├── eips.go                         // 一些EIP的实现
├── errors.go                       // 列出执行时错误
├── evm.go                          // 执行器 提供一些对外接口
├── gas.go                          // call gas花费计算 一级指令耗费gas级别
├── gas_table.go                    // 各个指令对应的计算耗费gas的函数 
├── gas_table_test.go
├── gen_structlog.go
├── instructions.go                 // 指令对应的执行函数
├── instructions_test.go
├── interface.go                    // StateDB接口、EVM调用约定基本接口CallContext
├── interpreter.go                  // 解释器 调用核心
├── intpool.go                      // int值池 用来加速bit.Int的分配。 辅助对象
├── intpool_test.go
├── int_pool_verifier_empty.go
├── int_pool_verifier.go
├── jump_table.go                   // 指令和指令操作(操作,花费,验证)对应表
├── logger.go                       // logger、Tracer  辅助对象
├── logger_json.go
├── logger_test.go
├── memory.go                       // 内存模型及其访问函数
├── memory_table.go                 // EVM 内存操作表 计算指令所需内存大小
├── opcodes.go                      // EVM指令集
├── stack.go                        // 堆栈及其方法
└── stack_table.go                  // 一些栈的辅助函数, minStack、maxStack等

~/go-ethereum-master/core# tree
.
├── evm.go                          // EVM用到的一些函数
├── gaspool.go                      // GasPool实时记录区块在执行交易期间可用的gas量
├── state_processor.go              // 处理状态转移
├── state_transition.go             // 状态转换模型
└── types                           // 一些核心数据结构
    ├── block.go                    // block、blockHeader
    ├── log.go                      // log
    ├── receipt.go                  // receipt
    ├── transaction.go              // transaction、message
    └── transaction_signing.go

evm moudle
这是网上找到的一张EVM模块的整体结构图,有些已经发生变化。到目前为止(2020.02.25),EVM的指令集版本已经有7个了。operation的字段也有一些改动。
core/vm/jump_table.go

// 指令集, 下面定义了7种指令集,针对7种不同的以太坊版本
var (
	frontierInstructionSet         = newFrontierInstructionSet()
	homesteadInstructionSet        = newHomesteadInstructionSet()
	tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet()
	spuriousDragonInstructionSet   = newSpuriousDragonInstructionSet()
	byzantiumInstructionSet        = newByzantiumInstructionSet()
	constantinopleInstructionSet   = newConstantinopleInstructionSet()
	istanbulInstructionSet         = newIstanbulInstructionSet()
)

EVM的代码结构要比想象的简单。EVM涉及的核心对象有解释器Interpreter、解释器的配置选项Config、为EVM提供辅助信息的执行上下文Context以及用于完整状态查询的EVM数据库stateDB
从上图可以看出,EVM通过解释器运行智能合约,而解释器依赖于config的核心结构:JumpTable [256]operationJumpTable的下标是操作码,JumpTable[opCode]对应的operation对象存储了指令对应的处理逻辑, gas计算函数, 堆栈验证方法, memory使用的大小以及一些flag。
以太坊的不同版本对应着不同的JumpTable,只有frontierInstructionSet的初始化函数中初始化了基本指令的operation对象,之后的版本都是对前一个版本的修修补补:首先生成前一个版本的指令,然后应用一些EIP,增加自己特有的指令,或者改动某些指令。例如最新的Istanbul版本:
core/vm/jump_table.go

// newIstanbulInstructionSet returns the frontier, homestead
// byzantium, contantinople and petersburg instructions.
// 先初始化前一个版本Constantinople的指令集,然后应用一些EIP.
func newIstanbulInstructionSet() JumpTable {
   
	instructionSet := newConstantinopleInstructionSet()

	enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344
	enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
	enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200

	return instructionSet
}

Contract

EVM是智能合约的运行时环境,因此我们有必要了解一下合约的结构以及比较重要的方法。
core/vm/contract.go

// ContractRef is a reference to the contract's backing object
// Contrtref是对背后的合约对象的引用
type ContractRef interface {
   
	Address() common.Address // Address方法返回合约地址
}

// Contract represents an ethereum contract in the state database. It contains
// the contract code, calling arguments. Contract implements ContractRef
// Contract在状态数据库中表示一个以太坊合约。它包含合约代码,调用参数。
// Contract 实现 ContractRef接口
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是初始化此合约的调用者的结果。
	// 然而,当“调用方法”被委托时,需要将此值初始化为调用者的调用者的地址。
	CallerAddress common.Address
	caller        ContractRef // 调用者
	self          ContractRef // 合约自身

    // JUMPDEST分析结果聚合。
    // 实际是合约字节码对应字节是指令还是普通数据的分析结果,若是指令,则可以作为jumpdest。
	jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
	// 本地保存本合约的代码JUMPDEST分析结果,不保存在调用者上下文中
	analysis bitvec // Locally cached result of JUMPDEST analysis

	Code     []byte          // 代码
	CodeHash common.Hash     // 代码hash
	CodeAddr *common.Address // 代码地址
	Input    []byte          // 合约输入的参数

	Gas   uint64   // Gas数量
	value *big.Int // 携带的数据,如交易的数额
}

构造函数

core/vm/contract.go

// NewContract returns a new contract environment for the execution of EVM.
// NewContract 为EVM的执行返回一个新的合约环境
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
   
	// 初始化Contract对象
	c := &Contract{
   CallerAddress: caller.Address(), caller: caller, self: object}

	// 将ContractRef接口类转换为Contarct具体类型,当成功标志为真时,
	// 表示成功将接口转换为具体类型,否则表示该接口不是具体类型的实例。
	if parent, ok := caller.(*Contract); ok {
   
		// Reuse JUMPDEST analysis from parent context if available.
		// 重用调用者上下文中的JUMPDEST
		c.jumpdests = parent.jumpdests
	} else {
   
		// 初始化新的jumpdests
		c.jumpdests = make(map[common.Hash]bitvec)
	}

	// Gas should be a pointer so it can safely be reduced through the run
	// Gas应为一个指针,这样它可以通过run方法安全地减少
	// This pointer will be off the state transition
	// 这个指针将脱离状态转换
	c.Gas = gas
	// ensures a value is set
	// 确保value被设置
	c.value = value

	return c //返回合约指针
}

方法

core/vm/contract.go

// Contract结构方法
// 判断跳转目标是否有效
func (c *Contract) validJumpdest(dest *big.Int) bool {
   
	udest := dest.Uint64() //将目标转换为Uint64类型
	// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
	// PC大小不能超过代码长度,并且位数不大于63位
	// Don't bother checking for JUMPDEST in that case.
	// 在这种情况下,不必检查JUMPDEST。
	if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) {
   
		return false
	}
	// Only JUMPDESTs allowed for destinations
	// 只有JUMPDEST可以成为跳转目标
	if OpCode(c.Code[udest]) != JUMPDEST {
   
		return false
	}

	// 下面的代码检查目的地的值是否是指令,而非普通数据

	// Do we have a contract hash already?
	// 我们已经有一个合约hash了吗?
	if c.CodeHash != (common.Hash{
   }) {
    //若不是空hash
		// Does parent context have the analysis?
		// 调用者上下文是否已经有一个分析,c.jumpdests = parent.jumpdests
		// go 中map 是引用类型,因此在这里c.jumpdests与parent.jumpdests指向同一结构,同一片内存区域
		analysis, exist := c.jumpdests[c.CodeHash] // 查看元素是否存在
		if !exist {
                                   //若不存在
			// Do the analysis and save in parent context
			// 进行分析并保存在调用者上下文中
			// We do not need to store it in c.analysis
			// 不需要在c.analysis存储结果
			analysis = codeBitmap(c.Code)
			//由于Map是引用类型,改变c.jumpdests等同于改变parent.jumpdests,键是各自的代码hash
			c.jumpdests[c.CodeHash] = analysis
		}
		// 检查跳转位置是否在代码段中,并返回结果
		return analysis.codeSegment(udest)
	}
	// We don't have the code hash, most likely a piece of initcode not already
	// in state trie. In that case, we do an analysis, and save it locally, so
	// we don't have to recalculate it for every JUMP instruction in the execution
	// However, we don't save it within the parent context
	// 我们还没有代码hash, 很可能是因为一部分初试代码还没有保存到状态树中。
	// 在那种情况下,我们进行代码分析并局部保存,在执行过程中我们就不必为每条跳转指令重新进行代码分析
	// 然而,我们并没有将分析结果保存在调用者上下文中
	// 一般是因为新合约创建,还未将合约写入状态数据库。
	if c.analysis == nil {
   
		c.analysis = codeBitmap(c.Code)
	}
	return c.analysis.codeSegment(udest)
}

// AsDelegate sets the contract to be a delegate call and returns the current
// contract (for chaining calls)
// AsDelegate将合约设置为委托调用并返回当前合约(用于链式调用)
func (c *Contract) AsDelegate() *Contract {
   
	// NOTE: caller must, at all times be a contract. It should never happen
	// that caller is something other than a Contract.
	// 注:调用者在任何时候都必须是一个合约,不应该是合约以外的东西。
	parent := c.caller.(*Contract)         //调用者的合约对象
	c.CallerAddress = parent.CallerAddress //将调用者地址设置为调用者的调用者的地址
	c.value = parent.value                 //值也是

	return c //返回当前合约
}

Contract在EVM中的使用

Transaction被转换成Message后传入EVM,在调用EVM.Call或者EVM.Create时,会将Message转换为Contract对象,以便后续执行。转换过程如图所示,合约代码从相应的状态数据库地址获取,然后加载到合约对象中。 message -> Contract

EVM

core/vm/evm.go

// EVM is the Ethereum Virtual Machine base object and provides
// the necessary tools to run a contract on the given state with
// the provided context. It should be noted that any error
// generated through any of the calls should be considered a
// revert-state-and-consume-all-gas operation, no checks on
// specific errors should ever be performed. The interpreter makes
// sure that any errors generated are to be considered faulty code.
// EVM 是以太坊虚拟机的基本对象,并且提供必要的工具,以便在给定的状态下使用提供的上下文运行合约。
// 应该注意的是,通过任何调用产生的任何错误都会导致状态回滚并消耗掉所有gas,
// 不应该执行任何对特定错误的检查。解释器确保产生的任何错误都被认为是错误代码。
//
// The EVM should never be reused and is not thread safe.
// EVM不应该被重用,而且也不是线程安全的。
type EVM struct {
   
	// Context provides auxiliary blockchain related information
	// Context提供区块链相关的辅助信息 提供访问当前区块链数据和挖矿环境的函数和数据
	Context
	// StateDB gives access to the underlying state
	// StateDB 以太坊状态数据库对象 提供对底层状态的访问
	StateDB StateDB
	// Depth is the current call stack
	// Depth 是当前调用堆栈
	depth int

	// ch
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值