学习文档链接:here

一、虚拟机外

主要功能:

执行前将Transaction类型转化成Message,创建虚拟机(EVM)对象,计算一些Gas消耗,以及执行交易完毕后创建收据(Receipt)对象并返回
  • 1

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)对象

我们来看下Receipt结构体:

type Receipt struct {
// Consensus fields
PostState []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"`
}

解释:

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 封装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数量。
...

我们来看一下ApplyMessage()

文件:/core/state_transition.go  --- ApplyMessage()

//发现调用了TransitionDb()
, _, gasUsed, failed, err := st.TransitionDb()

我们来看一下TransitionDb()

文件:/core/state_transition.go  --- TransitionDb()

//购买gas
//计算tx固有gas
//EVM执行
//计算本次执行交易的实际gas消耗
//偿退gas
//奖励所属区块的挖掘者

二、 虚拟机内

包括执行转帐,和创建合约并执行合约的指令数组

2.1 EVM结构体

我们来看一下EVM的结构体:

文件:/core/vm/evm.go

type EVM struct {

    Context  --携带辅助信息:Transaction的信息(GasPrice, GasLimit),Block的信息(Number, Difficulty),以及转帐函数等
StateDB StateDB --为EVM提供statedb的相关操作
depth int
chainConfig *params.ChainConfig
chainRules params.Rules
vmConfig Config
interpreter *Interpreter --解释器,用来解释执行EVM中合约的指令
abort int32
}

2.2 完成转账

交易的转帐操作由Context对象中的TransferFunc类型函数来实现,类似的函数类型,还有CanTransferFunc, 和GetHashFunc。
文件:/core/evm.go --Transfer()

db.SubBalance(sender, amount)  //转出账户减到一定金额以太币
db.AddBalance(recipient, amount) //转入账户增加一定金额以太币 //注意:转出和转入账户的操作不会立即生效,StateDB 并不是真正的数据库,只是一行为类似数据库的结构体它在内部以Trie的数据结构来管理各个基于地址的账户,可以理解成一个cache;当该账户的信息有变化时,变化先存储在Trie中。仅当整个Block要被插入到BlockChain时,StateDB 里缓存的所有账户的所有改动,才会被真正的提交到底层数据库。

2.3 合约的创建、赋值

我们先来看一下contract 结构体

文件:/core/vm/contract.go  

type Contract struct {
CallerAddress common.Address
caller ContractRef //转账转出方地址
self ContractRef //转入方地址 jumpdests destinations // result of JUMPDEST analysis. Code []byte //指令数组,其中每一个byte都对应于一个预定义的虚拟机指令
CodeHash common.Hash
CodeAddr *common.Address
Input []byte //数据数组,是指令所操作的数据集合 Gas uint64
value *big.Int
Args []byte
DelegateCall bool
}
创建合约: call(),create() -- 二者均在StateProcessor的ApplyTransaction()被调用以执行单个交易,并且都有调用转帐函数完成转帐。

我们来看一下call()

文件:/core/vm/call.go  

var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead
if 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

2.4 预编译合约

我们来看一下run():

文件:/core/vm/run.go  

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)
}
}
return evm.interpreter.Run(snapshot, contract, input)
可见如果待执行的Contract对象恰好属于一组预编译的合约集合-此时以指令地址CodeAddr为匹配项-那么它可以直接运行;没有经过预编译的Contract,才会由Interpreter解释执行。这里的"预编译",可理解为不需要编译(解释)指令(Code)。预编译的合约,其逻辑全部固定且已知,所以执行中不再需要Code,仅需Input即可。

在代码实现中,预编译合约只需实现两个方法Required()和Run()即可,这两方法仅需一个入参input。

2.5 解释器执行合约的指令

我们来看一下interpreter.go

可以看到一个Config结构体

文件:/core/vm/.interpreter.go

type Config struct {
Debug bool
EnableJit bool
ForceJit bool
Tracer Tracer
NoRecursion bool
DisableGasMetering bool
EnablePreimageRecording bool
JumpTable [256]operation //
}
operation: 每个operation对象正对应一个已定义的虚拟机指令,它所含有的四个函数变量execute, gasCost, validateStack, memorySize 提供了这个虚拟机指令所代表的所有操作。每个指令长度1byte,Contract对象的成员变量Code类型为[]byte,就是这些虚拟机指令的任意集合。operation对象的函数操作,主要会用到Stack,Memory, IntPool 这几个自定义的数据结构。

