天天看點

以太坊EVM源碼注釋之資料結構以太坊EVM源碼分析之資料結構

以太坊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源碼注釋之資料結構以太坊EVM源碼分析之資料結構

這是網上找到的一張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]operation

JumpTable

的下标是操作碼,

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

對象,以便後續執行。轉換過程如圖所示,合約代碼從相應的狀态資料庫位址擷取,然後加載到合約對象中。

以太坊EVM源碼注釋之資料結構以太坊EVM源碼分析之資料結構

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

	// chainConfig contains information about the current chain
	// chainConfig包括目前鍊的配置資訊 目前節點的區塊鍊配置資訊
	chainConfig *params.ChainConfig
	// chain rules contains the chain rules for the current epoch
	// chainRules包含目前階段的鍊規則
	chainRules params.Rules
	// virtual machine configuration options used to initialise the
	// evm.
	// vmConfig 是用于初始化evm的虛拟機配置選項。 虛拟機配置資訊
	vmConfig Config
	// global (to this context) ethereum virtual machine
	// used throughout the execution of the tx.
	// 交易執行所采用的全局(對于這個上下文來說)以太坊虛拟機
	interpreters []Interpreter
	interpreter  Interpreter
	// abort is used to abort the EVM calling operations
	// NOTE: must be set atomically
	// abort用來終止EVM的調用操作
	// 注意:設定時必須是原子操作
	abort int32
	// callGasTemp holds the gas available for the current call. This is needed because the
	// available gas is calculated in gasCall* according to the 63/64 rule and later
	// applied in opCall*.
	// callGasTemp 儲存目前調用可用的gas。這是必要的,因為可用的gas是根據63/64規則在gasCall*中計算的,之後應用在opCall*中。
	// 
	// 除去父合約在記憶體等方面花去的雜七雜八的gas成本,實際用于執行子合約的gas。也就是子合約可以使用的gas數量。
	callGasTemp uint64
}
           

構造函數

core/vm/evm.go

// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
// NewEVM是EVM的構造函數。傳回的EVM不是線程安全的,應該隻使用*一次*。
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
	// 初始化字段
	evm := &EVM{
		Context:      ctx,
		StateDB:      statedb,
		vmConfig:     vmConfig,
		chainConfig:  chainConfig,
		chainRules:   chainConfig.Rules(ctx.BlockNumber),
		interpreters: make([]Interpreter, 0, 1), // 長度0,容量1
	}

	if chainConfig.IsEWASM(ctx.BlockNumber) {
		// to be implemented by EVM-C and Wagon PRs.
		// 由EVM-C和Wagon PRs實作。
		// 注釋代碼主要是向解釋器集合添加新的解釋器, EVMVCInterpreter 或者 EWASMInterpreter
		// if vmConfig.EWASMInterpreter != "" {
		//  extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
		//  path := extIntOpts[0]
		//  options := []string{}
		//  if len(extIntOpts) > 1 {
		//    options = extIntOpts[1..]
		//  }
		//  evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
		// } else {
		// 	evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
		// }
		panic("No supported ewasm interpreter yet.")
	}

	// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
	// as we always want to have the built-in EVM as the failover option.
	// vmConfig.EVMInterpreter将被EVM-C使用, 這裡不會選中它,因為我們總是希望将内置的EVM作為故障轉移(失敗備援)選項。
	evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
	// 到目前為止,函數隻為interpreters添加了一個解釋器。目前源代碼中隻有一個版本的解釋器EVMInterpreter
	evm.interpreter = evm.interpreters[0]

	return evm
}
           

此函數建立一個新的虛拟機對象,将EVM字段初始化,然後調用

NewEVMInterpreter

建立解釋器對象,添加解釋器,目前隻有一個版本的解釋器

EVMInterperter

,注釋代碼中描述了下一代解釋器

ewasm interpreter

的添加過程。注意,此函數參數

vmConfig

并未填充

vmConfig.JumpTable

,此結構在

NewEVMInterperter

中進行填充。

Context

core/vm/evm.go

