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

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

1
2
3
4
5
if (m_state == SyncState::Blocks)
{
requestBlocks(_peer);
return;
}

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

1
2
3
4
5
6
if (host().bq().knownFull())
{
LOG(m_loggerDetail) << "Waiting for block queue before downloading blocks";
pauseSync();
return;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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里则记录当前正要下载区块体的区块号,避免重复下载相同的区块。

1
2
3
4
5
if (neededBodies.size() > 0)
{
m_bodySyncPeers[_peer] = neededNumbers;
_peer->requestBlockBodies(neededBodies);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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还是退到了头,那么就要认真准备下面需要下载哪些区块头了。

1
2
3
4
5
6
7
8
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表示还没下载的区块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

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

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

1
2
3
4
5
while(count > 0 && m_downloadingHeaders.count(start) != 0)
{
start++;
count--;
}

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

1
2
3
4
5
6
7
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为数量的区块头:

1
2
3
4
5
6
7
count = headers.size();
if (count > 0)
{
m_headerSyncPeers[_peer] = headers;
assert(!haveItem(m_headers, start));
_peer->requestBlockHeaders(start, count, 0, false);
}

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

1
2
3
4
5
else if (start >= next->first)
{
start = next->first + next->second.size();
++next;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×