剛寫了一篇區(qū)塊鏈同步的文章,發(fā)現(xiàn)里面有一些DAO分叉的代碼繞不過去,需要專門寫一篇來簡單介紹一下這次以太坊歷史上著名的分叉事件。
區(qū)塊鏈分叉分為硬分叉和軟分叉,區(qū)別請自行谷歌,The DAO分叉是一次硬分叉。
The DAO是一個存在于以太坊區(qū)塊鏈智能合約中的去中心化組織,這個組織于2016年4月30日起通過合約發(fā)起眾籌,短短28天就籌集了相當于1.5億美金的以太幣。然而很不幸的是合約本身存在漏洞,更不幸的是被一個(多個?)聰明的黑客利用了,6月17日他(他們?)從中盜走了價值5000萬美元的以太幣。
巨大的損失讓The DAO和全球投資者難以承受,以V神為代表的7人組決定采取反制措施,當初采取軟分叉還是硬分叉在社區(qū)是有爭議的,但是經(jīng)過各種厲害權衡,硬分叉的方案被確定了下來。
這次分叉本身也存在很大爭議,相當于網(wǎng)游中的回檔,直接宣布黑客所擁有的以太幣是無效的,簡單粗暴,但是違反了區(qū)塊鏈上已打包交易任何人無法篡改的原則,人為地干預區(qū)塊鏈也違背了去中心化的精神。因此分叉后以太坊就此分裂,由V神和大多數(shù)人支持的一支叫以太坊(ETH),另一只叫以太經(jīng)典(ETC)。ETC由于缺乏官方的支持,處于弱勢,但是還是有一批堅守區(qū)塊鏈精神的人在維持。
這次硬分叉的方案是這樣的:
- 分叉的節(jié)點位于1920000高度的區(qū)塊,該區(qū)塊的
extra data設為dao-hard-fork (Hex:0x64616f2d686172642d666f726b),作為ETH和ETC區(qū)分的標志。 - 將DAO賬戶的所有余額全部轉到一個退款合約賬戶上。
在1920000塊上總計有58個DAO和子DAO合約賬戶的共12,001,961.845205763407115004 ETH被轉到了退款合約賬戶上。
這些轉賬沒有在塊交易中體現(xiàn),而是直接被寫入源碼中!
你可以在Etherscan上找到這個區(qū)塊:區(qū)塊1920000
下面我們來看看這個分叉在源碼中的體現(xiàn):
- 首先是記錄了分叉點。
libethashseal\genesis\mainNetwork.cpp里記錄了mainnet的一些參數(shù),其中就有"daoHardforkBlock": "0x1d4c00",0x1d4c00就是1920000!libethashseal\genesis目錄下的ropsten.cpp里記錄了ropsten測試鏈的參數(shù),可以看到ropsten的daoHardforkBlock值為0,表示沒有分叉。 - 在
Block::performIrregularModifications()函數(shù)中完成了轉賬操作。
void Block::performIrregularModifications()
{
u256 const& daoHardfork = m_sealEngine->chainParams().daoHardforkBlock;
if (daoHardfork != 0 && info().number() == daoHardfork)
{
Address recipient("0xbf4ed7b27f1d666546e30d74d50d173d20bca754");
Addresses allDAOs = childDaos();
for (Address const& dao: allDAOs)
m_state.transferBalance(dao, recipient, m_state.balance(dao));
m_state.commit(State::CommitBehaviour::KeepEmptyAccounts);
}
}
這段代碼判斷如果當前塊高度為1920000時,遍歷所有的DAO賬號,將所有余額轉到recipient中,recipient就是那個退款合約賬戶。
分叉影響也波及到了區(qū)塊鏈同步模塊:
- 收到peer的StatusPacket包后,需要進行一個DAO challenge。
BlockChainSync::onPeerStatus()中有如下代碼:
// Before starting to exchange the data with the node, let's verify that it's on our chain
if (!requestDaoForkBlockHeader(_peer))
{
// DAO challenge not needed
syncPeer(_peer, false);
}
BlockChainSync::requestDaoForkBlockHeader()函數(shù)實現(xiàn)為:
bool BlockChainSync::requestDaoForkBlockHeader(std::shared_ptr<EthereumPeer> _peer)
{
// DAO challenge
unsigned const daoHardfork = static_cast<unsigned>(host().chain().sealEngine()->chainParams().daoHardforkBlock);
if (daoHardfork == 0)
return false;
m_daoChallengedPeers.insert(_peer);
_peer->requestBlockHeaders(daoHardfork, 1, 0, false);
return true;
}
對于mainnet來說,daoHardfork為1920000,因此它首先需要向該peer請求這個分叉塊頭。
- 收到對方的
BlockHeadersPacket包后還需要校驗:
if (m_daoChallengedPeers.find(_peer) != m_daoChallengedPeers.end())
{
if (verifyDaoChallengeResponse(_r))
syncPeer(_peer, false);
else
_peer->disable("Peer from another fork.");
m_daoChallengedPeers.erase(_peer);
return;
}
校驗的代碼位于BlockChainSync::verifyDaoChallengeResponse()函數(shù)中:
bool BlockChainSync::verifyDaoChallengeResponse(RLP const& _r)
{
if (_r.itemCount() != 1)
return false;
BlockHeader info(_r[0].data(), HeaderData);
return info.number() == host().chain().sealEngine()->chainParams().daoHardforkBlock &&
info.extraData() == fromHex("0x64616f2d686172642d666f726b");
}
可以看到校驗的方法很簡單,就是看塊頭的extraData里記錄的是不是dao-hard-fork (Hex:0x64616f2d686172642d666f726b),如果是則說明是ETH,可以繼續(xù)同步,否則是ETC,禁用該peer。
節(jié)點通過DAO challenge后就可以按正常流程同步了。