// Context provides the EVM with auxiliary information. Once provided
// it shouldn't be modified.
// Context為EVM提供輔助資訊。一旦提供不能被修改
type Context struct {
	// CanTransfer returns whether the account contains
	// sufficient ether to transfer the value
	// CanTransfer傳回賬戶是否擁有足夠的ether進行交易
	CanTransfer CanTransferFunc
	// Transfer transfers ether from one account to the other
	// Transfer把ether從一個賬戶轉移到另一個賬戶
	Transfer TransferFunc
	// GetHash returns the hash corresponding to n
	// GetHash傳回區塊鍊中第n個塊的hash
	GetHash GetHashFunc

	// Message information
	// 提供發起者資訊 sender的位址
	Origin common.Address // Provides information for ORIGIN
	// gas價格
	GasPrice *big.Int // Provides information for GASPRICE

	// Block information

	// 受益人,一般是礦工位址
	Coinbase common.Address // Provides information for COINBASE
	// 區塊所能消耗的gas限制,可由礦工投票調整
	GasLimit uint64 // Provides information for GASLIMIT
	// 區塊号
	BlockNumber *big.Int // Provides information for NUMBER
	// 時間
	Time *big.Int // Provides information for TIME
	// 難度,目前挖礦要解決的難題難度
	Difficulty *big.Int // Provides information for DIFFICULTY
}
           

構造函數

找到該交易的打包者,然後将各個字段填充。

core/evm.go

// NewEVMContext creates a new context for use in the EVM.
// NewEVMContext建立一個用于EVM的新上下文。
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
	// If we don't have an explicit author (i.e. not mining), extract from the header
	// 如果我們沒有一個明确的作者,從塊頭提取
	var beneficiary common.Address
	if author == nil {
		// 忽略錯誤,我們已經通過了頭部的有效性驗證
		beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation
	} else {
		beneficiary = *author
	}
	return vm.Context{
		CanTransfer: CanTransfer,
		Transfer:    Transfer,
		GetHash:     GetHashFn(header, chain),
		Origin:      msg.From(),
		Coinbase:    beneficiary,
		BlockNumber: new(big.Int).Set(header.Number),
		Time:        new(big.Int).SetUint64(header.Time),
		Difficulty:  new(big.Int).Set(header.Difficulty),
		GasLimit:    header.GasLimit,
		GasPrice:    new(big.Int).Set(msg.GasPrice()),
	}
}
           

StateDB

core/vm/interface.go

// StateDB is an EVM database for full state querying.
// StateDB是一個用于完整狀态查詢的EVM資料庫。
type StateDB interface {
	CreateAccount(common.Address)

	SubBalance(common.Address, *big.Int)
	AddBalance(common.Address, *big.Int)
	GetBalance(common.Address) *big.Int

	GetNonce(common.Address) uint64
	SetNonce(common.Address, uint64)

	GetCodeHash(common.Address) common.Hash
	GetCode(common.Address) []byte
	SetCode(common.Address, []byte)
	GetCodeSize(common.Address) int

	AddRefund(uint64)
	SubRefund(uint64)
	GetRefund() uint64

	GetCommittedState(common.Address, common.Hash) common.Hash
	GetState(common.Address, common.Hash) common.Hash
	SetState(common.Address, common.Hash, common.Hash)

	Suicide(common.Address) bool
	HasSuicided(common.Address) bool

	// Exist reports whether the given account exists in state.
	// Notably this should also return true for suicided accounts.
	// Exist報告在狀态中是否存在給定帳戶。
	// 值得注意的是已經自毀的賬号也傳回true。
	Exist(common.Address) bool
	// Empty returns whether the given account is empty. Empty
	// is defined according to EIP161 (balance = nonce = code = 0).
	// Empty傳回給定帳戶是否為空。
	// 空的概念根據EIP161定義(balance = nonce = code = 0)
	Empty(common.Address) bool

	RevertToSnapshot(int)
	Snapshot() int

	AddLog(*types.Log)
	AddPreimage(common.Hash, []byte)

	ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
}
           

core/state/statedb.go

// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
// stateDB用來存儲以太坊中關于merkle trie的所有内容。 StateDB負責緩存和存儲嵌套狀态。
// 這是檢索合約和賬戶的一般查詢界面:
type StateDB struct {
	db   Database // 後端的資料庫
	trie Trie     // 樹 main account trie

	// This map holds 'live' objects, which will get modified while processing a state transition.
	// 下面的Map用來存儲目前活動的對象,這些對象在狀态轉換的時候會被修改。
	stateObjects map[common.Address]*stateObject
	// State objects finalized but not yet written to the trie 已完成修改的狀态對象(state object),但尚未寫入trie
	stateObjectsPending map[common.Address]struct{}
	// State objects modified in the current execution 在目前執行過程中修改的狀态對象(state object)
	stateObjectsDirty map[common.Address]struct{}

	// DB error. 資料庫錯誤
	// State objects are used by the consensus core and VM which are
	// unable to deal with database-level errors. Any error that occurs
	// during a database read is memoized here and will eventually be returned
	// by StateDB.Commit.
	// stateObject會被共識算法的核心和VM使用,在這些代碼内部無法處理資料庫級别的錯誤。
	// 在資料庫讀取期間發生的任何錯誤都會記錄在這裡,最終由StateDB.Commit傳回。
	dbErr error

	// The refund counter, also used by state transitioning.
	// 退款計數器,用于狀态轉換
	refund uint64

	thash, bhash common.Hash                  // 目前的transaction hash 和block hash
	txIndex      int                          // 目前的交易的index
	logs         map[common.Hash][]*types.Log // 日志 key是交易的hash值
	logSize      uint                         // 日志大小

	preimages map[common.Hash][]byte // SHA3的原始byte[], EVM計算的 SHA3->byte[]的映射關系

	// Journal of state modifications. This is the backbone of
	// Snapshot and RevertToSnapshot.
	// 狀态修改日志。這是快照和復原到快照的支柱。
	journal        *journal
	validRevisions []revision
	nextRevisionId int

	// Measurements gathered during execution for debugging purposes
	// 為調試目的而在執行期間收集的度量
	AccountReads   time.Duration
	AccountHashes  time.Duration
	AccountUpdates time.Duration
	AccountCommits time.Duration
	StorageReads   time.Duration
	StorageHashes  time.Duration
	StorageUpdates time.Duration
	StorageCommits time.Duration
}
           

構造函數

core/state/statedb.go

// StateDB的構造函數
// 一般的用法 statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))

// Create a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) {
	tr, err := db.OpenTrie(root)
	if err != nil {
		return nil, err
	}
	return &StateDB{
		db:                  db,
		trie:                tr,
		stateObjects:        make(map[common.Address]*stateObject),
		stateObjectsPending: make(map[common.Address]struct{}),
		stateObjectsDirty:   make(map[common.Address]struct{}),
		logs:                make(map[common.Hash][]*types.Log),
		preimages:           make(map[common.Hash][]byte),
		journal:             newJournal(),
	}, nil
}
           

Config

core/vm/interpreter.go

// Config are the configuration options for the Interpreter
// Config是解釋器的配置選項
type Config struct {
	Debug                   bool   // Enables debugging 調試模式
	Tracer                  Tracer // Opcode logger 日志記錄
	NoRecursion             bool   // Disables call, callcode, delegate call and create 禁用Call, callCode, delegate call和create.
	EnablePreimageRecording bool   // Enables recording of SHA3/keccak preimages 記錄SHA3的原象

	// EVM指令表 如果未設定,将自動填充
	// 解釋器每拿到一個準備執行的新指令時,就會從 JumpTable 中擷取指令相關的資訊,即 operation 對象。
	JumpTable [256]operation // EVM instruction table, automatically populated if unset

	EWASMInterpreter string // External EWASM interpreter options 外部EWASM解釋器選項
	EVMInterpreter   string // External EVM interpreter options 外部EVM解釋器選項

	ExtraEips []int // Additional EIPS that are to be enabled 啟用的額外的EIP
}
           

core/vm/gas_table.go

