以太坊源码分析(18)以太坊交易执行分析

#以太坊交易执行分析

在这里,将其整体串起来,从state_processor.Process函数开始,归纳一下其所作的处理。

##1 Process
Process 根据以太坊规则运行交易信息来对statedb进行状态改变,以及奖励挖矿者或者是其他的叔父节点。
Process返回执行过程中累计的收据和日志,并返回过程中使用的Gas。 如果由于Gas不足而导致任何交易执行失败,将返回错误。
**处理逻辑:**
~~~
1. 定义及初始化收据、耗费的gas、区块头、日志、gas池等变量;
2. 如果是DAO事件硬分叉相关的处理,则调用misc.ApplyDAOHardFork(statedb)执行处理;
3. 对区块中的每一个交易,进行迭代处理;处理逻辑:
a. 对当前交易做预处理,设置交易的hash、索引、区块hash,供EVM发布新的状态日志使用;
b. 执行ApplyTransaction,获取收据;
c. 若上一步出错,中断整个Process,返回错误;
d. 若正常,累积记录收据及日志。循环进入下一个交易的处理。
4. 调用共识模块做Finalize处理;
5. 返回所有的收据、日志、总共使用的gas。
~~~

##2 ApplyTransaction(1.3.b )
ApplyTransaction尝试将交易应用于给定的状态数据库,并使用输入参数作为其环境。
它返回交易的收据,使用的Gas和错误,如果交易失败,表明块是无效的。
**处理逻辑:**
~~~
1. 将types.Transaction结构变量转为core.Message对象;这过程中会对发送者做签名验证,并获得发送者的地址缓存起来;
2. 创建新的上下文(Context),此上下文将在EVM 环境(EVM environment)中使用;上下文中包含msg,区块头、区块指针、作者(挖矿者、获益者);
3. 创建新的EVM environment,其中包括了交易相关的所有信息以及调用机制;
4. ApplyMessage, 将交易应用于当前的状态中,也就是执行状态转换,新的状态包含在环境对象中;得到执行结果以及花费的gas;
5. 判断是否分叉情况( `config.IsByzantium(header.Number)` ),如果不是,获取当前的statedb的状态树根哈希;
6. 创建一个收据, 用来存储中间状态的root, 以及交易使用的gas;
7. 如果是创建合约的交易,那么我们把创建地址存储到收据里面;
8. 拿到所有的日志并创建日志的布隆过滤器;返回。
~~~

##3 ApplyMessage(2.4)
ApplyMessage将交易应用于当前的状态中,代码里就是创建了一个StateTransition然后调用其TransitionDb()方法。
ApplyMessage返回由任何EVM执行(如果发生)返回的字节(但这个返回值在ApplyTransaction中被忽略了),
使用的Gas(包括Gas退款),如果失败则返回错误。 一个错误总是表示一个核心错误,
意味着这个消息对于这个特定的状态将总是失败,并且永远不会在一个块中被接受。

##4 StateTransition.TransitionDb()
~~~
1. 预检查,出错则函数返回;
a. 检查交易的Nonce值是否合规;
b. buyGas:根据发送者定的gaslimit和GasPrice,从发送者余额中扣除以太币;从区块gas池中减掉本次gas;并对运行环境做好更新;
2. 支付固定费用 intrinsic gas;
3. 如果是合约创建, 那么调用evm的Create方法创建新的合约,使用交易的data作为新合约的部署代码;
4. 否则不是合约创建,增加发送者的Nonce值,然后调用evm.Call执行交易;
5. 计算并执行退款,将退回的gas对应的以太币退回给交易发送者。
~~~

