ethereum-evm代碼分析(v1.8.24)
1、引導
本篇爲分析以太坊虛擬機部分的代碼,但是爲了大家方便理解,本次打算講解一筆交易發送以太坊客戶端,再到上鍊作爲一個完整鏈路來分析,並把以太坊evm作爲重點來講解。
1.1 sendTx()
第一部分主要涉及交易池部分邏輯代碼,不瞭解的部分請翻閱tx_pool代碼分析部分
# go-ethereum/eth/api_backend.go
//當用戶需要發送一筆交易時候,實際上是調用了以太坊客戶端的sendTx方法
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
// 調用以太坊交易池,將tx添加到本地交易列表中
return b.eth.txPool.AddLocal(signedTx)
}
// AddLocal enqueues a single transaction into the pool if it is valid, marking
// the sender as a local one in the mean time, ensuring it goes around the local
// pricing constraints.
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
return pool.addTx(tx, !pool.config.NoLocals)
}
// addTx enqueues a single transaction into the pool if it is valid.
func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
pool.mu.Lock()
defer pool.mu.Unlock()
// 在此將交易放入交易池,若相同nonce的交易存在,那麼嘗試覆蓋並廣播交易
// 若是新交易則放入賬戶的queue中,如果賬戶queue中存在則覆蓋,
// Try to inject the transaction and update any state
replace, err := pool.add(tx, local)
if err != nil {
return err
}
// 如果我們添加的是一筆新的交易,那麼執行以下交易升級
// If we added a new transaction, run promotion checks and return
if !replace {
from, _ := types.Sender(pool.signer, tx) // already validated
pool.promoteExecutables([]common.Address{from})
}
return nil
}
從上面代碼可以看出,用戶發送交易到以太坊客戶端,以太坊拿到客戶端以後如何放入交易池的邏輯。
在上面邏輯執行以後若交易能進入pending列表,那麼交易就會被廣播,通過p2p模塊廣播給若干接點。
如交易沒進入pending列表,進入了賬戶queue中,那麼隨後若交易正常,那麼也會進入賬戶的pending列表中,並且會被廣播。
若本地節點是挖礦節點則將交易打包成區塊進行上鍊,那麼下面轉到挖礦部分代碼,看看如何拿到交易進行執行
1.2 commitNewWork()
此部分爲挖礦代碼部分的主要代碼部分,在這個期間,做了將pending裏面的交易拿出來執行後打包到一個新區塊中的工作。那麼下面開始分析交易如何被拿出來執行的
// commitNewWork generates several new sealing tasks based on the parent block.
func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
............................
// 以上部分與本次分析相關性不大,忽略處理
// 下面從交易池拿出所有pending的交易
// Fill the block with all available pending transactions.
pending, err := w.eth.TxPool().Pending()
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
return
}
// Short circuit if there is no available pending transactions
if len(pending) == 0 {
w.updateSnapshot()
return
}
// 將交易分爲遠程的和本地的交易
// Split the pending transactions into locals and remotes
localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
for _, account := range w.eth.TxPool().Locals() {
if txs := remoteTxs[account]; len(txs) > 0 {
delete(remoteTxs, account)
localTxs[account] = txs
}
}
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
// 下面這一步主要是開始將交易進行執行了
if w.commitTransactions(txs, w.coinbase, interrupt) {
return
}
}
if len(remoteTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
if w.commitTransactions(txs, w.coinbase, interrupt) {
return
}
}
// 最後提交一個 區塊
w.commit(uncles, w.fullTaskHook, true, tstart)
}
下面部分代碼主要將需要打包在區塊中的交易逐個執行,邏輯如下
func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
// Short circuit if current is nil
if w.current == nil {
return true
}
if w.current.gasPool == nil {
w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
}
// 合併logs
var coalescedLogs []*types.Log
for {
// In the following three cases, we will interrupt the execution of the transaction.
// (1) new head block event arrival, the interrupt signal is 1
// (2) worker start or restart, the interrupt signal is 1
// (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2.
// For the first two cases, the semi-finished work will be discarded.
// For the third case, the semi-finished work will be submitted to the consensus engine.
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit)
if ratio < 0.1 {
ratio = 0.1
}
w.resubmitAdjustCh <- &intervalAdjust{
ratio: ratio,
inc: true,
}
}
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
}
// 不能低於執行交易的最小gas 21000
// If we don't have enough gas for any further transactions then we're done
if w.current.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
break
}
// 從txs裏面拿出頂端的 gasPrice最小的
// Retrieve the next transaction and abort if all done
tx := txs.Peek()
if tx == nil {
break
}
// Error may be ignored here. The error has already been checked
// during transaction acceptance is the transaction pool.
//
// We use the eip155 signer regardless of the current hf.
from, _ := types.Sender(w.current.signer, tx)
// Check whether the tx is replay protected. If we're not in the EIP155 hf
// phase, start ignoring the sender until we do.
if tx.Protected() && !w.config.IsEIP155(w.current.header.Number) {
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.config.EIP155Block)
txs.Pop()
continue
}
// 準備開始執行交易
// Start executing the transaction
w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount)
// 此段代碼將涉及到交易執行,離虛擬機部分很近了
logs, err := w.commitTransaction(tx, coinbase)
// 以下部分與本次分析關係不大 ,忽略處理
............................................
}
commitTransaction()實際將交易傳到了core.ApplyTransaction(),並將tx轉換爲一個message,並且在ApplyTransaction()構造了虛擬機的context,隨後創建一個虛擬機,並調用ApplyMessage後調用TransitionDb()實際將交易執行,具體邏輯如下
// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, 0, err
}
// Create a new context to be used in the EVM environment
context := NewEVMContext(msg, header, bc, author)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
.............................................
}
// ApplyMessage computes the new state by applying the given message
// against the old state within the environment.
//
// ApplyMessage returns the bytes returned by any EVM execution (if it took place),
// the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}
// TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed.
// An error indicates a consensus issue.
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
// 做一次預檢,主要做了nonce檢查和賬戶餘額檢查
if err = st.preCheck(); err != nil {
return
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
// 如果本次tx的to地址爲空則爲合約創建交易
contractCreation := msg.To() == nil
// Pay intrinsic gas
gas, err := IntrinsicGas(st.data, contractCreation, homestead)
if err != nil {
return nil, 0, false, err
}
if err = st.useGas(gas); err != nil {
return nil, 0, false, err
}
var (
evm = st.evm
// vm errors do not effect consensus and are therefor
// not assigned to err, except for insufficient balance
// error.
vmerr error
)
if contractCreation {
// 如果是合約創建交易,則調用evm.create()方法
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// 要不然調用合約的Call()方法執行交易,在這裏先把evm當成黑盒,先知道這裏是實際調用evm執行字節碼即可
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// The only possible consensus-error would be if there wasn't
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
}
// 計算回退的gas
st.refundGas()
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err
}
1.3 第一小節總結
經過上面主流程的分析以後,現在已經可以知道一筆交易發送到以太坊客戶端以後經歷了一系列操作後一直到需要虛擬機實際執行交易的部分。
2、以太坊虛擬機部分分析
下面按照慣例先介紹一下以太坊相關數據結構
# go-ethereum/core/vm/evm.go
// context是在執行一筆交易時候的附加信息,上下文環境
// Context provides the EVM with auxiliary information. Once provided
// it shouldn't be modified.
type Context struct {
// canTransfer函數用來判斷是否可以執行轉賬,很簡單
// CanTransfer returns whether the account contains
// sufficient ether to transfer the value
CanTransfer CanTransferFunc
// transfer函數用來執行轉賬的函數,很簡單
// Transfer transfers ether from one account to the other
Transfer TransferFunc
// GetHash returns the hash corresponding to n
// 獲取hash值函數
GetHash GetHashFunc
// 下面是交易執行的一些簡要附加信息
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
// Block information
Coinbase common.Address // Provides information for COINBASE
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
}
//
// 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不能被重用且不是線程安全的,每一筆交易都會創建一個新的context和evm
// The EVM should never be reused and is not thread safe.
type EVM struct {
// Context provides auxiliary(輔助) blockchain related information
Context
// StateDB gives access to the underlying state
StateDB StateDB
// 當前調用棧的深度
// Depth is the current call stack
depth int
// chainConfig contains information about the current chain
chainConfig *params.ChainConfig
// chain rules contains the chain rules for the current epoch
chainRules params.Rules
// virtual machine configuration options used to initialise the
// 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 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 uint64
}
// 虛擬機解釋器
// EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct {
evm *EVM
cfg Config
gasTable params.GasTable
intPool *intPool
hasher keccakState // Keccak256 hasher instance shared across opcodes
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
}
// 以太坊操作結構體。以太坊定義了一個長度爲256的operation數組來存放虛擬機操作,每一個操作有4個相關函數和6個標記位
type operation struct {
//實際執行函數
// execute is the operation function
execute executionFunc
// 計算執行花費的gas
// gasCost is the gas function and returns the gas required for execution
gasCost gasFunc
// 驗證棧的函數,棧必須符合一定條件比如深度小於1024
// validateStack validates the stack (size) for the operation
validateStack stackValidationFunc
// 返回操作需要的內存大小
// memorySize returns the memory size required for the operation
memorySize memorySizeFunc
//操作標記位
halts bool // indicates whether the operation should halt further execution
jumps bool // indicates whether the program counter should not increment
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
}
以上就是以太坊虛擬機相關的主要數據結構,下面開始介紹一些邏輯實現
2.1 go-ethereum/core/evm.go分析
# go-ethereum/core/evm.go
// 先介紹一個主要用來創建一些環境信息的邏輯代碼
// 此函數主要用來爲以太坊創建一個上下文環境
// NewEVMContext creates a new context for use in the 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()),
}
}
// 返回一個函數,返回的函數主要用區塊號來取區塊的hash值
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
var cache map[uint64]common.Hash
return func(n uint64) common.Hash {
// 如果沒有緩存,那麼創建一個緩存,並將傳來的區塊頭的父區塊信息放入緩存
// If there's no hash cache yet, make one
if cache == nil {
cache = map[uint64]common.Hash{
ref.Number.Uint64() - 1: ref.ParentHash,
}
}
// 如果從緩存中找到相關區塊號對應的區塊hash,那麼直接返回
// Try to fulfill the request from the cache
if hash, ok := cache[n]; ok {
return hash
}
// 要不然從給的區塊往前推,逐個緩存區塊號和區塊hash,並且在找到要查詢的區塊號對應的區塊hash時候返回
// Not cached, iterate the blocks and cache the hashes
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
cache[header.Number.Uint64()-1] = header.ParentHash
if n == header.Number.Uint64()-1 {
return header.ParentHash
}
}
return common.Hash{}
}
}
// 主要判斷一下賬戶餘額夠不夠轉賬
// CanTransfer checks whether there are enough funds in the address' account to make a transfer.
// This does not take the necessary gas in to account to make the transfer valid.
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
return db.GetBalance(addr).Cmp(amount) >= 0
}
// transfer主要執行了實際的轉賬操作,給發送者/接受者 減去/加上相應金額,此處的加減不是立即生效
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
db.SubBalance(sender, amount)
db.AddBalance(recipient, amount)
}
以上主要是以太坊源代碼core下面evm.go的主要邏輯分析,其實做了給以太坊虛擬機每次執行創建上下文環境一件事。
2.2 go-ethereum/core/vm/evm.go
下面開始分析以太坊虛擬機的核心文件,vm包下面的evm.go
2.2.1 NewEVM()
// 這是創建虛擬機的邏輯,執行交易時每次使用虛擬機都新創建一個
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
// 給evm屬性初始化變量
evm := &EVM{
Context: ctx,
StateDB: statedb,
vmConfig: vmConfig,
chainConfig: chainConfig,
chainRules: chainConfig.Rules(ctx.BlockNumber),
interpreters: make([]Interpreter, 0, 1),
}
if chainConfig.IsEWASM(ctx.BlockNumber) {
// to be implemented by EVM-C and Wagon PRs.
// 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.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
evm.interpreter = evm.interpreters[0]
return evm
}
上面主要是emv在創建時候的簡單邏輯,由第一節分析的tx執行過程可知,實際上每次交易調用虛擬機執行時候主要調用了虛擬機的兩個方法evm.Call()
和 evm.Create()
兩個方法,那麼下面的分析主要由這兩個方法展開
2.2.2 evm.Call()
// 這個方法是除合約創建以外所tx實際執行都要使用的方法
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// 先判斷若開啓了非遞歸模式且棧深度大於0 則直接返回
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// 如果evm的調用棧大於Call/Create方法的最大深度限制1024 那麼返回錯誤
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
// 如果value字段大於調用者的餘額那麼返回餘額不足錯誤
// Fail if we're trying to transfer more than the available balance
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
// 將addr(被調方)地址轉爲AccountRef類型,用snapshot記錄當前狀態快照
var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
// 若addr不存在於statedb中
if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
// 判斷此地址若不是預編譯合約地址 且區塊號符合eip158提議 且 value值爲0 ,那麼簡單返回
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
}
// 若上面條件不成立,那麼創建此賬戶
evm.StateDB.CreateAccount(addr)
}
// 先將以太幣轉賬操作執行 call --> to(addr)
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
// 創建contract結構體對call、to、value、gas等信息包裝成contract結構體方便虛擬機執行
contract := NewContract(caller, to, value, gas)
// 下面將addr、codehash、code賦值給contract的成員屬性
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// Even if the account has no code, we need to continue because it might be a precompile
start := time.Now()
// debug模式下捕獲一次evm執行交易的開始結束時間
// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func() { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}()
}
// 下面調用run方法執行本次tx
ret, err = run(evm, contract, input, false)
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
// 若錯誤不爲空,那麼進行revert回滾
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted {
contract.UseGas(contract.Gas)
}
}
// 返回執行結果,剩餘gas,錯誤信息
return ret, contract.Gas, err
}
// run()方法分析
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
// 若contract的addr不爲空,那麼判斷此addr是不是預編譯合約,若是那麼執行RunPrecompileContract方法
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
// 此處爲預編譯合約
precompiles = PrecompiledContractsByzantium
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
}
}
// 調用evm的解釋器執行contract
for _, interpreter := range evm.interpreters {
// CanRun()直接返回true
if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter {
// Ensure that the interpreter pointer is set back
// to its current value upon return.
defer func(i Interpreter) {
evm.interpreter = i
}(evm.interpreter)
evm.interpreter = interpreter
}
return interpreter.Run(contract, input, readOnly)
}
}
return nil, ErrNoCompatibleInterpreter
}
//interpreter.Run()方法分析
// 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.
// 下面短話主要說出了errExecutionReverted錯誤以外,其他錯誤將消耗所有gas
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
if in.intPool == nil {
// 若intPool爲空,那麼嘗試從intPool的pool的頂端面拿一個出來用,intPool的pool爲空的情況下創建一個新的intPool,intPool底層是一個棧結構且長度不超過256
// 在本次運行結束以後將intPool放入intPool的pool中且將解釋器的intPool置空
// intPool的pool容量爲25
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-- }()
// 如果此次執行被標記爲只讀模式那麼將解釋器的模式也改成只讀模式,在本次執行完成以後恢復解釋器的執行模式爲非只讀模式
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This makes also sure that the readOnly flag isn't removed for child calls.
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
// 重設返回數據爲nil
// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
in.returnData = nil
// 若contract.Code長度爲0,比如調用了普通轉以太幣的操作而不是合約,那麼直接返回
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode 當前操作碼
mem = NewMemory() // bound memory 創建內存,用代碼實現的內存操作
stack = newstack() // local stack 創建一個棧,深度爲1024
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter 程序計數器
cost uint64 // 花費計數
// 以下主要爲了日誌方面的記錄
// copies used by tracer
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
)
// 將input賦值給contract.input
contract.Input = input
// 執行結束以後將棧的數據放入intPool中
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
// 如果是debug模式,那麼對相應的需要log的數據做記錄
if in.cfg.Debug {
defer func() {
if err != nil {
if !logged {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
} else {
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
}
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the
// parent context.
// 虛擬機中斷標誌位爲0的時候繼續進行for循環
for atomic.LoadInt32(&in.evm.abort) == 0 {
if in.cfg.Debug {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
// 從code裏面取出字節碼
op = contract.GetOp(pc)
// 從jumpTable中獲取字節碼對應的operation
operation := in.cfg.JumpTable[op]
// 如果此operation非法,返回錯誤
if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
}
// 判斷棧的最大深度是否超過1024,超過則返回
if err := operation.validateStack(stack); err != nil {
return nil, err
}
// 對本字節碼做一些驗證,主要防止evm在readOnly模式下執行了修改狀態的字節碼
// If the operation is valid, enforce and write restrictions
if err := in.enforceRestrictions(op, operation, stack); err != nil {
return nil, err
}
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
if operation.memorySize != nil {
memSize, overflow := bigUint64(operation.memorySize(stack))
if overflow {
return nil, errGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, errGasUintOverflow
}
}
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
// 開始計算本次opCode所消耗的gas
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
// UseGas會將c.gas - cost 判斷剩餘gas是不是能夠執行此次操作
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
// 分配內存
if memorySize > 0 {
mem.Resize(memorySize)
}
if in.cfg.Debug {
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
logged = true
}
// 在此實際執行了相關操作碼,爲了方便在這裏用ADD字節碼舉例
//func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
//x, y := stack.pop(), stack.peek()
//math.U256(y.Add(x, y))
//interpreter.intPool.put(x)
//return nil, nil
//}
// execute the operation
res, err := operation.execute(&pc, in, contract, mem, stack)
// verifyPool is a build flag. Pool verification makes sure the integrity
// of the integer pool by comparing values to a default value.
if verifyPool {
verifyIntegerPool(in.intPool)
}
// 如果字節碼中的returns標誌位爲true那麼將字節碼執行的結果放在解釋器的returnData中
// if the operation clears the return data (e.g. it has returning data)
// set the last return to the result of the operation.
if operation.returns {
in.returnData = res
}
switch {
// 若執行完操作碼後出現錯誤,那麼返回錯誤
case err != nil:
return nil, err
// 若操作碼回滾標記爲true,那麼返回回滾錯誤
case operation.reverts:
return res, errExecutionReverted
// 若操作碼停止標記位爲true,那麼返回執行結果
case operation.halts:
return res, nil
// 若操作碼跳轉標記位爲false,那麼程序計數器自增,進行下次循環
case !operation.jumps:
pc++
}
}
return nil, nil
}
以上主要是call()函數執行時的邏輯,其中[256]operation個操作字節碼的生成在go-ethereum/core/vm/jump_table.go中,其中每個operation的execute函數的實現在go-ethereum/core/vm/instructions.go
下面分析create()函數的邏輯
2.2.3 evm.Create()
// Create函數主要用來創建合約,Create2是支持了新的eip的函數,這裏暫時不分析
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
// 計算合約地址,使用調用者地址和nonce創建合約地址
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
}
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the
// limit.
//判斷evm調用的深度是否大於最大調用限制
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, ErrDepth
}
// 若value大於0 則判斷賬戶的餘額是否充足
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
// 獲取調用者的nonce值並將調用者的nonce更新爲+1以後的值
nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1)
// 確保此地址沒有合約已經存在,一般來說一個stateObject只能有一個code存在
// Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address)
// 如果新生成的合約地址的Nonce不爲0或者使用此地址獲取的codehash值不爲初始值,返回合約地址衝突錯誤
// 下面主要檢查新生成的合約地址是不是個新地址,是不是被使用過
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
return nil, common.Address{}, 0, ErrContractAddressCollision
}
// 獲取當前statedb的快照
// Create a new account on the state
snapshot := evm.StateDB.Snapshot()
// 在statedb中創建此賬戶
evm.StateDB.CreateAccount(address)
// 如果區塊號的範圍是在eip158的範圍內,那麼將合約地址的nonce設置爲1
if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
evm.StateDB.SetNonce(address, 1)
}
// 先執行以太幣轉賬
evm.Transfer(evm.StateDB, caller.Address(), address, value)
// initialise a new contract and set the code that is to be used by the
// EVM. The contract is a scoped environment for this execution context
// only.
//和調用call一樣,將數據包裝在contract裏面
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
// 如果配置了非遞歸且evm.depth大於0,那麼說明遞歸調用了,直接返回
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, address, gas, nil
}
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
}
start := time.Now()
// 具體執行
ret, err := run(evm, contract, nil, false)
// 判斷是否超出最大代碼大小限制
// check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// if the contract creation ran successfully and no errors were returned
// calculate the gas required to store the code. If the code could not
// be stored due to not enough gas set an error and let it be handled
// by the error checking condition below.
// 如果執行成功了,且創建數據花費的gas數量沒超過賬戶限制,那麼在statedb中存入此賬戶的代碼
if err == nil && !maxCodeSizeExceeded {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(address, ret)
} else {
err = ErrCodeStoreOutOfGas
}
}
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
// 如果代碼大小超出限制 或者 錯誤不爲空且區塊號是homestead版本或者錯誤不等於code存儲費用超出賬戶gas錯誤 那麼回滾到前面的狀態 且消耗完本次賬戶提供的gas
if maxCodeSizeExceeded || (err != nil && (evm.ChainConfig().IsHomestead(evm.BlockNumber) || err != ErrCodeStoreOutOfGas)) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted {
contract.UseGas(contract.Gas)
}
}
// Assign err if contract code size exceeds the max while the err is still empty.
if maxCodeSizeExceeded && err == nil {
err = errMaxCodeSizeExceeded
}
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}
return ret, address, contract.Gas, err
}
以上部分粗略的分析了以太坊交易如何在以太坊虛擬機中執行的過程。期間還有許多細節需要仔細去理解。
另外 jump_table.go和instractions.go是虛擬機字節碼的具體實現,請自行翻閱
在vm/evm.go下面還有CallCode、DelegateCall、StaticCall方法不允許直接調用,而是在字節碼層面間接調用,需要了解的請自行翻閱。邏輯和call類似