【以太坊源码分析】从一个典型的交易流程和挖矿流程看以太坊源码

一、 典型交易流程源码

【以太坊源码分析】从一个典型的交易流程和挖矿流程看以太坊源码
入口在StateProcessor的Process()函数,具体为什么在这里,可以从下一节中的挖矿源码分析中看到。

  1. 根据硬分叉改变一些stateDB的状态(由于以太坊历史上有一些硬分叉的过程,所有现在的源码很多位置都有对于硬分叉的判断,本系列源码分析只关注主流程,以后不再赘述有关硬分叉的部分)
  2. 循环执行区块中的交易,这一部分是整个交易流程的核心部分,所有对交易的验证、奖励、gas的计算、evm的执行都在这部分,主要分为以下几部分:
  • a) state初始设置,如交易hash、块hash等已有信息。

  • b) 执行交易,返回receipt和logs。这里返回的receipt是虚拟机执行的每一步的结果,logs是对应的日志,返回这两个并形成merkle根是为了快速验证每一步的结果都是正确的。
    * statedb.Prepare(tx.Hash(), block.Hash(), i)函数做一些准备工作,包括创建evm虚拟环境等;
    * 由ApplyTransaction()函数进行具体的交易执行(包括转账和智能合约的相关动作),交付gas,这里的gas包括支付给矿工的gas和refundGas;
    * 更新merkle根。

    在这个部分中,第一次出现了merkle树的概念,在这里的作用是把每一个小的hash由树的形式最终计算出一个merkle root,任何一个小的hash的改变都将导致merkle root的改变,这样我们在区块链中就可以通过merkle root快速验证区块中的数据是否被篡改。
    Gas在以太坊中的作用相信大家已经都了解了,但是这里还出现了一个refundGas,根据源码来看,这笔是返回给发起交易的账户的,具体为什么要这样做还不清楚,这部分在以太坊黄皮书中有讲述(计算方式),感兴趣的读者可以自己去看一下https://ethereum.stackexchange.com/questions/594/what-are-the-limits-to-gas-refunds。

  1. 最后是调用共识中的Finalize函数,这个函数主要是完成一些对区块挖掘者的奖励等最后收尾工作,值得注意的是这个函数中的操作一般不会写入merkle根,这也是为什么每个矿工挖出来的区块都奖励了自己(和其他矿工生成的区块有一笔交易不同),也可以通过其他矿工的验证。

二、挖矿

【以太坊源码分析】从一个典型的交易流程和挖矿流程看以太坊源码
挖矿的源码包在miner目录下,其中stress_clique.go和stress_ethash.go都是测试共识的代码,其余几个才是主要挖矿的代码。Miner对象核心就是worker对象,Worker对象主要用于准备当前区块环境,比如这个区块包含的交易等。然后调用共识引擎挖矿,以pow为例,主要就是算随机数然后填充到当前区块中,完成挖掘。我们从miner.go开始看起。

  1. 这个部分主要是构造一个miner对象,这部分主要是构造worker对象、监听相关事件、启动相关的一些线程,接下来我们具体来看。
  • a) 构造worker对象以及导入相关配置(如chainConfig),worker对象主要是准备区块,所以在worker对象中会订阅跟交易相关的事件,这里订阅了三个事件:
    • eth.TxPool().SubscribeNewTxsEvent,这个是TxPool对象发出的,指的是一个新的交易tx被加入了TxPool,这时如果worker没有处于挖掘中,那么就去执行这个tx,并把它收纳进Work.txs数组,为下次挖掘新区块备用。
    • eth.BlockChain().SubscribeChainHeadEvent, ChainHeadEvent是指区块链中已经加入了一个新的区块作为整个链的链头,这时worker的回应是立即开始准备挖掘下一个新区块
    • eth.BlockChain().SubscribeChainSideEvent ChainSideEvent指区块链中加入了一个新区块作为当前链头的旁支,worker会把这个区块收纳进possibleUncles[]数组,作为下一个挖掘新区块可能的Uncle之一
  • b) 启动线程work.mainLoop、work.newWorkLoop、worker.resultLoop、worker.taskLoop。
    • i. work.mainLoop
      挖矿主循环,这里面又会监听几个事件,newWorkCh代表一个新的区块需要提交挖矿;chainSideCh代表发现了一个叔区块,我们需要视情况而定加入到当前区块中,重新开始挖矿(一个区块最多包含两个叔区块);txsCh代表接收到一个新的交易,如果没有开始挖掘,则执行这个交易,加入当前区块中(上一部分中交易执行的入口就是从这里发现的)。
    • ii. work.newWorkLoop
      这里主要是work主线程,负责提交任务。这里主要涉及到三个比较重要的函数,分别是commit()、recalcRecommit()、clearPending()。Commit用于提交任务,发送newWorkReq到上一步中的newWorkCh通;,recalcRecommit是根据反馈计算一个等待时间;clearPending是清除当前在等待的任务。搞清楚这几个函数的作用之后,这里面监听的事件就很容易明白了,概括一下就是要么等待,要么就是清除队列准备提交(相信读者经过上面的学习这里应该很容易看懂,篇幅有限,笔者不再赘述)。
    • iii. worker.resultLoop
      结果处理,用于处理封装好之后的区块。通过resultCh接收到封装好的区块之后,做一个简单的验证(可能由于resubmit导致区块重复),构造好logs和event,发送相关事件。最后将这个块插入到unconfirmed链中。
      注意,这里第一次出现了unconfirmed这个概念,这相当于未确认的块,所有挖出来或者接收的区块都会先插入到这个地方,后续遇到这个我们再详细讲。
      iv. worker.taskLoop
      这是负责连接worker和agent的地方,newWorkLoop中的commit函数会发送taskCh到taskLoop中,由taskLoop调用agent进行seal。也就是说,taskLoop负责把worker的任务推送到具体的engine。
  1. 经过上述过程,整个worker对象已经运行起来了,miner的核心就是这个worker,接下来就是启动miner.update()。这个函数首先订阅了downloader的相关的几个事件。收到Downloader的StartEvent时,意味者此时本节点正在从其他节点下载新区块,这时miner会立即停止进行中的挖掘工作,并继续监听;如果收到DoneEvent或FailEvent时,意味本节点的下载任务已结束-无论下载成功或失败-此时都可以开始挖掘新区块,并且此时会退出Downloader事件的监听。也就是说,只要接收到来自其他节点的区块,本节点就会自动停止当前区块的挖掘,转向下一个区块。

