在p2p(6)那一節(jié)末尾我們涉及到了BlockChainSync::syncPeer()
函數(shù)蛤签,實(shí)際上到這里已經(jīng)進(jìn)入了另外一個(gè)重要模塊:區(qū)塊鏈同步模塊,這個(gè)模塊算是P2P模塊交互模塊震肮。
我們知道區(qū)塊鏈?zhǔn)且粋€(gè)分布式賬本,在所有的全節(jié)點(diǎn)上都有區(qū)塊鏈的一個(gè)完整副本鲫尊,這些全節(jié)點(diǎn)之間是相互同步的關(guān)系沦偎。當(dāng)我們?cè)诒镜卮罱ê靡粋€(gè)全節(jié)點(diǎn)時(shí),首先需要從其他節(jié)點(diǎn)把所有區(qū)塊同步過來鸿捧,目前以太坊mainnet鏈有600多萬個(gè)區(qū)塊疙渣,ropsten測(cè)試鏈有400多萬個(gè)區(qū)塊,具體的區(qū)塊信息可以在Etherscan網(wǎng)站查詢到泼菌。
如果想要在鏈上發(fā)送一個(gè)交易啦租,必須要等到本地區(qū)塊鏈同步接近最新塊,否則交易不會(huì)被廣播出來篷角。換句話說,區(qū)塊鏈同步接近完成是進(jìn)行交易的前提條件虐块!這里用接近完成而不用完成是因?yàn)閰^(qū)塊同步永遠(yuǎn)都不會(huì)完成嘉蕾,以太坊差不多10多秒就會(huì)產(chǎn)生一個(gè)新的區(qū)塊。
幾百萬個(gè)區(qū)塊的同步是一個(gè)相當(dāng)漫長且痛苦的過程儡率,我目前同步的是ropsten測(cè)試鏈,也許是鏈上經(jīng)常存在攻擊儿普,也許是中國這邊節(jié)點(diǎn)少,同步過程相當(dāng)不穩(wěn)定箕肃,快的時(shí)候一晚上能同步50萬個(gè)區(qū)塊,慢的時(shí)候卡在某個(gè)區(qū)塊一動(dòng)不動(dòng)好幾天障贸。
因此非常有必要深入了解區(qū)塊鏈的同步過程吟宦。
題外話不多說了,還是從BlockChainSync::syncPeer()
函數(shù)開始吧
void BlockChainSync::syncPeer(std::shared_ptr<EthereumPeer> _peer, bool _force)
{
// ...
if (m_state == SyncState::Waiting)
return;
u256 td = host().chain().details().totalDifficulty;
if (host().bq().isActive())
td += host().bq().difficulty();
u256 syncingDifficulty = std::max(m_syncingTotalDifficulty, td);
if (_force || _peer->m_totalDifficulty > syncingDifficulty)
{
if (_peer->m_totalDifficulty > syncingDifficulty)
LOG(m_logger) << "Discovered new highest difficulty";
// start sync
m_syncingTotalDifficulty = _peer->m_totalDifficulty;
if (m_state == SyncState::Idle || m_state == SyncState::NotSynced)
{
LOG(m_loggerInfo) << "Starting full sync";
m_state = SyncState::Blocks;
}
_peer->requestBlockHeaders(_peer->m_latestHash, 1, 0, false);
_peer->m_requireTransactions = true;
return;
}
if (m_state == SyncState::Blocks)
{
requestBlocks(_peer);
return;
}
}
開頭有一個(gè)判斷條件m_state == SyncState::Waiting
袁波,這是一個(gè)是否同步的開關(guān)篷牌,從別的節(jié)點(diǎn)peer同步過來的區(qū)塊是放到一個(gè)緩存里的,當(dāng)這個(gè)緩存滿的時(shí)候枷颊,開關(guān)關(guān)閉脉让,同步會(huì)暫時(shí)中止。
u256 td = host().chain().details().totalDifficulty;
if (host().bq().isActive())
td += host().bq().difficulty();
u256 syncingDifficulty = std::max(m_syncingTotalDifficulty, td);
這段代碼是計(jì)算本地當(dāng)前同步的區(qū)塊鏈的總難度隔缀。
區(qū)塊鏈礦工競(jìng)爭(zhēng)是通過難度來衡量的,所有節(jié)點(diǎn)傾向于相信難度大的區(qū)塊
如果該節(jié)點(diǎn)peer的總難度比我自身難度大猾瘸,那么就需要從該節(jié)點(diǎn)同步(這里有一個(gè)漏洞,如果有人偽造一個(gè)非常大的難度牵触,那么本節(jié)點(diǎn)會(huì)一直從對(duì)方同步,直到一個(gè)新的更大難度的節(jié)點(diǎn)出現(xiàn),這樣可能會(huì)導(dǎo)致同步卡住)
m_state
表示同步的狀態(tài)渊鞋,當(dāng)m_state
為SyncState::Idle
或者SyncState::NotSynced
時(shí)瞧挤,同步就真正開始了特恬!
區(qū)塊分為區(qū)塊頭和區(qū)塊體,這兩部分是分別下載的癌刽。
首先下載的是對(duì)方節(jié)點(diǎn)最新塊的區(qū)塊頭尝丐,也就是:
_peer->requestBlockHeaders(_peer->m_latestHash, 1, 0, false);
這里調(diào)用的是EthereumPeer::requestBlockHeaders()
函數(shù)。
反之如果該節(jié)點(diǎn)難度沒有我自身難度大远荠,并且之前同步過區(qū)塊頭的話失息,就準(zhǔn)備同步區(qū)塊體,也就是:
if (m_state == SyncState::Blocks)
{
requestBlocks(_peer);
return;
}
我們先來看看EthereumPeer::requestBlockHeaders()
函數(shù)的實(shí)現(xiàn)盹兢。
在
EthereumPeer
類里有兩個(gè)requestBlockHeaders()
函數(shù),分別是按區(qū)塊號(hào)來同步和按區(qū)塊hash值來同步浦妄,這里調(diào)用的是后者替裆。
void EthereumPeer::requestBlockHeaders(h256 const& _startHash, unsigned _count, unsigned _skip, bool _reverse)
{
// ...
setAsking(Asking::BlockHeaders);
RLPStream s;
prep(s, GetBlockHeadersPacket, 4) << _startHash << _count << _skip << (_reverse ? 1 : 0);
LOG(m_logger) << "Requesting " << _count << " block headers starting from " << _startHash
<< (_reverse ? " in reverse" : "");
m_lastAskedHeaders = _count;
sealAndSend(s);
}
這個(gè)函數(shù)比較簡單,就是向?qū)Ψ桨l(fā)送一個(gè)GetBlockHeadersPacket
數(shù)據(jù)包宜咒。那么對(duì)方接到這個(gè)包以后怎么回應(yīng)呢把鉴?照例到EthereumPeer::interpret()
函數(shù)里去找:
case GetBlockHeadersPacket:
{
/// Packet layout:
/// [ block: { P , B_32 }, maxHeaders: P, skip: P, reverse: P in { 0 , 1 } ]
const auto blockId = _r[0];
const auto maxHeaders = _r[1].toInt<u256>();
const auto skip = _r[2].toInt<u256>();
const auto reverse = _r[3].toInt<bool>();
auto numHeadersToSend = maxHeaders <= c_maxHeadersToSend ? static_cast<unsigned>(maxHeaders) : c_maxHeadersToSend;
if (skip > std::numeric_limits<unsigned>::max() - 1)
{
cnetdetails << "Requested block skip is too big: " << skip;
break;
}
pair<bytes, unsigned> const rlpAndItemCount = hostData->blockHeaders(blockId, numHeadersToSend, skip, reverse);
RLPStream s;
prep(s, BlockHeadersPacket, rlpAndItemCount.second).appendRaw(rlpAndItemCount.first, rlpAndItemCount.second);
sealAndSend(s);
addRating(0);
break;
}
可以看到這里主要是調(diào)用了hostData->blockHeaders()
函數(shù)獲取區(qū)塊頭庭砍,并回復(fù)對(duì)方BlockHeadersPacket
數(shù)據(jù)包。其中hostData
是EthereumHostData
類指針怠缸,blockId
可能有兩個(gè)值,分別是區(qū)塊號(hào)或者區(qū)塊hash值扳炬,對(duì)應(yīng)前面兩個(gè)requestBlockHeaders()
函數(shù)。maxHeaders
是請(qǐng)求區(qū)塊頭的數(shù)量恨樟。
我們?cè)倏纯?code>EthereumHostData::blockHeaders()函數(shù)實(shí)現(xiàn):
這個(gè)函數(shù)有點(diǎn)長,先貼一部分代碼吧:
auto numHeadersToSend = _maxHeaders;
auto step = static_cast<unsigned>(_skip) + 1;
assert(step > 0 && "step must not be 0");
h256 blockHash;
if (_blockId.size() == 32) // block id is a hash
{
blockHash = _blockId.toHash<h256>();
// ...
if (!m_chain.isKnown(blockHash))
blockHash = {};
else if (!_reverse)
{
auto n = m_chain.number(blockHash);
if (numHeadersToSend == 0)
blockHash = {};
else if (n != 0 || blockHash == m_chain.genesisHash())
{
auto top = n + uint64_t(step) * numHeadersToSend - 1;
auto lastBlock = m_chain.number();
if (top > lastBlock)
{
numHeadersToSend = (lastBlock - n) / step + 1;
top = n + step * (numHeadersToSend - 1);
}
assert(top <= lastBlock && "invalid top block calculated");
blockHash = m_chain.numberHash(static_cast<unsigned>(top)); // override start block hash with the hash of the top block we have
}
else
blockHash = {};
}
}
numHeadersToSend
這個(gè)值是需要發(fā)送的最大區(qū)塊頭數(shù)量缩多,_skip
值為0养晋,因此step
值為1。
接著判斷_blockId
里是區(qū)塊hash還是區(qū)塊號(hào)匙握,貼出來的這部分代碼是區(qū)塊hash,處理區(qū)塊號(hào)那部分代碼類似秦忿,有興趣可以自己去看蛾娶。
if (!m_chain.isKnown(blockHash))
blockHash = {};
這里是判斷如果該區(qū)塊hash不在我本地區(qū)塊鏈里,則不返回任何東西蛔琅。
_reverse
值為false,取出blockHash
對(duì)應(yīng)的塊號(hào)n
辜窑,計(jì)算要取的最高塊號(hào)top
寨躁,再得到當(dāng)前區(qū)塊鏈最新塊號(hào)lastBlock
,判斷邊界條件职恳,top
值不能超過lastBlock
,如果超過了則top=lastBlock
色徘,再算出top
對(duì)應(yīng)的塊hash值blockHash
操禀。
注意這里的
blockHash
是最高塊的hash值,為什么需要這個(gè)值呢?因?yàn)閰^(qū)塊鏈里區(qū)塊是像單向鏈表連接起來的蔑水,其中0號(hào)區(qū)塊是創(chuàng)世區(qū)塊扬蕊,后續(xù)區(qū)塊從1開始遞增丹擎,每個(gè)區(qū)塊里會(huì)記錄上一級(jí)區(qū)塊的hash值,相當(dāng)于是指向父區(qū)塊的指針再愈,因此我們遍歷的時(shí)候只能從后往前遍歷护戳。
接著往下看:
auto nextHash = [this](h256 _h, unsigned _step)
{
static const unsigned c_blockNumberUsageLimit = 1000;
const auto lastBlock = m_chain.number();
const auto limitBlock = lastBlock > c_blockNumberUsageLimit ? lastBlock - c_blockNumberUsageLimit : 0; // find the number of the block below which we don't expect BC changes.
while (_step) // parent hash traversal
{
auto details = m_chain.details(_h);
if (details.number < limitBlock)
break; // stop using parent hash traversal, fallback to using block numbers
_h = details.parent;
--_step;
}
if (_step) // still need lower block
{
auto n = m_chain.number(_h);
if (n >= _step)
_h = m_chain.numberHash(n - _step);
else
_h = {};
}
return _h;
};
這里定義了一個(gè)函數(shù)nextHash()
媳荒,用來從后向前遍歷區(qū)塊hash的。_h
是當(dāng)前區(qū)塊hash钳枕,_step
值為1。
可以看到這里對(duì)區(qū)塊做了一個(gè)分段衔沼,進(jìn)行了區(qū)別處理,如果_h
所在區(qū)塊與最新區(qū)塊距離超過1000個(gè)塊指蚁,則采用區(qū)塊號(hào)遞減方式來遍歷自晰,也就是按遍歷數(shù)組的方式遍歷,即_h = m_chain.numberHash(n - _step);
缘圈,否則按單向鏈表的方式遍歷,即_h = details.parent;
糟把。
最后一部分準(zhǔn)備返回?cái)?shù)據(jù)
bytes rlp;
unsigned itemCount = 0;
vector<h256> hashes;
for (unsigned i = 0; i != numHeadersToSend; ++i)
{
if (!blockHash || !m_chain.isKnown(blockHash))
break;
hashes.push_back(blockHash);
++itemCount;
blockHash = nextHash(blockHash, step);
}
for (unsigned i = 0; i < hashes.size() && rlp.size() < c_maxPayload; ++i)
rlp += m_chain.headerData(hashes[_reverse ? i : hashes.size() - 1 - i]);
return make_pair(rlp, itemCount);
把需要返回的區(qū)塊頭放到rlp中牲剃,并統(tǒng)計(jì)返回的區(qū)塊頭數(shù)量itemCount
。
從這里可以看到有時(shí)候
itemCount
是0的缠犀,也就是可以不返回任何區(qū)塊頭数苫,在實(shí)際同步中會(huì)經(jīng)常碰到這種情況虐急。