美文网首页
以太坊C++源码解析(五)区块链同步(4)

以太坊C++源码解析(五)区块链同步(4)

作者: sky2016 | 来源:发表于2018-12-07 15:45 被阅读12次

继续上一节的内容,收到其他peer发过来的区块头之后,流程要怎么走了呢?还记得上一节BlockChainSync::onPeerBlockHeaders()函数的末尾是collectBlocks()continueSync()两个函数吗?collectBlocks()由于没有可合并的区块,我们留到后面去讲,而continueSync()会调用syncPeer()这个函数,这次由于m_state状态已经是SyncState::Blocks,因此最终将会调用BlockChainSync::requestBlocks()函数。

if (m_state == SyncState::Blocks)
{
    requestBlocks(_peer);
    return;
}

这和我们思考的逻辑相符,下载完了区块头,这不就是要请求下载区块体了吗?
因此我们来好好看看BlockChainSync::requestBlocks()这个函数,这也是一个非常重要的函数。
照例来一段一段分析:

if (host().bq().knownFull())
{
    LOG(m_loggerDetail) << "Waiting for block queue before downloading blocks";
    pauseSync();
    return;
}

函数开头就是一个非常重要的开关,我们之前提到过下载的区块会暂时存放到一级缓冲区里,合并后再写入二级缓冲区BlockQueue,那么当BlockQueue满了怎么办?这里的处理是暂停同步,调用pauseSync()来设置m_state值为SyncState::Waiting,在几个重要的同步函数中检测这个值就可以停止区块下载了。

auto header = m_headers.begin();
h256s neededBodies;
vector<unsigned> neededNumbers;
unsigned index = 0;
if (m_haveCommonHeader && !m_headers.empty() && m_headers.begin()->first == m_lastImportedBlock + 1)
{
    while (header != m_headers.end() && neededBodies.size() < c_maxRequestBodies && index < header->second.size())
    {
        unsigned block = header->first + index;
        if (m_downloadingBodies.count(block) == 0 && !haveItem(m_bodies, block))
        {
            neededBodies.push_back(header->second[index].hash);
            neededNumbers.push_back(block);
            m_downloadingBodies.insert(block);
        }

        ++index;
        if (index >= header->second.size())
            break; // Download bodies only for validated header chain
    }
}

这段是确定需要下载哪些区块的区块体,我们自然会想到应该是那些已经下载区块头对应的区块体,没错!但是有三个前提条件。这三个条件为:

  • 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里则记录当前正要下载区块体的区块号,避免重复下载相同的区块。

if (neededBodies.size() > 0)
{
    m_bodySyncPeers[_peer] = neededNumbers;
    _peer->requestBlockBodies(neededBodies);
}

如果成功找到了需要下载区块体的区块,那么就调用requestBlockBodies去向对方请求。
如果没有,那么就说明上面的三大条件没有满足,不能下载区块体,那么就继续下载区块头吧。

unsigned start = 0;
if (!m_haveCommonHeader)
{
    // download backwards until common block is found 1 header at a time
    start = m_lastImportedBlock;
    if (!m_headers.empty())
        start = std::min(start, m_headers.begin()->first - 1);
    m_lastImportedBlock = start;
    m_lastImportedBlockHash = host().chain().numberHash(start);

    if (start <= m_chainStartBlock + 1)
        m_haveCommonHeader = true; //reached chain start
}

我在之前都多次讲过区块链同步过程中的回退现象,在区块链本身不稳定的情况下,这种回退十分常见,比如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还是退到了头,那么就要认真准备下面需要下载哪些区块头了。

start = m_lastImportedBlock + 1;
auto next = m_headers.begin();
unsigned count = 0;
if (!m_headers.empty() && start >= m_headers.begin()->first)
{
    start = m_headers.begin()->first + m_headers.begin()->second.size();
    ++next;
}

如果start小于m_headers里的最低块那就最好,否则将start设为第一个连续区块区域之后,并且next设为第二个连续区块区域的开始,如果还用上面那个例子,那么看起来就像这样:

示意图
其中虚线框的区块6和区块7表示还没下载的区块。
while (count == 0 && next != m_headers.end())
{
    count = std::min(c_maxRequestHeaders, next->first - start);
    while(count > 0 && m_downloadingHeaders.count(start) != 0)
    {
        start++;
        count--;
    }
    std::vector<unsigned> headers;
    for (unsigned block = start; block < start + count; block++)
        if (m_downloadingHeaders.count(block) == 0)
        {
            headers.push_back(block);
            m_downloadingHeaders.insert(block);
        }
    count = headers.size();
    if (count > 0)
    {
        m_headerSyncPeers[_peer] = headers;
        assert(!haveItem(m_headers, start));
        _peer->requestBlockHeaders(start, count, 0, false);
    }
    else if (start >= next->first)
    {
        start = next->first + next->second.size();
        ++next;
    }
}

这段用来精确地确定start值和需要下载区块头数量count

count = std::min(c_maxRequestHeaders, next->first - start);

开始的时候count设置为第二个连续区块区域和第一个连续区块区域之间所有块,但是不能超过最大值c_maxRequestHeaders,然后排除掉已经统计过的区块:

while(count > 0 && m_downloadingHeaders.count(start) != 0)
{
    start++;
    count--;
}

并将满足条件的区块加入到headers中:

std::vector<unsigned> headers;
for (unsigned block = start; block < start + count; block++)
    if (m_downloadingHeaders.count(block) == 0)
    {
        headers.push_back(block);
        m_downloadingHeaders.insert(block);
    }

如果headers大小不为0,则向peer请求start为起点,count为数量的区块头:

count = headers.size();
if (count > 0)
{
    m_headerSyncPeers[_peer] = headers;
    assert(!haveItem(m_headers, start));
    _peer->requestBlockHeaders(start, count, 0, false);
}

否则如果start超过了第二个连续区块区域,则将start设为第二个连续区块区域的末尾,next设置为第三个连续区块区域的开始,继续上面的流程:

else if (start >= next->first)
{
    start = next->first + next->second.size();
    ++next;
}

相关文章

网友评论

      本文标题:以太坊C++源码解析(五)区块链同步(4)

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