三、 总结

这一节我们从典型的交易流程讲起,从而拓展到挖矿的过程。其实以太坊中最核心的就是挖矿和交易的处理,个人认为如果能自己把这个流程梳理清楚了,其他的部分也就不在话下。以太坊其他的部分,比如rpc、event、downloader、p2p等等,其实都是在为这整个过程做服务。其他的模块我们多多少少都能在这一次的源码分析中看见,这一部分也是区块链创新的核心所在。
接下来是我对以太坊的一些感悟:

  • Gas是以太坊的灵魂,正是由了这个部分,整个系统才作为一个完善的系统一直在运行。
  • 以太坊另一个大的创新就是EVM,这正是区别于比特币的东西,也正是这个把区块链又往前推动了一大步,让区块链处理业务变成了可能,包括后来的fabric、BCOS、EOS等系统都保留了虚拟机这个东西。
  • 为了保证分叉的减少,以太坊另一个创新在于引入了叔区块的概念,不能被引入主链的区块有机会作为叔区块存在。这样大大减少了孤块的产生,减少了分叉,提升了整个网络的安全性。值得注意的是叔区块也是有一定奖励的(感兴趣的读者可以自行搜索,不再赘述)。
  • 等待区块确认这个概念是用unconfirmed chain实现的,通过这个集合的移动,我们明白了区块确认的原理(笔者以前一直以为未确认只是业界共识,没有在代码中实现)。

以下是我读源码的一些收获:

  • 在整个系统中event是一个很重要的存在,我们发现事件的订阅和退订似乎无处不在。整个系统用event串联了起来,我觉得有必要去仔细学习以下以太坊的event的使用。
  • Miner过程中大量的使用了协程,我想这也是大型项目使用go语言的原因吧。每个协程都各自为战,然后由通道将不同协程串联起来。这样完成了整个系统的解耦,也使得整个系统有更好的可扩展性(我觉得这一点值得我们好好学习,建议读者去看Go并发编程实战这本书),我觉得mainLoop、newWorkLoop、resultLoop、taskLoop这几个协程的协作是一个很好的例子。
  • 跟上一篇博客结论相同,这次miner源码分析中,我们发现了整个过程只是在构建一个miner对象,同时启动了一些协程。源码过程核心就是构造需要的对象(miner),在这个过程中启动工作协程。

四、参考文献

https://github.com/ethereum/go-ethereum 以太坊源码go-ethereum
https://blog.csdn.net/KeenCryp/article/details/86586132 refundGas说明
https://blog.csdn.net/teaspring/article/details/78050274 以太坊源码分析
https://github.com/ZtesoftCS/go-ethereum-code-analysis/blob/master/miner挖矿部分源码分析CPU挖矿.md 以太坊源码分析
https://blog.csdn.net/u012412689/article/details/88534757 本系列上一篇博客

其他:笔者水平有限,如有错误请联系[email protected]