以太坊源码学习 -- EVM
以太坊源码学习 – EVM
学习文档链接:here
一、虚拟机外
主要功能:
执行前将Transaction类型转化成Message,创建虚拟机(EVM)对象,计算一些Gas消耗,以及执行交易完毕后创建收据(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逐个遍历执行,ApplyTransaction, 每次执行完返回一个收据(Receipt)对象
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们来看下Receipt结构体:
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类型的数组,其中每一个Log对象记录了Tx中一小步的操作。所以,每一个tx的执行结果,由一个Receipt对象来表示;更详细的内容,由一组Log对象来记录。这个Log数组很重要,比如在不同Ethereum节点(Node)的相互同步过程中,待同步区块的Log数组有助于验证同步中收到的block是否正确和完整,所以会被单独同步(传输)。PostState: 保存了创建该Receipt对象时,整个Block内所有“帐户”的当时状态。Ethereum 里用stateObject来表示一个账户Account,这个账户可转帐(transfer value), 可执行tx, 它的唯一标示符是一个Address类型变量。 这个Receipt.PostState 就是当时所在Block里所有stateObject对象的RLP Hash值。Bloom: Ethereum内部实现的一个256bit长Bloom Filter。 Bloom Filter概念定义可见wikipedia,它可用来快速验证一个新收到的对象是否处于一个已知的大量对象集合之中。这里Receipt的Bloom,被用以验证某个给定的Log是否处于Receipt已有的Log数组中。
- 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的执行
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)//创建一个收据Receipt对象,最后返回该Recetip对象,以及整个tx执行过程所消耗Gas数量。
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
我们来看一下ApplyMessage()
文件:/core/state_transition.go --- ApplyMessage()//发现调用了TransitionDb()
, _, gasUsed, failed, err := st.TransitionDb()
- 1
- 2
- 3
- 4
我们来看一下TransitionDb()
文件:/core/state_transition.go --- TransitionDb()
//购买gas
//计算tx固有gas
//EVM执行
//计算本次执行交易的实际gas消耗
//偿退gas
//奖励所属区块的挖掘者
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
二、 虚拟机内
包括执行转帐,和创建合约并执行合约的指令数组
2.1 EVM结构体
我们来看一下EVM的结构体:
文件:/core/vm/evm.gotype EVM struct {Context --携带辅助信息:Transaction的信息(GasPrice, GasLimit),Block的信息(Number, Difficulty),以及转帐函数等StateDB StateDB --为EVM提供statedb的相关操作depth intchainConfig *params.ChainConfigchainRules params.RulesvmConfig Configinterpreter *Interpreter --解释器,用来解释执行EVM中合约的指令abort int32
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
2.2 完成转账
交易的转帐操作由Context对象中的TransferFunc类型函数来实现,类似的函数类型,还有CanTransferFunc, 和GetHashFunc。
- 1
- 2
文件:/core/evm.go --Transfer()db.SubBalance(sender, amount) //转出账户减到一定金额以太币
db.AddBalance(recipient, amount) //转入账户增加一定金额以太币
//注意:转出和转入账户的操作不会立即生效,StateDB 并不是真正的数据库,只是一行为类似数据库的结构体它在内部以Trie的数据结构来管理各个基于地址的账户,可以理解成一个cache;当该账户的信息有变化时,变化先存储在Trie中。仅当整个Block要被插入到BlockChain时,StateDB 里缓存的所有账户的所有改动,才会被真正的提交到底层数据库。
- 1
- 2
- 3
- 4
- 5
- 6
2.3 合约的创建、赋值
我们先来看一下contract 结构体
文件:/core/vm/contract.go type Contract struct {CallerAddress common.Addresscaller ContractRef //转账转出方地址self ContractRef //转入方地址jumpdests destinations // result of JUMPDEST analysis.Code []byte //指令数组,其中每一个byte都对应于一个预定义的虚拟机指令CodeHash common.HashCodeAddr *common.AddressInput []byte //数据数组,是指令所操作的数据集合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
创建合约: call(),create() -- 二者均在StateProcessor的ApplyTransaction()被调用以执行单个交易,并且都有调用转帐函数完成转帐。
- 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)
}//转账
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))//调用run,执行该合约的指令
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 预编译合约
我们来看一下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
可见如果待执行的Contract对象恰好属于一组预编译的合约集合-此时以指令地址CodeAddr为匹配项-那么它可以直接运行;没有经过预编译的Contract,才会由Interpreter解释执行。这里的"预编译",可理解为不需要编译(解释)指令(Code)。预编译的合约,其逻辑全部固定且已知,所以执行中不再需要Code,仅需Input即可。在代码实现中,预编译合约只需实现两个方法Required()和Run()即可,这两方法仅需一个入参input。
- 1
- 2
- 3
- 4
2.5 解释器执行合约的指令
我们来看一下interpreter.go
可以看到一个Config结构体
文件:/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
operation: 每个operation对象正对应一个已定义的虚拟机指令,它所含有的四个函数变量execute, gasCost, validateStack, memorySize 提供了这个虚拟机指令所代表的所有操作。每个指令长度1byte,Contract对象的成员变量Code类型为[]byte,就是这些虚拟机指令的任意集合。operation对象的函数操作,主要会用到Stack,Memory, IntPool 这几个自定义的数据结构。
- 1
- 2
然后我们看一下interpreter.run()
文件: 文件:/core/vm/.interpreter.go --run()核心: 逐个byte遍历入参Contract对象的Code变量,将其解释为一个已知的operation,然后依次调用该operation对象的四个函数operation在操作过程中,会需要几个数据结构: Stack,实现了标准容器 -栈的行为;Memory,一个字节数组,可表示线性排列的任意数据;还有一个intPool,提供对big.Int数据的存储和读取。需要特别注意的是LOGn指令操作,它用来创建n个Log对象,这里n最大是4。还记得Log在何时被用到么?每个交易(Transaction,tx)执行完成后,会创建一个Receipt对象用来记录这个交易的执行结果。Receipt携带一个Log数组,用来记录tx操作过程中的所有变动细节,而这些Log,正是通过合适的LOGn指令-即合约指令数组(Contract.Code)中的单个byte,在其对应的operation里被创建出来的。每个新创建的Log对象被缓存在StateDB中的相对应的stateObject里,待需要时从StateDB中读取。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
以太坊源码学习 -- EVM相关推荐
- 以太坊源码学习(一) 正本清源
以太坊源码学习(一)正本清源 背景 geth源码一直在不断增加,优化,发展到现在已经非常庞大,第一次看geth源码,会有不小的难度.虽然如此,还是可以从geth仓库的第一个commit开始,这时的代码 ...
- 以太坊源码学习(一)
转载自:https://blog.csdn.net/karizhang/article/details/79110981 背景 geth源码一直在不断增加,优化,发展到现在已经非常庞大,第一次看get ...
- 以太坊源码学习—RLP编码
[原文]:https://segmentfault.com/a/1190000011763339 RLP(Recursive Length Prefix),中文翻译过来叫递归长度前缀编码,它是以太坊序 ...
- kademlia java_死磕以太坊源码分析之Kademlia算法
死磕以太坊源码分析之Kademlia算法 KAD 算法概述 Kademlia是一种点对点分布式哈希表(DHT),它在容易出错的环境中也具有可证明的一致性和性能.使用一种基于异或指标的拓扑结构来路由查询 ...
- 3 v4 中心节点固定_死磕以太坊源码分析之p2p节点发现
死磕以太坊源码分析之p2p节点发现 在阅读节点发现源码之前必须要理解kadmilia算法,可以参考:KAD算法详解. 节点发现概述 节点发现,使本地节点得知其他节点的信息,进而加入到p2p网络中. 以 ...
- 以太坊源码分析-交易
以太坊源码分析-交易 机理 先说一点区块链转账的基本概念和流程 用户输入转账的地址和转入的地址和转出的金额 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行) 系统对交易信 ...
- php区块链以太坊,兄弟连区块链教程以太坊源码分析CMD深入分析(一)
兄弟连区块链教程以太坊源码分析CMD深入分析. cmd包分析 cmd下面总共有13个子包,除了util包之外,每个子包都有一个主函数,每个主函数的init方法中都定义了该主函数支持的命令,如 geth ...
- 以太坊源码阅读3——MPT原理
以太坊源码阅读3--MPT原理 介绍 MPT(Merkel-Patricia Tree),翻译为梅克尔-帕特里夏树 MPT提供了一个基于密码学验证的底层数据结构,用来存储键值对( key-value) ...
- 以太坊源码阅读2——RLP编码
以太坊源码阅读2--RLP编码 RLP介绍 目前网上的资料都是RLP(Recursive Length prefix),叫递归长度前缀编码,但目前源码的doc.go的第20行里面的注释写的是 The ...
最新文章
- 怎样新建Oracle数据库
- 第五天2017/04/06(上午:文件的“读写操作”)
- java 线程状态_Java线程为何没有Running状态?我猜你不知道。
- 对mysql explain讲的比较清楚的
- discuz nt 安装,完全攻略。
- mysql insert 锁
- jq之hover()
- mysql介质故障_pciessd异常readonly致mysql反复crash
- Ubuntu把家目录文件夹名称改为英文
- C++实现Behavioral - Observer模式 (转)
- Atitit it sftwr dev 原则准则 principle 目录 第一章 简单原则 kiss	1 第一节 . You Ain’t Gonna Need It(YAGNI)避免过度设计	1
- 软件测试日志怎么写,为什么要进行日志测试和如何进行日志测试?
- java print打印方法
- asp文件解密 加密
- 企业上云规划与云原生环境设计
- 科幻电影里的机器人假肢已经照进现实
- poj1753 解题思路
- 服务器安装嵌入式系统,嵌入式设备连接云服务器
- Linux多功能下载机(Arias2)
- Jav8 HashMap-putVal() 方法分析