以太坊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子產品的整體結構圖,有些已經發生變化。到目前為止(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
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
,可以分解為以下幾個部分:
-
:函數選擇子或者說是方法ID。0xcdcd77c0
的函數簽名是baz
,然後進行hash操作并取前4個位元組作為函數選擇子:baz(uint32,bool)
KeccakHash("baz(uint32,bool)")[0:4] => 0xcdcd77c0
-
:第一個參數,uint32, 值0x0000000000000000000000000000000000000000000000000000000000000045
填充為32位元組。69
-
:第二個參數,布爾值0x0000000000000000000000000000000000000000000000000000000000000001
。true
是以調用
Foo
合約時傳輸的
Input
的值為:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
函數選擇子告訴了EVM我們想要調用合約的哪個方法,它和參數資料一起,被編碼到了交易的
data
資料中。跟合約代碼一起送到解釋器裡的還有
Input
,而這個
Input
中的資料是由交易的
data
提供的。函數選擇子和參數的解析功能并不由EVM完成,而是合約編譯器在編譯時插入代碼完成的。
在我們編譯智能合約的時候,編譯器會自動在生成的位元組碼的最前面增加一段函數選擇邏輯: 首先通過
CALLDATALOAD
指令将“4-byte signature”壓入堆棧中,然後依次跟該合約中包含的函數進行比對,如果比對則調用
JUMPI
指令跳入該段代碼繼續執行。
資料加載相關的指令
-
:把輸入資料加載到CALLDATALOAD
中Stack
-
:把輸入資料加載到CALLDATACOPY
中Memory
-
:把目前合約代碼拷貝到CODECOPY
中Memory
-
:把外部合約代碼拷貝到EXTCODECOPY
中Memory
這些指令對應的操作如下圖所示:
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)
}
}
參考文獻
-
Ethereum Yellow Paper
ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
https://ethereum.github.io/yellowpaper/paper.pdf
-
Ethereum White Paper
A Next-Generation Smart Contract and Decentralized Application Platform
https://github.com/ethereum/wiki/wiki/White-Paper
-
Ethereum EVM Illustrated
https://github.com/takenobu-hs/ethereum-evm-illustrated
-
Go Ethereum Code Analysis
https://github.com/ZtesoftCS/go-ethereum-code-analysis
-
以太坊源碼解析:evm
https://yangzhe.me/2019/08/12/ethereum-evm/
-
以太坊 - 深入淺出虛拟機
https://learnblockchain.cn/2019/04/09/easy-evm/
-
Contract ABI Specification
https://solidity.readthedocs.io/en/v0.5.10/abi-spec.html?highlight=selector#function-selector
-
認識以太坊智能合約
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