###4.3 evm.Create创建新的合约
~~~
1. 检查执行深度,若超过params.CallCreateDepth(即1024)就出错返回;刚开始的执行深度为0,肯定继续往下执行;
2. 检查是否可执行转账,即检查账户余额是否≥要转账的数额;
3. 发送者Nonce加1;
4. 创建合约地址并获取hash,若该合约地址已存在,或不合法(空),则出错返回;
5. 保存statedb快照,然后根据合约地址创建账户;
6. 执行转账evm.Transfer(在statedb中,将value所代表的以太币从发送者账户转到新合约账户);
7. 根据发送者、前面创建的合约账户,转账的钱,已用的gas创建并初始化合约;将交易的data作为合约的代码;
8. 运行前一步创建的合约
9. 判断运行结果是否有错误。如果合约成功运行并且没有错误返回,则计算存储返回数据所需的GAS。 如果由于没有足够的GAS而导致返回值不能被存储则设置错误,并通过下面的错误检查条件来处理。
10. 若EVM返回错误或上述存储返回值出现错误,则回滚到快照的状态,并且消耗完剩下的所有gas。
~~~

###4.4 evm.Call执行交易
Call方法, 无论我们转账或者是执行合约代码都会调用到这里, 同时合约里面的call指令也会执行到这里。
Call方法和evm.Create的逻辑类似,但少了一些步骤。
~~~
1. 检查是否允许递归执行以及执行深度,若深度超过params.CallCreateDepth(即1024)就出错返回;
2. 检查是否可执行转账,即检查账户余额是否≥要转账的数额;
3. 保存statedb快照,创建接收者账户;
4. 如果接收者在statedb中尚不存在,则执行precompiles预编译,与编译结果为nil时出错返回;无错误则在statedb中创建接收者账户;
5. 执行转账;
6. 根据发送者、接收者,转账的钱,已用的gas创建并初始化合约;将交易的data作为合约的代码;
7. 运行前一步创建的合约
8. 若EVM返回错误,则回滚到快照的状态,并且消耗完剩下的所有gas。
~~~

虚拟机中合约的执行另行分析。

### eth源码交易发送接收,校验存储分析:

