除了上面的同步形式外,區(qū)塊鏈節(jié)點(diǎn)之間還存在另外兩種特殊形式的同步,一種是交易同步,也就是當(dāng)某個節(jié)點(diǎn)完成一筆交易后,需要向其他節(jié)點(diǎn)廣播這個交易,另一種是礦工成功挖到一個區(qū)塊,也要向其他節(jié)點(diǎn)廣播這個新的區(qū)塊。我們來看看這兩種同步是怎么進(jìn)行的。
交易同步
交易同步的數(shù)據(jù)包類型是TransactionsPacket,EthereumPeer收到這個包以后直接就在EthereumPeerObserver里做了處理,并沒有轉(zhuǎn)交給BlockChainSync,可能是因?yàn)檫@個處理太簡單的緣故,我們也可以從代碼中看出來:
void onPeerTransactions(std::shared_ptr<EthereumPeer> _peer, RLP const& _r) override
{
unsigned itemCount = _r.itemCount();
LOG(m_logger) << "Transactions (" << dec << itemCount << " entries)";
m_tq.enqueue(_r, _peer->id());
}
這里的處理就是放到m_tq里,這里的m_tq是TransactionQueue對象,TransactionQueue是一個專門放pending交易的隊(duì)列,也就是還沒有正式進(jìn)入?yún)^(qū)塊鏈的“無主”交易待的地方。關(guān)于這個類,后續(xù)會專門來講。
新區(qū)塊同步
新區(qū)塊包的數(shù)據(jù)類型是NewBlockPacket,這次處理的重任重新回到了BlockChainSync,因此,我們來看看BlockChainSync::onPeerNewBlock的處理吧。
這個處理分為兩部分,第一部分是常規(guī)檢查:
if (_r.itemCount() != 2)
{
_peer->disable("NewBlock without 2 data fields.");
return;
}
BlockHeader info(_r[0][0].data(), HeaderData);
auto h = info.hash();
DEV_GUARDED(_peer->x_knownBlocks)
_peer->m_knownBlocks.insert(h);
unsigned blockNumber = static_cast<unsigned>(info.number());
if (blockNumber > (m_lastImportedBlock + 1))
{
LOG(m_loggerDetail) << "Received unknown new block";
// Update the hash of highest known block of the peer.
// syncPeer will then request the highest block header to properly restart syncing
_peer->m_latestHash = h;
syncPeer(_peer, true);
return;
}
這個包不同于之前的區(qū)塊數(shù)據(jù)包,這個包里同時包含區(qū)塊頭和區(qū)塊體,第一部分是對區(qū)塊頭里的信息進(jìn)行處理。如果新收到的區(qū)塊比我目前最新的節(jié)點(diǎn)更新,那么說明該節(jié)點(diǎn)有更新的數(shù)據(jù),那么就調(diào)用syncPeer從該節(jié)點(diǎn)進(jìn)行同步。
第二部分就是導(dǎo)入?yún)^(qū)塊體了:
switch (host().bq().import(_r[0].data()))
{
case ImportResult::Success:
// ...
}
這里的流程和前一節(jié)差不多,將這個區(qū)塊導(dǎo)入二級緩沖區(qū)中去驗(yàn)證。
幾點(diǎn)補(bǔ)充
到這里ETH區(qū)塊鏈同步的主要流程都涉及到了,剩下的部分單獨(dú)再分析。本節(jié)再對同步補(bǔ)充幾點(diǎn):
- 以太坊ropsten網(wǎng)絡(luò)比主網(wǎng)mainnet要差很多,對于同步來說就是噩夢。如果需要測試交易什么的建議自己搭建私有鏈來測試.
- 同步過程漫長,需要有耐心,而且是越來越慢,后期單個區(qū)塊包含的交易更多,計算量和存儲量都巨大。
- 同步硬盤建議SSD,容量在1TB以上。
- 同步存儲主要是三類數(shù)據(jù)庫,Block數(shù)據(jù)庫,Extra數(shù)據(jù)庫和State數(shù)據(jù)庫,其中State數(shù)據(jù)庫最大,這個就是所謂的世界狀態(tài)了。
- 同步有坑!??!,還記得以太坊C++源碼解析(五)區(qū)塊鏈同步(2)里的那張圖嗎?整個流程有兩個閥門,閥門1是從BlockQueue到區(qū)塊鏈,閥門2是從網(wǎng)絡(luò)到一級緩沖區(qū)。這兩個閥門正常工作流程是這樣的:當(dāng)二級緩沖區(qū)里有數(shù)據(jù)時,閥門1開啟,否則關(guān)閉;另外由于數(shù)據(jù)進(jìn)入二級緩沖區(qū)的速度通常比從二級緩沖區(qū)進(jìn)入?yún)^(qū)塊鏈的速度要快很多,因此二級緩沖區(qū)里有大量數(shù)據(jù),當(dāng)二級緩沖區(qū)滿時,閥門2關(guān)閉,否則開啟。但是這兩個閥門在實(shí)際測試中是存在故障的,閥門1故障的表現(xiàn)是數(shù)據(jù)校驗(yàn)后未打開,數(shù)據(jù)不會進(jìn)入?yún)^(qū)塊鏈,導(dǎo)致二級緩沖區(qū)一直滿,從而導(dǎo)致閥門2一直關(guān)閉,整個同步過程停止,這種我稱為撐死;閥門2故障的表現(xiàn)是在已關(guān)閉的情況下,二級緩沖區(qū)未滿時不能正常打開,二級緩沖區(qū)數(shù)據(jù)全部被取光后整個同步過程停止,這種我稱為餓死。