// operation存儲了一條指令的所需要的函數。一個operation對應一條指令。
// operation存儲了指令對應的處理邏輯, gas消耗, 堆棧驗證方法, memory使用的大小等。
type operation struct {
	// execute is the operation function
	// 執行函數,指令處理邏輯
	execute     executionFunc
	constantGas uint64  // 固定gas
	dynamicGas  gasFunc // 指令消耗gas的計算函數
	// minStack tells how many stack items are required
	// minStack 表示需要多少個堆棧項
	minStack int
	// maxStack specifies the max length the stack can have for this operation
	// to not overflow the stack.
	// maxStack指定這個操作不會使堆棧溢出的堆棧最大長度。
	// 也就是說,隻要堆棧不超過這個最大長度,這個操作就不會導緻棧溢出。
	maxStack int

	// memorySize returns the memory size required for the operation
	// 指令需要的記憶體大小
	memorySize memorySizeFunc

	// 表示操作是否停止進一步執行。指令執行完成後是否停止解釋器的執行。
	halts bool // indicates whether the operation should halt further execution
	// 訓示程式計數器是否不增加。 若是跳轉指令,則pc不需要自增,而是直接改為跳轉目标位址。
	jumps bool // indicates whether the program counter should not increment
	// 确定這個操作是否修改狀态。是否是寫指令(會修改 StatDB 中的資料)
	writes bool // determines whether this a state modifying operation
	// 訓示檢索到的操作是否有效并且已知 是不是一個有效操作碼
	valid bool // indication whether the retrieved operation is valid and known
	// 确定操作是否復原狀态(隐式停止)。指令指行完後是否中斷執行并復原狀态資料庫。
	reverts bool // determines whether the operation reverts state (implicitly halts)
	// 确定操作是否設定了傳回資料内容 訓示該操作是否有傳回值
	returns bool // determines whether the operations sets the return data content
}
           

Interpreter

core/vm/interpreter.go

// Interpreter is used to run Ethereum based contracts and will utilise the
// passed environment to query external sources for state information.
// The Interpreter will run the byte code VM based on the passed
// configuration.
// 解釋器用于運作基于以太坊的合約,并将使用傳遞的環境來查詢外部源以擷取狀态資訊。
// 解釋器将根據傳遞的配置運作VM位元組碼。
type Interpreter interface {
	// Run loops and evaluates the contract's code with the given input data and returns
	// the return byte-slice and an error if one occurred.
	// 用給定的入參循環執行合約的代碼,并傳回傳回結果的位元組切片,如果出現錯誤的話傳回錯誤。
	Run(contract *Contract, input []byte, static bool) ([]byte, error)
	// CanRun tells if the contract, passed as an argument, can be
	// run by the current interpreter. This is meant so that the
	// caller can do something like:
	// CanRun告訴目前解釋器是否可以運作目前合約,合約作為參數傳遞。這表示調用者可以這樣做:
	//
	// ```golang
	// for _, interpreter := range interpreters {
	//   if interpreter.CanRun(contract.code) {
	//     interpreter.Run(contract.code, input)
	//   }
	// }
	// ```
	CanRun([]byte) bool
}

// EVMInterpreter represents an EVM interpreter
// EVMInterpreter表示一個EVM解釋器,實作了Interpreter接口
type EVMInterpreter struct {
	evm *EVM
	cfg Config

	intPool *intPool

	// Keccak256 hasher執行個體跨指令共享
	hasher keccakState // Keccak256 hasher instance shared across opcodes
	// Keccak256 hasher結果數組跨指令共享
	hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes

	readOnly bool // Whether to throw on stateful modifications
	// 最後一個調用的傳回資料,便于接下來複用
	returnData []byte // Last CALL's return data for subsequent reuse
}
           

構造函數

先将

vmConfig.JumpTable

初始化為對應版本的指令集,然後生成

EVMInterpreter

對象并傳回。

core/vm/interpreter.go