```
创建合约指的是将合约部署到区块链上,这也是通过发送交易来实现。在创建合约的交易中,to字段要留空不填,在data字段中指定合约的二进制代码,
from字段是交易的发送者也是合约的创建者。

执行合约的交易

调用合约中的方法,需要将交易的to字段指定为要调用的合约的地址,通过data字段指定要调用的方法以及向该方法传递的参数。

所有对账户的变动操作都会先提交到stateDB里面,这个类似一个行为数据库,或者是缓存,最终执行需要提交到底层的数据库当中,底层数据库是levelDB(K,V数据库)

core/interface.go定义了stateDB的接口

ProtocolManager主要成员包括:
peertSet{}类型成员用来缓存相邻个体列表,peer{}表示网络中的一个远端个体。
通过各种通道(chan)和事件订阅(subscription)的方式,接收和发送包括交易和区块在内的数据更新。当然在应用中,订阅也往往利用通道来实现事件通知。
ProtocolManager用到的这些通道的另一端,可能是其他的个体peer,也可能是系统内单例的数据源比如txPool,或者是事件订阅的管理者比如event.Mux。
Fetcher类型成员累积所有其他个体发送来的有关新数据的宣布消息,并在自身对照后,安排相应的获取请求。
Downloader类型成员负责所有向相邻个体主动发起的同步流程。

func(pm *ProtocolManager) Start()

以上这四段相对独立的业务流程的逻辑分别是:
1.广播新出现的交易对象。txBroadcastLoop()会在txCh通道的收端持续等待,一旦接收到有关新交易的事件,会立即调用BroadcastTx()函数广播给那些尚无该交易对象的相邻个体。
2.广播新挖掘出的区块。minedBroadcastLoop()持续等待本个体的新挖掘出区块事件,然后立即广播给需要的相邻个体。当不再订阅新挖掘区块事件时,这个函数才会结束等待并返回。很有意思的是,在收到新挖掘出区块事件后,minedBroadcastLoop()会连续调用两次BroadcastBlock(),两次调用仅仅一个bool型参数@propagate不一样,当该参数为true时,会将整个新区块依次发给相邻区块中的一小部分;而当其为false时,仅仅将新区块的Hash值和Number发送给所有相邻列表。
3.定时与相邻个体进行区块全链的强制同步。syncer()首先启动fetcher成员,然后进入一个无限循环,每次循环中都会向相邻peer列表中“最优”的那个peer作一次区块全链同步。发起上述同步的理由分两种:如果有新登记(加入)的相邻个体,则在整个peer列表数目大于5时,发起之;如果没有新peer到达,则以10s为间隔定时的发起之。这里所谓"最优"指的是peer中所维护区块链的TotalDifficulty(td)最高,由于Td是全链中从创世块到最新头块的Difficulty值总和,所以Td值最高就意味着它的区块链是最新的,跟这样的peer作区块全链同步,显然改动量是最小的,此即"最优"。
4.将新出现的交易对象均匀的同步给相邻个体。txsyncLoop()主体也是一个无限循环,它的逻辑稍微复杂一些:首先有一个数据类型txsync{p, txs},包含peer和tx列表;通道txsyncCh用来接收txsync{}对象;txsyncLoop()每次循环时,如果从通道txsyncCh中收到新数据,则将它存入一个本地map[]结构,k为peer.ID,v为txsync{},并将这组tx对象发送给这个peer;每次向peer发送tx对象的上限数目100*1024,如果txsync{}对象中有剩余tx,则该txsync{}对象继续存入map[]并更新tx数目;如果本次循环没有新到达txsync{},则从map[]结构中随机找出一个txsync对象,将其中的tx组发送给相应的peer,重复以上循环。

以上四段流程就是ProtocolManager向相邻peer主动发起的通信过程。尽管上述各函数细节从文字阅读起来容易模糊,不过最重要的内容还是值得留意下的:本个体(peer)向其他peer主动发起的通信中,按照数据类型可分两类:交易tx和区块block;而按照通信方式划分,亦可分为广播新的单个数据和同步一组同类型数据,这样简单的两两配对,便可组成上述四段流程。

在上文的介绍中,出现了多处有关p2p通信协议的结构类型,比如eth.peer,p2p.Peer,Server等等。这里不妨对这些p2p通信协议族的结构一并作个总解。以太坊中用到的p2p通信协议族的结构类型,大致可分为三层:

第一层处于pkg eth中,可以直接被eth.Ethereum,eth.ProtocolManager等顶层管理模块使用,在类型声明上也明显考虑了eth.Ethereum的使用特点。典型的有eth.peer{}, eth.peerSet{},其中peerSet是peer的集合类型,而eth.peer代表了远端通信对象和其所有通信操作,它封装更底层的p2p.Peer对象以及读写通道等。
第二层属于pkg p2p,可认为是泛化的p2p通信结构,比较典型的结构类型包括代表远端通信对象的p2p.Peer{}, 封装自更底层连接对象的conn{},通信用通道对象protoRW{}, 以及启动监听、处理新加入连接或断开连接的Server{}。这一层中,各种数据类型的界限比较清晰,尽量不出现揉杂的情况,这也是泛化结构的需求。值得关注的是p2p.Protocol{},它应该是针对上层应用特意开辟的类型,主要作用包括容纳应用程序所要求的回调函数等,并通过p2p.Server{}在新连接建立后,将其传递给通信对象peer。从这个类型所起的作用来看,命名为Protocol还是比较贴切的,尽管不应将其与TCP/IP协议等既有概念混淆。
第三层处于golang自带的网络代码包中,也可分为两部分:第一部分pkg net,包括代表网络连接的<Conn>接口,代表网络地址的<Addr>以及它们的实现类;第二部分pkg syscall,包括更底层的网络相关系统调用类等,可视为封装了网络层(IP)和传输层(TCP)协议的系统实现。

```



