美文网首页
比特币线程模型

比特币线程模型

作者: BTech | 来源:发表于2018-08-17 14:44 被阅读0次

    最近在研究比特币源码, 发现很难有个突破点. 作为程序员, 在分析一个东西的时候, 很喜欢站在线程的角度来看这个东西. 于是就有了这篇文章. 作为以后深入分析源码的契机.

    本文分析基于当前master(2018.7), 最近一个tag是v0.16.2rc1

    基本线程模型

    下图展示了新交易, 新区块和挖矿三个过程的线程交互模式.

    其中Bitcoind process是一个不包含挖矿功能的全节点(新版本中已经移除了单独的挖矿线程), OtherNode是逻辑上与Bitcoind对等的其他节点, SpendProducer即交易产生节点, 大多数情况下它来自钱包或者其他节点的广播, SingleMiner可以理解为一个矿池中的矿工角色(单挖矿功能).

    Bitcoind process包含了很多线程, 这儿只列出了比较核心的三个:

    • HttpServer(接受并处理rpc请求, 是一组线程), 所有通过tableRPC注册的命令最终都是在此线程中执行
    • ThreadSocketHandler(线程入口函数名), 处理节点间TCP连接的底层交互操作, socket读写等
    • ThreadMesageHandler(线程入口函数名), 主要处理由ThreadSocketHandler读写来的消息, 节点间交互逻辑的主要执行者


    过程详细说明:

    此处以新交易处理流程, 新区块处理流程和矿池中的矿工挖矿交互流程为例. 从这些交互的例子中也能有大概印象HttpServer和ThreadSocketHandler的交互侧重点差异所在.

    新交易过程: (从New Transaction开始)

    1. 交易产生节点创建一笔交易后(交易数据结构,以下简称ntx, 是new transaction的简写), 通过inv消息将ntx发送该其连接的节点.
    2. 转换为全节点视角, 当全节点收到ntx后, 会将ntx异步发送给ThreadMesageHandler(共享内存方式, 线程间通信).
    3. ThreadMesageHandler处理对应消息(ProcessMessage函数的NetMsgType::TX对应逻辑):
    • checkWork, 主要在AcceptToMemoryPoolWorker函数中执行, 检查包括合规性检查, 最小交易费, 双花检查, 执行解锁脚本等等
    • memPool update, 基于上一步的检查结果, 将新交易加入memPool. memPool中包含了很多集合, 此步骤具体细节将在以后的文章中说明.
    • RELAY, 将ntx广播出去. 具体做法是将一个inv消息追加到每个node的发送队列中, 等ThreadMesageHandler线程自动检测到此消息并发送给对应节点.
    • 其他节点收到ntx后, 如果是全节点, 将会重复此新交易过程处理步骤.

    新区块到达:(从New Block消息开始)

    1. 当一个矿池挖出一个新区块后, 这个矿池会尽快将此区块广播出去.
    2. 转换为收到新区块的全节点视角, 当全节点收到一个新区块后, 会消息异步发送给ThreadMesageHandler(同新交易过程).
    3. ThreadMesageHandler处理对应消息(ProcessMessage函数的NetMsgType::CMPCTBLOCK对应逻辑):
    • CheckHeader 主要检查新区块与当前最长链的关系, 此处假设新区块的previous block就是当前的最长链的最后一个区块, 假设其他所有检查都通过.
    • 进入ProcessNewBlock函数, 进行区块检查, 包括MerkleRoot, 所有交易合法性, SigOps等等.
    • 主链接受新区块
    • 更新memPool, 主要是将新区块中包含的交易信息从memPool中移除.
    • RELAY, 将新区块广播出去, 具体做法就是发送一个NewPoWValidBlock, 其连接的处理函数会将一个CMPCTBLOCK类型的消息写入所有连接Node的发送队列中, 等ThreadMesageHandler线程自动检测到此消息并发送给对应节点.
    • 其他节点收到新区块后, 如果是全节点, 将重复此新区块到达处理步骤.

    矿工挖矿流程:(从getblocktemplate开始)

    新版本去掉了内置的挖矿线程, 挖矿程序默认是一个单独进程, 通过此接口获得包括最新交易在内的区块模板, 并在此模板基础上挖矿.

    1. 矿工向全节点请求一个最新区块模板
    2. 全节点从其memPool中选出有价值的交易,产生区块头及CoinBase交易组装成区块模板
    3. 全节点将区块模板及其他限制参数(范围,大小等)写入返回值中, 返回给矿工
    4. 矿工开始挖矿
    5. 矿工挖矿成功后, 将新区块回传给全节点
    6. 全节点收到新区块后, 对其进行简单检查(区块hash, previousHash等), 然后进行新区块处理流程. 具体流程与新区块到达一致, 此不赘述
    7. RELAY, 中心节点将新区块广播出去, 与新区块处理流程一致, 此不赘述
    8. 收到新区块的其他节点, 如果是全节点, 将重复新区块到达处理步骤

    挖矿核心代码

    在前文一再说明, 新版本已经将挖矿的线程移除, 挖矿程序一般是一个独立运行的进程, 但在新分支中依然能够看到挖矿过程的代码. 此处去掉了与挖矿无关的操作, 只留下了核心代码以方便理解. 通过这些代码可以让我们对挖矿过程有个感性上的认识.

        bool mine(CBLock* pblock, long nMaxTries, int nInnerLoopCount){
             while(true){
                 updateExternNonce(pblock); // modify part of baseCoin tx and, at the same time, merkleRoot has been changed
                 while(blockTemplate->nonce == nInnerLoopCount ||  nMaxTries  == 0 || !CheckProofOfWork(pblock->GetHash(), pblock->nBits)){
                     ++blockTemplate->nonce ;
                     nMaxTries --;
                 }
                 if(blockTemplate->nonce == nInnerLoopCount ){
                      blockTemplate->nonce=0;
                      continue;
                 }
                 if(nMaxTries  == 0) break;
                 return true;
            }
            return false;
        }
    

    从代码中我们看到, 调用挖矿方法时, 调用者通过设置最大重试次数的方式控制挖矿运行时长. 每次挖矿事实上是通过调整nonce或者extraNonce的方式改变区块hash值, 进而完成一次挖矿尝试, 当找到小于难度值的hash值时代表挖矿成功.

    nonce是区块头中专门为调整区块hash头而预留的可变字段.
    extraNonce是区块第一笔交易(CoinBase交易)的解锁脚本的一部分. 因为CoinBase交易不需要解锁脚本, 所以这个区域可以被用作其他一些用途. CoinBase的解锁脚本被更改后, 其hash值会产生变化, 进而使整个区块的merkleRoot值发生变化, 进而导致区块头发生变化. 区块头发生变化后, 挖矿就又可以从0开始尝试nonce字段了.

    BTech原创,未经许可不得转载

    相关文章

      网友评论

          本文标题:比特币线程模型

          本文链接:https://www.haomeiwen.com/subject/zxozbftx.html