继续上一节的内容,收到其他peer发过来的区块头之后,流程要怎么走了呢?还记得上一节BlockChainSync::onPeerBlockHeaders()
函数的末尾是collectBlocks()
和continueSync()
两个函数吗?collectBlocks()
由于没有可合并的区块,我们留到后面去讲,而continueSync()
会调用syncPeer()
这个函数,这次由于m_state
状态已经是SyncState::Blocks
,因此最终将会调用BlockChainSync::requestBlocks()
函数。
1 | if (m_state == SyncState::Blocks) |
这和我们思考的逻辑相符,下载完了区块头,这不就是要请求下载区块体了吗?
因此我们来好好看看BlockChainSync::requestBlocks()
这个函数,这也是一个非常重要的函数。
照例来一段一段分析:
1 | if (host().bq().knownFull()) |
函数开头就是一个非常重要的开关,我们之前提到过下载的区块会暂时存放到一级缓冲区里,合并后再写入二级缓冲区BlockQueue
,那么当BlockQueue
满了怎么办?这里的处理是暂停同步,调用pauseSync()
来设置m_state
值为SyncState::Waiting
,在几个重要的同步函数中检测这个值就可以停止区块下载了。
1 | auto header = m_headers.begin(); |
这段是确定需要下载哪些区块的区块体,我们自然会想到应该是那些已经下载区块头对应的区块体,没错!但是有三个前提条件。这三个条件为:
- m_haveCommonHeader
- !m_headers.empty()
- m_headers.begin()->first == m_lastImportedBlock + 1
其中第二个条件很容易理解且满足,主要是第一个和第三个条件,第一个条件的含义我在前面已经讲过,这个表示下载真正开始。第三个条件表示目前在m_headers
里最低区块正是上次已经下载块的下一个块。
满足这三个条件以后才开始遍历m_headers
里第一个连续区块区域,比如m_headers
里目前存放的区块是[[区块3,区块4,区块5], [区块8,区块9]],那么这里遍历的就是[区块3,区块4,区块5]]这三个连续区块。需要下载区块体的区块hash被记录到neededBodies
里,neededNumbers
记录对应的区块号,m_downloadingBodies
里则记录当前正要下载区块体的区块号,避免重复下载相同的区块。
1 | if (neededBodies.size() > 0) |
如果成功找到了需要下载区块体的区块,那么就调用requestBlockBodies
去向对方请求。
如果没有,那么就说明上面的三大条件没有满足,不能下载区块体,那么就继续下载区块头吧。
1 | unsigned start = 0; |
我在之前都多次讲过区块链同步过程中的回退现象,在区块链本身不稳定的情况下,这种回退十分常见,比如ropsten
链,但是在主链上似乎很少见到。那么回退是什么时候发生的呢?就是在这里了。
这里判断m_haveCommonHeader
值为false
就会发生回退,回退的过程是选取当前已经下载好的区块号和m_headers
中最低区块的上一个区块之间取较小值作为新的同步起点start
,也就是同步回退了,并将m_lastImportedBlock
设为新的起点。如果已经退到链的起始块,那么退无可退了就只能前进了,将m_haveCommonHeader
设为true
。
如果m_haveCommonHeader
值还是为false
,也就是并没有退到头,这实际上是else
分支的内容,我提前来讲是因为这段太简单,就一句话:
_peer->requestBlockHeaders(start, 1, 0, false);
试探性地向对方请求起点处的一个区块,如果这个区块还不能让m_haveCommonHeader
值为true的话,那么就继续退。
如果m_haveCommonHeader
值为true
了,不管是之前就是true
还是退到了头,那么就要认真准备下面需要下载哪些区块头了。
1 | start = m_lastImportedBlock + 1; |
如果start
小于m_headers
里的最低块那就最好,否则将start
设为第一个连续区块区域之后,并且next设为第二个连续区块区域的开始,如果还用上面那个例子,那么看起来就像这样:
其中虚线框的区块6和区块7表示还没下载的区块。
1 | while (count == 0 && next != m_headers.end()) |
这段用来精确地确定start
值和需要下载区块头数量count
。
1 | count = std::min(c_maxRequestHeaders, next->first - start); |
开始的时候count
设置为第二个连续区块区域和第一个连续区块区域之间所有块,但是不能超过最大值c_maxRequestHeaders
,然后排除掉已经统计过的区块:
1 | while(count > 0 && m_downloadingHeaders.count(start) != 0) |
并将满足条件的区块加入到headers
中:
1 | std::vector<unsigned> headers; |
如果headers
大小不为0,则向peer请求start
为起点,count
为数量的区块头:
1 | count = headers.size(); |
否则如果start
超过了第二个连续区块区域,则将start
设为第二个连续区块区域的末尾,next
设置为第三个连续区块区域的开始,继续上面的流程:
1 | else if (start >= next->first) |