```
Receiptroot我们刚刚在区块头有看到,那他具体包含的是什么呢?它是一个交易的结果,主要包括了poststate,交易所花费的gas,bloom和logs

blockchain无结构化查询需求,仅hash查询,key/value数据库最方便,底层用levelDB存储,性能好

stateDB用来存储世界状态
Core/state/statedb.go

注意:1. StateDB完整记录Transaction的执行情况; 2. StateDB的重点是StateObjects; 3. StateDB中的 stateObjects,Account的Address为 key,记录其Balance、nonce、code、codeHash ,以及tire中的 {string:Hash}等信息;

所有的结构凑明朗了,那具体的验证过程是怎么样的呢
Core/state_processor.go
Core/state_transition.go
Core/block_validator.go

StateProcessor 1. 调用StateTransition,验证(执行)Transaction; 2. 计算Gas、Recipt、Uncle Reward

StateTransition
1. 验证(执行)Transaction;
3. 扣除transaction.data.payload计算数据所需要消耗的gas;
4. 在vm中执行code(生成contract or 执行contract);vm执 行过程中,其gas会被自动消耗。如果gas不足,vm会自 选退出;
5. 将多余的gas退回到sender.balance中;
6. 将消耗的gas换成balance加到当前env.Coinbase()中;

BlockValidator
1. 验证UsedGas
2. 验证Bloom
3. 验证receiptSha
4. 验证stateDB.IntermediateRoot

/core/vm/evm.go
交易的转帐操作由Context对象中的TransferFunc类型函数来实现,类似的函数类型,还有CanTransferFunc, 和GetHashFunc。
core/vm/contract.go
合约是evm用来执行指令的结构体

入口:/cmd/geth/main.go/main

```
#EVM分析

>EVM不能被重用,非线程安全

Context结构体:为EVM提供辅助信息。一旦提供,不应更改。
~~~
// Context 为EVM提供辅助信息。一旦提供,不应更改。
type Context struct {
    // CanTransfer 返回 账户是否拥有足够的以太币以执行转账 CanTransfer CanTransferFunc
    // Transfer 转账函数,将以太币从一个账户转到另一个账户
    Transfer TransferFunc
    // GetHash 返回n对应的哈希
    GetHash GetHashFunc

    // Message information
    Origin common.Address // Provides information for ORIGIN
    GasPrice *big.Int // Provides information for GASPRICE

    // Block information
    Coinbase common.Address // Provides information for COINBASE
    GasLimit uint64 // Provides information for GASLIMIT
    BlockNumber *big.Int // Provides information for NUMBER
    Time *big.Int // Provides information for TIME
    Difficulty *big.Int // Provides information for DIFFICULTY
}
~~~

> state_processor.Process开始执行交易处理,就是在那里为入口进入到evm的执行的,具体见[core-state-process-analysis.md](core-state-process-analysis.md)


##EVM的实现
以太坊的EVM整个完全是自己实现的,能够直接执行Solidity字节码,没有使用任何第三方运行时。
运行过程是同步的,没有启用go协程。

1. evm最终是调用Interpreter运行字节码;
2. Interpreter.go实现运行处理;解析出操作码后,通过JumpTable获取操作码对应的函数运行,并维护pc计数器、处理返回值等;
3. jump_table.go定义了操作码的跳转映射;
4. instructions.go实现每一个操作码的具体的处理;
5. opcodes.go中定义了操作码常量


对于EVM的测试,以太坊将测试代码放在了core\vm\runtime目录下,提供了供测试用的运行时及测试用例。
测试用例的示例如:
~~~
func TestExecute(t *testing.T) {
    ret, _, err := Execute([]byte{
        byte(vm.PUSH1), 10,
        byte(vm.PUSH1), 0,
        byte(vm.MSTORE),
        byte(vm.PUSH1), 32,
        byte(vm.PUSH1), 0,
        byte(vm.RETURN),
    }, nil, nil)
    if err != nil {
        t.Fatal("didn't expect error", err)
    }

    num := new(big.Int).SetBytes(ret)
    if num.Cmp(big.NewInt(10)) != 0 {
        t.Error("Expected 10, got", num)
    }
}
~~~

以太坊源码分析(18)以太坊交易执行分析

以太坊源码分析(18)以太坊交易执行分析以太坊源码分析(18)以太坊交易执行分析

网址:http://www.qukuailianxueyuan.io/

以太坊源码分析(18)以太坊交易执行分析

以太坊源码分析(18)以太坊交易执行分析

欲领取造币技术与全套虚拟机资料

区块链技术交流QQ群:756146052  备注:CSDN

尹成学院微信:备注:CSDN

以太坊源码分析(18)以太坊交易执行分析