// 構造函數
// NewEVMInterpreter returns a new instance of the Interpreter.
// NewEVMInterpreter傳回解釋器的一個新執行個體。
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
	// We use the STOP instruction whether to see
	// the jump table was initialised. If it was not
	// we'll set the default jump table.
	// 我們使用STOP指令來判斷指令表是否被初始化。若沒有,設定預設的指令表。
	if !cfg.JumpTable[STOP].valid {
		var jt JumpTable
		switch {
		case evm.chainRules.IsIstanbul:
			jt = istanbulInstructionSet
		case evm.chainRules.IsConstantinople:
			jt = constantinopleInstructionSet
		case evm.chainRules.IsByzantium:
			jt = byzantiumInstructionSet
		case evm.chainRules.IsEIP158:
			jt = spuriousDragonInstructionSet
		case evm.chainRules.IsEIP150:
			jt = tangerineWhistleInstructionSet
		case evm.chainRules.IsHomestead:
			jt = homesteadInstructionSet
		default:
			jt = frontierInstructionSet
		}
		for i, eip := range cfg.ExtraEips {
			if err := EnableEIP(eip, &jt); err != nil {
				// Disable it, so caller can check if it's activated or not
				// 若出現了錯誤,禁用它,這樣調用者可以檢查它是否激活。
				cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...)
				log.Error("EIP activation failed", "eip", eip, "error", err)
			}
		}
		cfg.JumpTable = jt
	}

	return &EVMInterpreter{
		evm: evm,
		cfg: cfg,
	}
}
           

Input資料結構

Contract Application Binary Interface(ABI)是以太坊生态系統中與合約互動的标準方式,既可用于從區塊鍊之外互動,也可用于合約間的互動。資料根據其類型進行編碼。編碼不具備自解釋性,是以需要一個範式來解碼。

我們假設合約的接口函數是強類型的,在編譯時已知,并且是靜态的。我們假設所有合約在編譯時都具有它們調用的合約的接口定義。[7]

函數選擇子(Function Selector)與參數編碼(Argument Encoding)

函數調用的

Input

的前4個位元組指定要調用的函數。它是函數簽名的Keccak-256 (SHA-3)hash的前(左,大端高階)4位元組。函數簽名即是帶有參數類型括号清單的函數名,參數類型由逗号分隔而不是空格。簽名不包括函數的傳回類型。從第5個位元組開始,後面跟着是編碼後的參數。給定一個合約:[7]

pragma solidity >=0.4.16 <0.7.0;


contract Foo {
    function bar(bytes3[2] memory) public pure {}
    function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
    function sam(bytes memory, bool, uint[] memory) public pure {}
}
           

如果我們想用參數69和true來調用

baz

方法,我們将傳遞68位元組的

Input

,可以分解為以下幾個部分:

  • 0xcdcd77c0

    :函數選擇子或者說是方法ID。

    baz

    的函數簽名是

    baz(uint32,bool)

    ,然後進行hash操作并取前4個位元組作為函數選擇子:

    KeccakHash("baz(uint32,bool)")[0:4] => 0xcdcd77c0

  • 0x0000000000000000000000000000000000000000000000000000000000000045

    :第一個參數,uint32, 值

    69

    填充為32位元組。
  • 0x0000000000000000000000000000000000000000000000000000000000000001

    :第二個參數,布爾值

    true

是以調用

Foo

合約時傳輸的

Input

的值為:

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
           

函數選擇子告訴了EVM我們想要調用合約的哪個方法,它和參數資料一起,被編碼到了交易的

data

資料中。跟合約代碼一起送到解釋器裡的還有

Input

,而這個

Input

中的資料是由交易的

data

提供的。函數選擇子和參數的解析功能并不由EVM完成,而是合約編譯器在編譯時插入代碼完成的。

以太坊EVM源碼注釋之資料結構以太坊EVM源碼分析之資料結構

在我們編譯智能合約的時候,編譯器會自動在生成的位元組碼的最前面增加一段函數選擇邏輯: 首先通過

CALLDATALOAD

指令将“4-byte signature”壓入堆棧中,然後依次跟該合約中包含的函數進行比對,如果比對則調用

JUMPI

指令跳入該段代碼繼續執行。

資料加載相關的指令

  • CALLDATALOAD

    :把輸入資料加載到

    Stack

  • CALLDATACOPY

    :把輸入資料加載到

    Memory

  • CODECOPY

    :把目前合約代碼拷貝到

    Memory

  • EXTCODECOPY

    :把外部合約代碼拷貝到

    Memory