然后我们看一下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中读取。
 

以太坊源码学习 – EVM的更多相关文章

  1. 以太坊的crypto模块--以太坊源码学习

    以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp2 ...

  2. 以太坊系列之三: 以太坊的crypto模块--以太坊源码学习

    以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp2 ...

  3. 以太坊系列之一: 以太坊RLP用法-以太坊源码学习

    RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式.RLP的唯一目标就是解决结构体的编码问题:对原子数据类型(比如,字符串,整数型, ...

  4. 以太坊系列之六: p2p模块--以太坊源码学习

    p2p模块 p2p模块对外暴露了Server关键结构,帮助上层管理复杂的p2p网路,使其集中于Protocol的实现,只关注于数据的传输. Server使用discover模块,在指定的UDP端口管理 ...

  5. 以太坊系列之五: p2p的nat模块--以太坊源码学习

    p2p的nat模块 该模块相对比较简单,因为nat的真正实现并不在此模块,主要是使用了第三方的nat-upnp和nat-pmp来实现真正的穿透(端口映射). 对外公布的接口 ```go // An i ...

  6. Geth命令用法-参数详解 and 以太坊源码文件目录

    本文是对以太坊客户端geth命令的解析 命令用法 geth [选项] 命令 [命令选项] [参数-] 版本 1.7.3-stable 命令 account 管理账户 attach 启动交互式JavaS ...

  7. 以太坊系列之二: 单调时间monotime-以太坊源码学习

    在程序中需要测量时间时最好使用monotime.Now()而不是time.Now(),相比之下前者更准确. 来个示例: func main() { var start, elapsed time.Du ...

  8. 以太坊源码分析(52)以太坊fast sync算法

    this PR aggregates a lot of small modifications to core, trie, eth and other packages to collectivel ...

  9. 以太坊系列之七: p2p模块的dial--以太坊源码学习

    dial.go阅读手记 dial.go是负责和peer建立连接关系的地方,主要是实现 type dialer interface { /* peers已经有的结点 */ newTasks(runnin ...

随机推荐

  1. 最好的cpm广告联盟哪里有

    最好的cpm广告联盟哪里有,58传媒广告联盟还要提醒众位站长的是网站在经营发展中必须最大化的扩展自己的优势力量.每个网站都有属于自己的优势魅力,这些优势特点只有得到最大化的发挥才能为网站带来意想不到的 ...

  2. Mysql中的视图

    什么是视图 通俗的讲,视图就是一条SELECT语句执行后返回的结果集.所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上. 视图的特性 视图是对若干张基本表的引用,一张虚表,查询语句执 ...

  3. Jqueryの锋利的jquery练习

    $(function(){ $("div.SubCategoryBox li:gt(7):not(:last)").hide(); $("div.SubCategoryB ...

  4. Cisco路由器的6种模式

    Cisco路由器的6种模式 -------------------------------------------------------------------------------------- ...

  5. 启动android程序和虚拟机时候出现如下错误的解决方法

    启动android程序和虚拟机时候出现如下错误的解决方法. 错误重现: [2011-07-13 16:22:48 - Emulator] invalid command-line parameter: ...

  6. [译]Java设计模式之解释器

    (文章翻译自Java Design Pattern: Interpreter) 解释器模式适用于当一些内容需要翻译的时候.下面的例子是一个非常简单的解释器实现.它将字母"a"和&q ...

  7. WAMPServer多站点配置方法

    WAMPServer多站点配置方法:1.在C:\wamp\www 新建文件夹test01,在里面新建index.php,内容为 "Hello Test01". 2.C:\wamp\ ...

  8. 爬虫系列(1)-----python爬取猫眼电影top100榜

    对于Python初学者来说,爬虫技能是应该是最好入门,也是最能够有让自己有成就感的,今天在整理代码时,整理了一下之前自己学习爬虫的一些代码,今天先上一个简单的例子,手把手教你入门Python爬虫,爬取 ...

  9. MyX5TbsPlusDemo【体验腾讯浏览服务Android SDK (TbsPlus 版)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 按照官网上的说明:只需接入aar文件和调用一个接口即可完成TBS接入,我们会通过全屏Activity展示TBS WebView,适用 ...

  10. [C#]typeof,Gettype()和is的区别

    typeof 参数是一个类型名称,比如你自己编写的一个类 GetType()是类的方法,继承自object,返回该实例的类型 is 用来检测实例的兼容性(是否可以相互转换) 例: class Anim ...