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

继续上一节的内容,收到其他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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容

  • 双11那天,在微博上偶然看到铃铛子的手绘训练营,念头一转,报了名。 除了上课,我还报名了微信打卡活动,不为了退费,...
    小重楼_阅读 293评论 0 0
  • 又念孤城,遍数苍捱,枯叶匆匆。忆芳华纷扰,半生惧色,孤帆远影,甚是飘零,炊烟缭绕,我欲厮守不见卿。温存处,正枯木凋...
    pz五月阅读 1,475评论 0 0
  • 准备日:(3天) ➡️07:45果蔬纤维素1粒+水300ml (温热水小口喝) ➡️08:00早餐:正常吃+蛋白粉...
    北京小寒阅读 2,378评论 0 0