以太坊源码学习 -- EVM
生活随笔
收集整理的這篇文章主要介紹了
以太坊源码学习 -- EVM
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
以太坊源碼學(xué)習(xí) – EVM
學(xué)習(xí)文檔鏈接:here
一、虛擬機外
主要功能:
執(zhí)行前將Transaction類型轉(zhuǎn)化成Message,創(chuàng)建虛擬機(EVM)對象,計算一些Gas消耗,以及執(zhí)行交易完畢后創(chuàng)建收據(jù)(Receipt)對象并返回- 1
- 2
1.1 入口 和 返回值
文件:/core/state_processor.go --- Process()for i, tx := range block.Transactions() {statedb.Prepare(tx.Hash(), block.Hash(), i)receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, totalUsedGas, cfg)if err != nil {return nil, nil, nil, err}receipts = append(receipts, receipt)allLogs = append(allLogs, receipt.Logs...) }//將block里面所有的tx逐個遍歷執(zhí)行,ApplyTransaction, 每次執(zhí)行完返回一個收據(jù)(Receipt)對象- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我們來看下Receipt結(jié)構(gòu)體:
type Receipt struct {// Consensus fieldsPostState []byte `json:"root"`Failed bool `json:"failed"`CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"`Bloom Bloom `json:"logsBloom" gencodec:"required"`Logs []*Log `json:"logs" gencodec:"required"`// Implementation fields (don't reorder!)TxHash common.Hash `json:"transactionHash" gencodec:"required"`ContractAddress common.Address `json:"contractAddress"`GasUsed *big.Int `json:"gasUsed" gencodec:"required"` }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
解釋:
Logs: Log類型的數(shù)組,其中每一個Log對象記錄了Tx中一小步的操作。所以,每一個tx的執(zhí)行結(jié)果,由一個Receipt對象來表示;更詳細的內(nèi)容,由一組Log對象來記錄。這個Log數(shù)組很重要,比如在不同Ethereum節(jié)點(Node)的相互同步過程中,待同步區(qū)塊的Log數(shù)組有助于驗證同步中收到的block是否正確和完整,所以會被單獨同步(傳輸)。PostState: 保存了創(chuàng)建該Receipt對象時,整個Block內(nèi)所有“帳戶”的當時狀態(tài)。Ethereum 里用stateObject來表示一個賬戶Account,這個賬戶可轉(zhuǎn)帳(transfer value), 可執(zhí)行tx, 它的唯一標示符是一個Address類型變量。 這個Receipt.PostState 就是當時所在Block里所有stateObject對象的RLP Hash值。Bloom: Ethereum內(nèi)部實現(xiàn)的一個256bit長Bloom Filter。 Bloom Filter概念定義可見wikipedia,它可用來快速驗證一個新收到的對象是否處于一個已知的大量對象集合之中。這里Receipt的Bloom,被用以驗證某個給定的Log是否處于Receipt已有的Log數(shù)組中。- 1
- 2
- 3
- 4
- 5
- 6
1.2 封裝EVM對象和Message對象
我們來看一下ApplyTransaction():
文件:/core/state_processor.go --- ApplyTransaction()//=====Message對象===== msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, nil, err }//=====EVM對象===== context := NewEVMContext(msg, header, bc, author) vmenv := vm.NewEVM(context, statedb, config, cfg)//完成tx的執(zhí)行 _, gas, failed, err := ApplyMessage(vmenv, msg, gp)//創(chuàng)建一個收據(jù)Receipt對象,最后返回該Recetip對象,以及整個tx執(zhí)行過程所消耗Gas數(shù)量。 ...- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
我們來看一下ApplyMessage()
文件:/core/state_transition.go --- ApplyMessage()//發(fā)現(xiàn)調(diào)用了TransitionDb() , _, gasUsed, failed, err := st.TransitionDb()- 1
- 2
- 3
- 4
我們來看一下TransitionDb()
文件:/core/state_transition.go --- TransitionDb() //購買gas //計算tx固有g(shù)as //EVM執(zhí)行 //計算本次執(zhí)行交易的實際gas消耗 //償退gas //獎勵所屬區(qū)塊的挖掘者- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
二、 虛擬機內(nèi)
包括執(zhí)行轉(zhuǎn)帳,和創(chuàng)建合約并執(zhí)行合約的指令數(shù)組
2.1 EVM結(jié)構(gòu)體
我們來看一下EVM的結(jié)構(gòu)體:
文件:/core/vm/evm.gotype EVM struct {Context --攜帶輔助信息:Transaction的信息(GasPrice, GasLimit),Block的信息(Number, Difficulty),以及轉(zhuǎn)帳函數(shù)等StateDB StateDB --為EVM提供statedb的相關(guān)操作depth intchainConfig *params.ChainConfigchainRules params.RulesvmConfig Configinterpreter *Interpreter --解釋器,用來解釋執(zhí)行EVM中合約的指令abort int32 }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
2.2 完成轉(zhuǎn)賬
交易的轉(zhuǎn)帳操作由Context對象中的TransferFunc類型函數(shù)來實現(xiàn),類似的函數(shù)類型,還有CanTransferFunc, 和GetHashFunc。- 1
- 2
- 1
- 2
- 3
- 4
- 5
- 6
2.3 合約的創(chuàng)建、賦值
我們先來看一下contract 結(jié)構(gòu)體
文件:/core/vm/contract.go type Contract struct {CallerAddress common.Addresscaller ContractRef //轉(zhuǎn)賬轉(zhuǎn)出方地址self ContractRef //轉(zhuǎn)入方地址jumpdests destinations // result of JUMPDEST analysis.Code []byte //指令數(shù)組,其中每一個byte都對應(yīng)于一個預(yù)定義的虛擬機指令CodeHash common.HashCodeAddr *common.AddressInput []byte //數(shù)據(jù)數(shù)組,是指令所操作的數(shù)據(jù)集合Gas uint64value *big.IntArgs []byteDelegateCall bool }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
我們來看一下call()
文件:/core/vm/call.go var (to = AccountRef(addr)snapshot = evm.StateDB.Snapshot() ) if !evm.StateDB.Exist(addr) {precompiles := PrecompiledContractsHomesteadif evm.ChainConfig().IsByzantium(evm.BlockNumber) {precompiles = PrecompiledContractsByzantium}if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {return nil, gas, nil}evm.StateDB.CreateAccount(addr) }//轉(zhuǎn)賬 evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)//賦值Contract對象 contract := NewContract(caller, to, value, gas) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))//調(diào)用run,執(zhí)行該合約的指令 ret, err = run(evm, snapshot, contract, input)if err != nil {evm.StateDB.RevertToSnapshot(snapshot)if err != errExecutionReverted {contract.UseGas(contract.Gas)} } return ret, contract.Gas, err- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
2.4 預(yù)編譯合約
我們來看一下run():
文件:/core/vm/run.go if contract.CodeAddr != nil {precompiles := PrecompiledContractsHomesteadif evm.ChainConfig().IsByzantium(evm.BlockNumber) {precompiles = PrecompiledContractsByzantium}if p := precompiles[*contract.CodeAddr]; p != nil {return RunPrecompiledContract(p, input, contract)} } return evm.interpreter.Run(snapshot, contract, input)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
2.5 解釋器執(zhí)行合約的指令
我們來看一下interpreter.go
可以看到一個Config結(jié)構(gòu)體
文件:/core/vm/.interpreter.gotype Config struct {Debug boolEnableJit boolForceJit boolTracer TracerNoRecursion boolDisableGasMetering boolEnablePreimageRecording boolJumpTable [256]operation // }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
然后我們看一下interpreter.run()
文件: 文件:/core/vm/.interpreter.go --run()核心: 逐個byte遍歷入?yún)ontract對象的Code變量,將其解釋為一個已知的operation,然后依次調(diào)用該operation對象的四個函數(shù)operation在操作過程中,會需要幾個數(shù)據(jù)結(jié)構(gòu): Stack,實現(xiàn)了標準容器 -棧的行為;Memory,一個字節(jié)數(shù)組,可表示線性排列的任意數(shù)據(jù);還有一個intPool,提供對big.Int數(shù)據(jù)的存儲和讀取。需要特別注意的是LOGn指令操作,它用來創(chuàng)建n個Log對象,這里n最大是4。還記得Log在何時被用到么?每個交易(Transaction,tx)執(zhí)行完成后,會創(chuàng)建一個Receipt對象用來記錄這個交易的執(zhí)行結(jié)果。Receipt攜帶一個Log數(shù)組,用來記錄tx操作過程中的所有變動細節(jié),而這些Log,正是通過合適的LOGn指令-即合約指令數(shù)組(Contract.Code)中的單個byte,在其對應(yīng)的operation里被創(chuàng)建出來的。每個新創(chuàng)建的Log對象被緩存在StateDB中的相對應(yīng)的stateObject里,待需要時從StateDB中讀取。- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
總結(jié)
以上是生活随笔為你收集整理的以太坊源码学习 -- EVM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gRPC初体验
- 下一篇: Android通过Geth RPC接口实