這些指令對應的操作如下圖所示:

以太坊EVM源碼注釋之資料結構以太坊EVM源碼分析之資料結構

Appendix A

Stack結構及其操作

core/vm/stack.go

// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialised objects.
// Stack是用于堆棧基本操作的對象。彈出到堆棧中的項将被更改。堆棧不負責添加新初始化的對象。
type Stack struct {
	data []*big.Int //指針的切片,堆棧中本質上存儲的是指針
}

func newstack() *Stack {
	return &Stack{data: make([]*big.Int, 0, 1024)} //初始長度為0,容量1024
}

//-----------棧的方法----------------

// Data returns the underlying big.Int array.
// Data傳回底層的big.Int數組
func (st *Stack) Data() []*big.Int {
	return st.data
}

func (st *Stack) push(d *big.Int) {
	// NOTE push limit (1024) is checked in baseCheck
	// 注意:在baseCheck中已經檢查了堆棧最大限制 (1024)
	//stackItem := new(big.Int).Set(d)
	//st.data = append(st.data, stackItem)
	st.data = append(st.data, d) // 數組末尾就是堆棧的頂部
}
func (st *Stack) pushN(ds ...*big.Int) { // 一次性壓入堆棧多個條目
	st.data = append(st.data, ds...)
}

// 彈出棧頂元素
func (st *Stack) pop() (ret *big.Int) {
	ret = st.data[len(st.data)-1]      // 彈出的條目
	st.data = st.data[:len(st.data)-1] // 堆棧深度減一
	return
}

func (st *Stack) len() int { // 堆棧長度,深度
	return len(st.data)
}

// 将堆棧中第n項與棧頂元素交換
func (st *Stack) swap(n int) {
	st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
}

// 将棧的第n項複制并入棧
func (st *Stack) dup(pool *intPool, n int) {
	st.push(pool.get().Set(st.data[st.len()-n]))
}

// 擷取棧頂元素的值但不彈出
func (st *Stack) peek() *big.Int {
	return st.data[st.len()-1]
}

// Back returns the n'th item in stack
// Back傳回棧的第n項
func (st *Stack) Back(n int) *big.Int {
	return st.data[st.len()-n-1]
}
           

core/vm/stack_table.go

// 一些棧的輔助函數

// maxStack specifies the max length the stack can have for this operation
// to not overflow the stack.
// maxStack指定該操作不會使堆棧溢出的堆棧最大長度。
// 也就是說,隻要堆棧不超過這個最大長度,這個操作就不會導緻棧溢出。
// 參數:pops 該操作執行過程中所做的pop次數; pushs 該操作執行過程中所做的push次數
func maxStack(pop, push int) int {
	return int(params.StackLimit) + pop - push
}

// minStack tells how many stack items are required
// minStack 表示需要多少個堆棧項
// 參數:pops 該操作執行過程中所做的pop次數; pushs 該操作執行過程中所做的push次數
// 需要的堆棧項數就是該操作pop的次數
func minStack(pops, push int) int {
	return pops
}
           

Memory結構及其操作

core/vm/memory.go

// Memory implements a simple memory model for the ethereum virtual machine.
// Memory為以太坊虛拟機實作一個簡單的記憶體模型
type Memory struct {
	store       []byte // 位元組數組
	lastGasCost uint64 // 已配置設定的記憶體所花費的gas,用于擴充記憶體時計算花費的gas。
}

// NewMemory returns a new memory model.
// NewMemory傳回一個新的記憶體模型
func NewMemory() *Memory {
	return &Memory{}
}

// Set sets offset + size to value
// 将offset--offset+size區域設定為value
func (m *Memory) Set(offset, size uint64, value []byte) {
	// It's possible the offset is greater than 0 and size equals 0. This is because
	// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
	// offset大于而size等于是可能出現的情況。這是因為CalcMemSize函數當size為0的時候會傳回0.(空操作)
	if size > 0 {
		// length of store may never be less than offset + size.
		// The store should be resized PRIOR to setting the memory
		// store的長度可能永遠不會小于offset+size。
		// 在設定這片記憶體之前,store的大小應該會被調整
		// 是以若出現offset+size > store長度的情況,記憶體還沒被配置設定,store為空。
		if offset+size > uint64(len(m.store)) {
			panic("invalid memory: store empty")
		}
		copy(m.store[offset:offset+size], value)
	}
}

// Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to
// 32 bytes.
// Set32将offset--offset+32的區域設定為val,若不夠32位元組,左填充0
func (m *Memory) Set32(offset uint64, val *big.Int) {
	// length of store may never be less than offset + size.
	// The store should be resized PRIOR to setting the memory
	if offset+32 > uint64(len(m.store)) {
		panic("invalid memory: store empty")
	}
	// Zero the memory area
	// 先将那片記憶體區域置0.
	copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
	// Fill in relevant bits
	// 将val填入該記憶體區域
	math.ReadBits(val, m.store[offset:offset+32])
}

// Resize resizes the memory to size
// Resize重新調整記憶體大小到size
func (m *Memory) Resize(size uint64) {
	if uint64(m.Len()) < size {
		m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
	}
}

// Get returns offset + size as a new slice
// GetCopy将記憶體offset:offset+size的内容複制然後傳回
func (m *Memory) GetCopy(offset, size int64) (cpy []byte) {
	if size == 0 {
		return nil
	}

	if len(m.store) > int(offset) {
		cpy = make([]byte, size)
		copy(cpy, m.store[offset:offset+size])

		return
	}

	return
}

// GetPtr returns the offset + size
// GetPtr傳回該記憶體區域的指針
func (m *Memory) GetPtr(offset, size int64) []byte {
	if size == 0 {
		return nil
	}

	if len(m.store) > int(offset) {
		return m.store[offset : offset+size]
	}

	return nil
}

// Len returns the length of the backing slice
// Len傳回記憶體長度
func (m *Memory) Len() int {
	return len(m.store)
}

// Data returns the backing slice
// Data傳回整個記憶體的資料
func (m *Memory) Data() []byte {
	return m.store
}
           

intPool結構及其操作

intPool就是256大小的

big.Int

的池,用來加速

big.Int

的配置設定。節省頻繁建立和銷毀

big.Int

對象的開銷。

core/vm/intPool.go

var checkVal = big.NewInt(-42) // 為什麼是-42??

const poolLimit = 256

// intPool is a pool of big integers that
// can be reused for all big.Int operations.
// intPool是一個大整數池,可以為所有big.Int操作重用。
type intPool struct {
	pool *Stack // 這個big.Int池以棧的形式存在
}

// intPool的構造函數
func newIntPool() *intPool {
	return &intPool{pool: newstack()}
}

// get retrieves a big int from the pool, allocating one if the pool is empty.
// Note, the returned int's value is arbitrary and will not be zeroed!
// get從池中擷取一個big.Int,如果池是空的,就配置設定一個。
// 注意:傳回的big.Int值是随機的,不是歸0以後的
func (p *intPool) get() *big.Int {
	if p.pool.len() > 0 { // 若不是空的,則直接擷取
		return p.pool.pop()
	}
	return new(big.Int) // 若是空的,則現場配置設定一個big.Int。效率較低
}

// getZero retrieves a big int from the pool, setting it to zero or allocating
// a new one if the pool is empty.
// getZero從池中擷取一個big.Int,并将之歸0.如果池是空的,就配置設定一個。
func (p *intPool) getZero() *big.Int {
	if p.pool.len() > 0 {
		return p.pool.pop().SetUint64(0)
	}
	return new(big.Int)
}

// put returns an allocated big int to the pool to be later reused by get calls.
// Note, the values as saved as is; neither put nor get zeroes the ints out!
// put放入池中一些已經配置設定的big.Int,稍後由get方法重用。
// 注意,這些值原樣保留。put和get方法都不會将這些整數歸0.
func (p *intPool) put(is ...*big.Int) {
	if len(p.pool.data) > poolLimit { // 若池已滿,傳回
		return
	}
	for _, i := range is {
		// verifyPool is a build flag. Pool verification makes sure the integrity
		// of the integer pool by comparing values to a default value.
		// verifyPool是一個生成标志。池的驗證函數通過将值與預設值進行比較來確定整數池的完整性。
		if verifyPool {
			i.Set(checkVal)
		}
		p.pool.push(i)
	}
}

