学习文档链接: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. 使用MyBatista----上传图像

    使用MyBatis上传图像,使用的是Oracle的数据库表,有一个TEACHER表,有7列,有1列是存储图片的,类型用BLOB,最大容量是4G,以二进制的形式写入数据库表. 建立这个表的对应实体类Te ...

  2. INFO main org.springframework.context.support.AbstractApplicationContext

    原因, spring-framework-5.0.2.RELEASE  需要使用 jdk8.

  3. Codeforces Round #211 (Div. 2)-D. Renting Bikes,二分!感谢队友出思路!

    D. Renting Bikes 读懂题后一开始和队友都以为是贪心.可是贪心又怎么贪呢..我们无法确定到底能买多少车但肯定是最便宜的前x辆.除了公共预算每个人的钱只能自己用,也无法确定每个人买哪一辆车 ...

  4. rac的不完全恢复

    模拟rac的不完全恢复,虽然小鱼对常规的完全和不完全恢复已经轻车熟路了,还是记录一个不完全恢复完整过程记录下来. 1 首先小鱼做了一个完全备份. RMAN> backup database in ...

  5. 【转】关于大型网站技术演进的思考(二十)--网站静态化处理—web前端优化—中(12)

    Web前端很多优化原则都是从如何提升网络通讯效率的角度提出的,但是这些原则使用的时候还是有很多陷阱在里面,如果我们不能深入理解这些优化原则背后所隐藏的技术原理,很有可能掉进这些陷阱里,最终没有达到最佳 ...

  6. [NOIP1998] 提高组 洛谷P1012 拼数

    题目描述 设有n个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数. 例如:n=3时,3个整数13,312,343联接成的最大整数为:34331213 又如:n=4时,4个整数7,13,4 ...

  7. msp430入门编程41

    msp430中C语言的软件工程--状态机建模

  8. UVA 10200 Prime Time【暴力,精度】

    题目链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_proble ...

  9. HDU 5667 Sequence

    指数有递推式,可以通过矩阵快速幂来求解.再用下面这公式快速幂取模即可. (C是素数) #include<cstdio> #include<cstring> #include&l ...

  10. topcoder srm 552

    div1 250pt: 题意:用RGB三种颜色的球摆N层的三角形,要求相邻的不同色,给出RGB的数量,问最多能摆几个 解法:三种颜色的数量要么是全一样,要么是两个一样,另外一个比他们多一个,于是可以分 ...