学习文档链接: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. 死磕以太坊源码分析之EVM指令集

    死磕以太坊源码分析之EVM指令集 配合以下代码进行阅读:https://github.com/blockchainGuide/ 写文不易,给个小关注,有什么问题可以指出,便于大家交流学习. 以下指令集 ...

  7. 死磕以太坊源码分析之EVM固定长度数据类型表示

    死磕以太坊源码分析之EVM固定长度数据类型表示 配合以下代码进行阅读:https://github.com/blockchainGuide/ 写文不易,给个小关注,有什么问题可以指出,便于大家交流学习 ...

  8. 死磕以太坊源码分析之EVM动态数据类型

    死磕以太坊源码分析之EVM动态数据类型 配合以下代码进行阅读:https://github.com/blockchainGuide/ 写文不易,给个小关注,有什么问题可以指出,便于大家交流学习. So ...

  9. 死磕以太坊源码分析之EVM如何调用ABI编码的外部方法

    死磕以太坊源码分析之EVM如何调用ABI编码的外部方法 配合以下代码进行阅读:https://github.com/blockchainGuide/ 写文不易,给个小关注,有什么问题可以指出,便于大家 ...

随机推荐

  1. selenium启动chrome出错处理:Message: 'chromedriver' executable needs to be in PATH

  2. MySQL数据库不识别server=.而是识别localhost

    MySQL数据库不识别server=.而是识别localhost

  3. NYOJ-487月老的烦恼(1)类似于素数筛法一样的打表及一种筛法核心代码。。

    月老的烦恼(1) 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 月老最近遇到了一个很棘手的问题,就是"剩男""剩女"急速增长,而 ...

  4. 【Kubernetes】Kubernetes删除namespace后持续terminating状态

    删除isti和foo的配置文件之后,namespace持续terminating状态,此时也无法再创建istio-system的namespace namespace "istio-syst ...

  5. K/3Cloud二次开发基于WebDev附加进程调试

    大部分人在进行K/3cloud二次开发插件的调试时,选择的是附加IIS进程w3wp调试,本文给大家介绍一下基于WebDev附加进程调试,不用重启iis. 步骤如下: 1)拷贝K/3cloud产品安装目 ...

  6. 洛谷 P4720 【模板】扩展 / 卢卡斯 模板题

    扩展卢卡斯定理 : https://www.luogu.org/problemnew/show/P4720 卢卡斯定理:https://www.luogu.org/problemnew/show/P3 ...

  7. HDU 6333 莫队+组合数

    Problem B. Harvest of Apples Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 262144/262144 K ...

  8. linux 解压zip文件

    linux 解压zip文件 学习了:https://blog.csdn.net/hbcui1984/article/details/1583796 unzip xx.zip

  9. 推荐-zabbix原理篇

    推荐-zabbix原理篇(1) 提交 我的留言 加载中 已留言 本文大纲 snmp介绍 监控流程 开源监控工具zabbix zabbix监控功能的实现 支持数据库存储类型 Zabbix架构中的组件 Z ...

  10. Robocopy进行大量迁移

    建议使用 Windows Server 2012 R2 或 Windows Server 2012 随附的 Robocopy.exe 版本. 即然官方建议我们用2012或2012R2所带的Roboco ...