// The intPool pool's default capacity
// intPool的池的預設容量
const poolDefaultCap = 25

// intPoolPool manages a pool of intPools.
// intPoolPool管理一個intPool的池
type intPoolPool struct {
	pools []*intPool // intPool的切片
	lock  sync.Mutex // 互斥信号量
}

// 初始化
var poolOfIntPools = &intPoolPool{
	pools: make([]*intPool, 0, poolDefaultCap),
}

// get is looking for an available pool to return.
// get查找一個可用的池然後傳回
func (ipp *intPoolPool) get() *intPool {
	ipp.lock.Lock()         // 上鎖
	defer ipp.lock.Unlock() // 運作完解鎖

	if len(poolOfIntPools.pools) > 0 {
		ip := ipp.pools[len(ipp.pools)-1]        // 找到一個池
		ipp.pools = ipp.pools[:len(ipp.pools)-1] // 從intPool池中删除該項
		return ip                                // 傳回
	}
	return newIntPool() // 若為空,現場初始化一個intPool傳回
}

// put a pool that has been allocated with get.
// 放入已配置設定的池。
func (ipp *intPoolPool) put(ip *intPool) {
	ipp.lock.Lock()
	defer ipp.lock.Unlock()

	if len(ipp.pools) < cap(ipp.pools) {
		ipp.pools = append(ipp.pools, ip)
	}
}
           

參考文獻

  1. Ethereum Yellow Paper

    ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER

    https://ethereum.github.io/yellowpaper/paper.pdf

  2. Ethereum White Paper

    A Next-Generation Smart Contract and Decentralized Application Platform

    https://github.com/ethereum/wiki/wiki/White-Paper

  3. Ethereum EVM Illustrated

    https://github.com/takenobu-hs/ethereum-evm-illustrated

  4. Go Ethereum Code Analysis

    https://github.com/ZtesoftCS/go-ethereum-code-analysis

  5. 以太坊源碼解析:evm

    https://yangzhe.me/2019/08/12/ethereum-evm/

  6. 以太坊 - 深入淺出虛拟機

    https://learnblockchain.cn/2019/04/09/easy-evm/

  7. Contract ABI Specification

    https://solidity.readthedocs.io/en/v0.5.10/abi-spec.html?highlight=selector#function-selector

  8. 認識以太坊智能合約

    https://yangzhe.me/2019/08/01/ethereum-cognition-and-deployment/#%E8%B0%83%E7%94%A8%E5%90%88%E7%BA%A6

    ntPoolPool) put(ip *intPool) {

    ipp.lock.Lock()

    defer ipp.lock.Unlock()

    if len(ipp.pools) < cap(ipp.pools) {

    ipp.pools = append(ipp.pools, ip)

    }

    }

## 參考文獻
1. Ethereum Yellow Paper   
   ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER   
	https://ethereum.github.io/yellowpaper/paper.pdf
2. Ethereum White Paper   
   A Next-Generation Smart Contract and Decentralized Application Platform
	https://github.com/ethereum/wiki/wiki/White-Paper
3. Ethereum EVM Illustrated   
   https://github.com/takenobu-hs/ethereum-evm-illustrated
4. Go Ethereum Code Analysis
   https://github.com/ZtesoftCS/go-ethereum-code-analysis
5. 以太坊源碼解析:evm
   https://yangzhe.me/2019/08/12/ethereum-evm/
6. 以太坊 - 深入淺出虛拟機   
   https://learnblockchain.cn/2019/04/09/easy-evm/
7. Contract ABI Specification
   https://solidity.readthedocs.io/en/v0.5.10/abi-spec.html?highlight=selector#function-selector
8. 認識以太坊智能合約
   https://yangzhe.me/2019/08/01/ethereum-cognition-and-deployment/#%E8%B0%83%E7%94%A8%E5%90%88%E7%BA%A6