arkpar
10 years ago
10 changed files with 941 additions and 589 deletions
@ -0,0 +1,701 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file EthereumHost.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "BlockChainSync.h" |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include <thread> |
||||
|
#include <libdevcore/Common.h> |
||||
|
#include <libp2p/Host.h> |
||||
|
#include <libp2p/Session.h> |
||||
|
#include <libethcore/Exceptions.h> |
||||
|
#include <libethcore/Params.h> |
||||
|
#include "BlockChain.h" |
||||
|
#include "TransactionQueue.h" |
||||
|
#include "BlockQueue.h" |
||||
|
#include "EthereumPeer.h" |
||||
|
#include "EthereumHost.h" |
||||
|
#include "DownloadMan.h" |
||||
|
using namespace std; |
||||
|
using namespace dev; |
||||
|
using namespace dev::eth; |
||||
|
using namespace p2p; |
||||
|
|
||||
|
|
||||
|
unsigned const c_chainReorgSize = 30000; |
||||
|
|
||||
|
|
||||
|
BlockChainSync::BlockChainSync(EthereumHost& _host): |
||||
|
m_host(_host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
BlockChainSync::~BlockChainSync() |
||||
|
{ |
||||
|
abortSync(); |
||||
|
} |
||||
|
|
||||
|
DownloadMan const& BlockChainSync::downloadMan() const |
||||
|
{ |
||||
|
return host().downloadMan(); |
||||
|
} |
||||
|
|
||||
|
DownloadMan& BlockChainSync::downloadMan() |
||||
|
{ |
||||
|
return host().downloadMan(); |
||||
|
} |
||||
|
|
||||
|
void BlockChainSync::abortSync() |
||||
|
{ |
||||
|
host().foreachPeer([this](EthereumPeer* _p) { onPeerAborting(_p); return true; }); |
||||
|
downloadMan().resetToChain(h256s()); |
||||
|
} |
||||
|
|
||||
|
void BlockChainSync::onPeerStatus(EthereumPeer*) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
unsigned BlockChainSync::estimateHashes() |
||||
|
{ |
||||
|
BlockInfo block = host().chain().info(); |
||||
|
time_t lastBlockTime = (block.hash() == host().chain().genesisHash()) ? 1428192000 : (time_t)block.timestamp; |
||||
|
time_t now = time(0); |
||||
|
unsigned blockCount = c_chainReorgSize; |
||||
|
if (lastBlockTime > now) |
||||
|
clog(NetWarn) << "Clock skew? Latest block is in the future"; |
||||
|
else |
||||
|
blockCount += (now - lastBlockTime) / (unsigned)c_durationLimit; |
||||
|
clog(NetAllDetail) << "Estimated hashes: " << blockCount; |
||||
|
return blockCount; |
||||
|
} |
||||
|
|
||||
|
PV60Sync::PV60Sync(EthereumHost& _host): |
||||
|
BlockChainSync(_host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
SyncStatus PV60Sync::status() const |
||||
|
{ |
||||
|
RecursiveGuard l(x_sync); |
||||
|
SyncStatus res; |
||||
|
res.state = m_state; |
||||
|
if (m_state == SyncState::Hashes) |
||||
|
{ |
||||
|
res.hashesTotal = m_estimatedHashes; |
||||
|
res.hashesReceived = static_cast<unsigned>(m_syncingNeededBlocks.size()); |
||||
|
res.hashesEstimated = true; |
||||
|
} |
||||
|
else if (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks || m_state == SyncState::Waiting) |
||||
|
{ |
||||
|
res.blocksTotal = downloadMan().chainSize(); |
||||
|
res.blocksReceived = downloadMan().blocksGot().size(); |
||||
|
} |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::setState(EthereumPeer* _peer, SyncState _s, bool _isSyncing, bool _needHelp) |
||||
|
{ |
||||
|
bool changedState = (m_state != _s); |
||||
|
m_state = _s; |
||||
|
|
||||
|
if (_isSyncing != (m_syncer == _peer) || (_isSyncing && changedState)) |
||||
|
changeSyncer(_isSyncing ? _peer : nullptr, _needHelp); |
||||
|
else if (_s == SyncState::Idle) |
||||
|
changeSyncer(nullptr, _needHelp); |
||||
|
|
||||
|
assert(!!m_syncer || _s == SyncState::Idle); |
||||
|
|
||||
|
if (!_isSyncing) |
||||
|
{ |
||||
|
m_syncingLatestHash = h256(); |
||||
|
m_syncingTotalDifficulty = 0; |
||||
|
m_syncingNeededBlocks.clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::transition(EthereumPeer* _peer, SyncState _s, bool _force, bool _needHelp) |
||||
|
{ |
||||
|
clog(NetMessageSummary) << "Transition!" << EthereumHost::stateName(_s) << "from" << EthereumHost::stateName(m_state) << ", " << (isSyncing(_peer) ? "syncing" : "holding") << (needsSyncing(_peer) ? "& needed" : ""); |
||||
|
|
||||
|
//DEV_INVARIANT_CHECK;
|
||||
|
if (m_state == SyncState::Idle && _s != SyncState::Idle) |
||||
|
_peer->m_requireTransactions = true; |
||||
|
|
||||
|
RLPStream s; |
||||
|
if (_s == SyncState::Hashes) |
||||
|
{ |
||||
|
if (m_state == SyncState::Idle) |
||||
|
{ |
||||
|
if (isSyncing(_peer)) |
||||
|
clog(NetWarn) << "Bad state: not asking for Hashes, yet syncing!"; |
||||
|
|
||||
|
m_syncingLatestHash = _peer->m_latestHash; |
||||
|
m_syncingTotalDifficulty = _peer->m_totalDifficulty; |
||||
|
setState(_peer, _s, true); |
||||
|
_peer->requestHashes(m_syncingLatestHash); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
return; |
||||
|
} |
||||
|
else if (m_state == SyncState::Hashes) |
||||
|
{ |
||||
|
if (!isSyncing(_peer)) |
||||
|
clog(NetWarn) << "Bad state: asking for Hashes yet not syncing!"; |
||||
|
|
||||
|
setState(_peer, _s, true); |
||||
|
_peer->requestHashes(m_syncingLastReceivedHash); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
else if (_s == SyncState::Blocks) |
||||
|
{ |
||||
|
if (m_state == SyncState::Hashes) |
||||
|
{ |
||||
|
if (!isSyncing(_peer)) |
||||
|
{ |
||||
|
clog(NetWarn) << "Bad state: asking for Hashes yet not syncing!"; |
||||
|
return; |
||||
|
} |
||||
|
if (shouldGrabBlocks(_peer)) |
||||
|
{ |
||||
|
clog(NetNote) << "Difficulty of hashchain HIGHER. Grabbing" << m_syncingNeededBlocks.size() << "blocks [latest now" << m_syncingLatestHash << ", was" << host().latestBlockSent() << "]"; |
||||
|
downloadMan().resetToChain(m_syncingNeededBlocks); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
clog(NetNote) << "Difficulty of hashchain not HIGHER. Ignoring."; |
||||
|
m_syncingLatestHash = h256(); |
||||
|
setState(_peer, SyncState::Idle, false); |
||||
|
return; |
||||
|
} |
||||
|
assert (isSyncing(_peer)); |
||||
|
} |
||||
|
// run through into...
|
||||
|
if (m_state == SyncState::Idle || m_state == SyncState::Hashes || m_state == SyncState::Blocks) |
||||
|
{ |
||||
|
// Looks like it's the best yet for total difficulty. Set to download.
|
||||
|
setState(_peer, SyncState::Blocks, isSyncing(_peer), _needHelp); // will kick off other peers to help if available.
|
||||
|
requestBlocks(_peer); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
else if (_s == SyncState::NewBlocks) |
||||
|
{ |
||||
|
if (m_state != SyncState::Idle && m_state != SyncState::NewBlocks) |
||||
|
clog(NetWarn) << "Bad state: Asking new blocks while syncing!"; |
||||
|
else |
||||
|
{ |
||||
|
setState(_peer, SyncState::NewBlocks, true, _needHelp); |
||||
|
requestBlocks(_peer); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
else if (_s == SyncState::Idle) |
||||
|
{ |
||||
|
host().foreachPeer([this](EthereumPeer* _p) { _p->setIdle(); return true; }); |
||||
|
if (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks) |
||||
|
{ |
||||
|
clog(NetNote) << "Finishing blocks fetch..."; |
||||
|
|
||||
|
// a bit overkill given that the other nodes may yet have the needed blocks, but better to be safe than sorry.
|
||||
|
if (isSyncing(_peer)) |
||||
|
noteDoneBlocks(_peer, _force); |
||||
|
|
||||
|
// NOTE: need to notify of giving up on chain-hashes, too, altering state as necessary.
|
||||
|
_peer->m_sub.doneFetch(); |
||||
|
_peer->setIdle(); |
||||
|
setState(_peer, SyncState::Idle, false); |
||||
|
} |
||||
|
else if (m_state == SyncState::Hashes) |
||||
|
{ |
||||
|
clog(NetNote) << "Finishing hashes fetch..."; |
||||
|
setState(_peer, SyncState::Idle, false); |
||||
|
} |
||||
|
// Otherwise it's fine. We don't care if it's Nothing->Nothing.
|
||||
|
DEV_INVARIANT_CHECK; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
clog(NetWarn) << "Invalid state transition:" << EthereumHost::stateName(_s) << "from" << EthereumHost::stateName(m_state) << ", " << (isSyncing(_peer) ? "syncing" : "holding") << (needsSyncing(_peer) ? "& needed" : ""); |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::requestBlocks(EthereumPeer* _peer) |
||||
|
{ |
||||
|
_peer->requestBlocks(); |
||||
|
if (_peer->m_asking != Asking::Blocks) //nothing to download
|
||||
|
{ |
||||
|
noteDoneBlocks(_peer, false); |
||||
|
if (downloadMan().isComplete()) |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::setNeedsSyncing(EthereumPeer* _peer, h256 _latestHash, u256 _td) |
||||
|
{ |
||||
|
_peer->m_latestHash = _latestHash; |
||||
|
_peer->m_totalDifficulty = _td; |
||||
|
|
||||
|
if (_peer->m_latestHash) |
||||
|
noteNeedsSyncing(_peer); |
||||
|
|
||||
|
_peer->session()->addNote("sync", string(isSyncing(_peer) ? "ongoing" : "holding") + (needsSyncing(_peer) ? " & needed" : "")); |
||||
|
} |
||||
|
|
||||
|
bool PV60Sync::isSyncing(EthereumPeer* _peer) const |
||||
|
{ |
||||
|
return m_syncer == _peer; |
||||
|
} |
||||
|
|
||||
|
bool PV60Sync::shouldGrabBlocks(EthereumPeer* _peer) const |
||||
|
{ |
||||
|
auto td = _peer->m_totalDifficulty; |
||||
|
auto lh = _peer->m_latestHash; |
||||
|
auto ctd = host().chain().details().totalDifficulty; |
||||
|
|
||||
|
if (m_syncingNeededBlocks.empty()) |
||||
|
return false; |
||||
|
|
||||
|
clog(NetNote) << "Should grab blocks? " << td << "vs" << ctd << ";" << m_syncingNeededBlocks.size() << " blocks, ends" << m_syncingNeededBlocks.back(); |
||||
|
|
||||
|
if (td < ctd || (td == ctd && host().chain().currentHash() == lh)) |
||||
|
return false; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::attemptSync(EthereumPeer* _peer) |
||||
|
{ |
||||
|
if (m_state != SyncState::Idle) |
||||
|
{ |
||||
|
clog(NetAllDetail) << "Can't sync with this peer - outstanding asks."; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// if already done this, then ignore.
|
||||
|
if (!needsSyncing(_peer)) |
||||
|
{ |
||||
|
clog(NetAllDetail) << "Already synced with this peer."; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
h256 c = host().chain().currentHash(); |
||||
|
unsigned n = host().chain().number(); |
||||
|
u256 td = host().chain().details().totalDifficulty; |
||||
|
|
||||
|
clog(NetAllDetail) << "Attempt chain-grab? Latest:" << c << ", number:" << n << ", TD:" << td << " versus " << _peer->m_totalDifficulty; |
||||
|
if (td >= _peer->m_totalDifficulty) |
||||
|
{ |
||||
|
clog(NetAllDetail) << "No. Our chain is better."; |
||||
|
resetNeedsSyncing(_peer); |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
clog(NetAllDetail) << "Yes. Their chain is better."; |
||||
|
m_estimatedHashes = _peer->m_expectedHashes - c_chainReorgSize; |
||||
|
transition(_peer, SyncState::Hashes); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::noteNeedsSyncing(EthereumPeer* _peer) |
||||
|
{ |
||||
|
// if already downloading hash-chain, ignore.
|
||||
|
if (isSyncing()) |
||||
|
{ |
||||
|
clog(NetAllDetail) << "Sync in progress: Just set to help out."; |
||||
|
if (m_state == SyncState::Blocks) |
||||
|
_peer->requestBlocks(); |
||||
|
} |
||||
|
else |
||||
|
// otherwise check to see if we should be downloading...
|
||||
|
attemptSync(_peer); |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::changeSyncer(EthereumPeer* _syncer, bool _needHelp) |
||||
|
{ |
||||
|
if (_syncer) |
||||
|
clog(NetAllDetail) << "Changing syncer to" << _syncer->session()->socketId(); |
||||
|
else |
||||
|
clog(NetAllDetail) << "Clearing syncer."; |
||||
|
|
||||
|
m_syncer = _syncer; |
||||
|
if (isSyncing()) |
||||
|
{ |
||||
|
if (_needHelp && (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks)) |
||||
|
host().foreachPeer([&](EthereumPeer* _p) |
||||
|
{ |
||||
|
clog(NetNote) << "Getting help with downloading blocks"; |
||||
|
if (_p != _syncer && _p->m_asking == Asking::Nothing) |
||||
|
transition(_p, m_state); |
||||
|
return true; |
||||
|
}); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// start grabbing next hash chain if there is one.
|
||||
|
host().foreachPeer([this](EthereumPeer* _p) |
||||
|
{ |
||||
|
attemptSync(_p); |
||||
|
return !isSyncing(); |
||||
|
}); |
||||
|
if (!isSyncing()) |
||||
|
{ |
||||
|
if (m_state != SyncState::Idle) |
||||
|
setState(_syncer, SyncState::Idle); |
||||
|
clog(NetNote) << "No more peers to sync with."; |
||||
|
} |
||||
|
} |
||||
|
assert(!!m_syncer || m_state == SyncState::Idle); |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::noteDoneBlocks(EthereumPeer* _peer, bool _clemency) |
||||
|
{ |
||||
|
resetNeedsSyncing(_peer); |
||||
|
if (downloadMan().isComplete()) |
||||
|
{ |
||||
|
// Done our chain-get.
|
||||
|
clog(NetNote) << "Chain download complete."; |
||||
|
// 1/100th for each useful block hash.
|
||||
|
_peer->addRating(downloadMan().chainSize() / 100); |
||||
|
downloadMan().reset(); |
||||
|
} |
||||
|
else if (isSyncing(_peer)) |
||||
|
{ |
||||
|
if (_clemency) |
||||
|
clog(NetNote) << "Chain download failed. Aborted while incomplete."; |
||||
|
else |
||||
|
{ |
||||
|
// Done our chain-get.
|
||||
|
clog(NetWarn) << "Chain download failed. Peer with blocks didn't have them all. This peer is bad and should be punished."; |
||||
|
clog(NetWarn) << downloadMan().remaining(); |
||||
|
clog(NetWarn) << "WOULD BAN."; |
||||
|
// m_banned.insert(_peer->session()->id()); // We know who you are!
|
||||
|
// _peer->disable("Peer sent hashes but was unable to provide the blocks.");
|
||||
|
} |
||||
|
downloadMan().reset(); |
||||
|
} |
||||
|
_peer->m_sub.doneFetch(); |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerStatus(EthereumPeer* _peer) |
||||
|
{ |
||||
|
RecursiveGuard l(x_sync); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
if (_peer->m_genesisHash != host().chain().genesisHash()) |
||||
|
_peer->disable("Invalid genesis hash"); |
||||
|
else if (_peer->m_protocolVersion != host().protocolVersion() && _peer->m_protocolVersion != EthereumHost::c_oldProtocolVersion) |
||||
|
_peer->disable("Invalid protocol version."); |
||||
|
else if (_peer->m_networkId != host().networkId()) |
||||
|
_peer->disable("Invalid network identifier."); |
||||
|
else if (_peer->session()->info().clientVersion.find("/v0.7.0/") != string::npos) |
||||
|
_peer->disable("Blacklisted client version."); |
||||
|
else if (host().isBanned(_peer->session()->id())) |
||||
|
_peer->disable("Peer banned for previous bad behaviour."); |
||||
|
else |
||||
|
{ |
||||
|
unsigned estimatedHashes = estimateHashes(); |
||||
|
_peer->m_expectedHashes = estimatedHashes; |
||||
|
setNeedsSyncing(_peer, _peer->m_latestHash, _peer->m_totalDifficulty); |
||||
|
} |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerBlocks(EthereumPeer* _peer, RLP const& _r) |
||||
|
{ |
||||
|
RecursiveGuard l(x_sync); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
unsigned itemCount = _r.itemCount(); |
||||
|
clog(NetMessageSummary) << "Blocks (" << dec << itemCount << "entries)" << (itemCount ? "" : ": NoMoreBlocks"); |
||||
|
|
||||
|
_peer->setIdle(); |
||||
|
if (m_state != SyncState::Blocks && m_state != SyncState::NewBlocks) |
||||
|
clog(NetWarn) << "Unexpected Blocks received!"; |
||||
|
|
||||
|
if (itemCount == 0) |
||||
|
{ |
||||
|
// Got to this peer's latest block - just give up.
|
||||
|
noteDoneBlocks(_peer, false); |
||||
|
if (downloadMan().isComplete()) |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
unsigned success = 0; |
||||
|
unsigned future = 0; |
||||
|
unsigned unknown = 0; |
||||
|
unsigned got = 0; |
||||
|
unsigned repeated = 0; |
||||
|
u256 maxDifficulty = 0; |
||||
|
h256 maxUnknown; |
||||
|
|
||||
|
for (unsigned i = 0; i < itemCount; ++i) |
||||
|
{ |
||||
|
auto h = BlockInfo::headerHash(_r[i].data()); |
||||
|
if (_peer->m_sub.noteBlock(h)) |
||||
|
{ |
||||
|
_peer->addRating(10); |
||||
|
switch (host().bq().import(_r[i].data(), host().chain())) |
||||
|
{ |
||||
|
case ImportResult::Success: |
||||
|
success++; |
||||
|
break; |
||||
|
|
||||
|
case ImportResult::Malformed: |
||||
|
case ImportResult::BadChain: |
||||
|
_peer->disable("Malformed block received."); |
||||
|
return; |
||||
|
|
||||
|
case ImportResult::FutureTimeKnown: |
||||
|
future++; |
||||
|
break; |
||||
|
case ImportResult::AlreadyInChain: |
||||
|
case ImportResult::AlreadyKnown: |
||||
|
got++; |
||||
|
break; |
||||
|
|
||||
|
case ImportResult::FutureTimeUnkwnown: |
||||
|
future++; //Fall through
|
||||
|
|
||||
|
case ImportResult::UnknownParent: |
||||
|
{ |
||||
|
unknown++; |
||||
|
if (m_state == SyncState::NewBlocks) |
||||
|
{ |
||||
|
BlockInfo bi; |
||||
|
bi.populateFromHeader(_r[i][0]); |
||||
|
if (bi.difficulty > maxDifficulty) |
||||
|
{ |
||||
|
maxDifficulty = bi.difficulty; |
||||
|
maxUnknown = h; |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
default:; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_peer->addRating(0); // -1?
|
||||
|
repeated++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
clog(NetMessageSummary) << dec << success << "imported OK," << unknown << "with unknown parents," << future << "with future timestamps," << got << " already known," << repeated << " repeats received."; |
||||
|
|
||||
|
if (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks) |
||||
|
{ |
||||
|
if (downloadMan().isComplete()) |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
else if (!got) |
||||
|
transition(_peer, m_state); |
||||
|
else |
||||
|
noteDoneBlocks(_peer, false); |
||||
|
} |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerHashes(EthereumPeer* _peer, h256s const& _hashes) |
||||
|
{ |
||||
|
RecursiveGuard l(x_sync); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
_peer->setIdle(); |
||||
|
if (!isSyncing(_peer)) |
||||
|
{ |
||||
|
clog(NetMessageSummary) << "Ignoring hashes synce not syncing"; |
||||
|
return; |
||||
|
} |
||||
|
if (_hashes.size() == 0) |
||||
|
{ |
||||
|
transition(_peer, SyncState::Blocks); |
||||
|
return; |
||||
|
} |
||||
|
unsigned knowns = 0; |
||||
|
unsigned unknowns = 0; |
||||
|
for (unsigned i = 0; i < _hashes.size(); ++i) |
||||
|
{ |
||||
|
auto h = _hashes[i]; |
||||
|
auto status = host().bq().blockStatus(h); |
||||
|
if (status == QueueStatus::Importing || status == QueueStatus::Ready || host().chain().isKnown(h)) |
||||
|
{ |
||||
|
clog(NetMessageSummary) << "block hash ready:" << h << ". Start blocks download..."; |
||||
|
assert (isSyncing(_peer)); |
||||
|
transition(_peer, SyncState::Blocks); |
||||
|
return; |
||||
|
} |
||||
|
else if (status == QueueStatus::Bad) |
||||
|
{ |
||||
|
cwarn << "block hash bad!" << h << ". Bailing..."; |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
return; |
||||
|
} |
||||
|
else if (status == QueueStatus::Unknown) |
||||
|
{ |
||||
|
unknowns++; |
||||
|
m_syncingNeededBlocks.push_back(h); |
||||
|
} |
||||
|
else |
||||
|
knowns++; |
||||
|
m_syncingLastReceivedHash = h; |
||||
|
} |
||||
|
clog(NetMessageSummary) << knowns << "knowns," << unknowns << "unknowns; now at" << m_syncingLastReceivedHash; |
||||
|
if (m_syncingNeededBlocks.size() > _peer->m_expectedHashes) |
||||
|
{ |
||||
|
_peer->disable("Too many hashes"); |
||||
|
m_syncingNeededBlocks.clear(); |
||||
|
m_syncingLatestHash = h256(); |
||||
|
transition(_peer, SyncState::Idle); |
||||
|
return; |
||||
|
} |
||||
|
// run through - ask for more.
|
||||
|
transition(_peer, SyncState::Hashes); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void PV60Sync::abortSync(EthereumPeer* _peer) |
||||
|
{ |
||||
|
if (isSyncing(_peer)) |
||||
|
{ |
||||
|
host().foreachPeer([this](EthereumPeer* _p) { _p->setIdle(); return true; }); |
||||
|
transition(_peer, SyncState::Idle, true); |
||||
|
} |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerAborting(EthereumPeer* _peer) |
||||
|
{ |
||||
|
abortSync(_peer); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerNewBlock(EthereumPeer* _peer, RLP const& _r) |
||||
|
{ |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
RecursiveGuard l(x_sync); |
||||
|
auto h = BlockInfo::headerHash(_r[0].data()); |
||||
|
clog(NetMessageSummary) << "NewBlock: " << h; |
||||
|
|
||||
|
if (_r.itemCount() != 2) |
||||
|
_peer->disable("NewBlock without 2 data fields."); |
||||
|
else |
||||
|
{ |
||||
|
switch (host().bq().import(_r[0].data(), host().chain())) |
||||
|
{ |
||||
|
case ImportResult::Success: |
||||
|
_peer->addRating(100); |
||||
|
break; |
||||
|
case ImportResult::FutureTimeKnown: |
||||
|
//TODO: Rating dependent on how far in future it is.
|
||||
|
break; |
||||
|
|
||||
|
case ImportResult::Malformed: |
||||
|
case ImportResult::BadChain: |
||||
|
_peer->disable("Malformed block received."); |
||||
|
return; |
||||
|
|
||||
|
case ImportResult::AlreadyInChain: |
||||
|
case ImportResult::AlreadyKnown: |
||||
|
break; |
||||
|
|
||||
|
case ImportResult::FutureTimeUnkwnown: |
||||
|
case ImportResult::UnknownParent: |
||||
|
clog(NetMessageSummary) << "Received block with no known parent. Resyncing..."; |
||||
|
setNeedsSyncing(_peer, h, _r[1].toInt<u256>()); |
||||
|
break; |
||||
|
default:; |
||||
|
} |
||||
|
|
||||
|
DEV_GUARDED(_peer->x_knownBlocks) |
||||
|
_peer->m_knownBlocks.insert(h); |
||||
|
} |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
void PV60Sync::onPeerNewHashes(EthereumPeer* _peer, h256s const& _hashes) |
||||
|
{ |
||||
|
RecursiveGuard l(x_sync); |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
if (isSyncing()) |
||||
|
{ |
||||
|
clog(NetMessageSummary) << "Ignoring since we're already downloading."; |
||||
|
return; |
||||
|
} |
||||
|
unsigned knowns = 0; |
||||
|
unsigned unknowns = 0; |
||||
|
for (auto h: _hashes) |
||||
|
{ |
||||
|
_peer->addRating(1); |
||||
|
DEV_GUARDED(_peer->x_knownBlocks) |
||||
|
_peer->m_knownBlocks.insert(h); |
||||
|
auto status = host().bq().blockStatus(h); |
||||
|
if (status == QueueStatus::Importing || status == QueueStatus::Ready || host().chain().isKnown(h)) |
||||
|
knowns++; |
||||
|
else if (status == QueueStatus::Bad) |
||||
|
{ |
||||
|
cwarn << "block hash bad!" << h << ". Bailing..."; |
||||
|
return; |
||||
|
} |
||||
|
else if (status == QueueStatus::Unknown) |
||||
|
{ |
||||
|
unknowns++; |
||||
|
m_syncingNeededBlocks.push_back(h); |
||||
|
} |
||||
|
else |
||||
|
knowns++; |
||||
|
} |
||||
|
clog(NetMessageSummary) << knowns << "knowns," << unknowns << "unknowns"; |
||||
|
if (unknowns > 0) |
||||
|
{ |
||||
|
clog(NetNote) << "Not syncing and new block hash discovered: syncing without help."; |
||||
|
downloadMan().resetToChain(m_syncingNeededBlocks); |
||||
|
transition(_peer, SyncState::NewBlocks, false, false); |
||||
|
} |
||||
|
DEV_INVARIANT_CHECK; |
||||
|
} |
||||
|
|
||||
|
bool PV60Sync::invariants() const |
||||
|
{ |
||||
|
if (m_state == SyncState::Idle && !!m_syncer) |
||||
|
return false; |
||||
|
if (m_state != SyncState::Idle && !m_syncer) |
||||
|
return false; |
||||
|
if (m_state == SyncState::Hashes) |
||||
|
{ |
||||
|
bool hashes = false; |
||||
|
host().foreachPeer([&](EthereumPeer* _p) { if (_p->m_asking == Asking::Hashes) hashes = true; return !hashes; }); |
||||
|
if (!hashes) |
||||
|
return false; |
||||
|
} |
||||
|
if (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks) |
||||
|
{ |
||||
|
bool blocks = false; |
||||
|
host().foreachPeer([&](EthereumPeer* _p) { if (_p->m_asking == Asking::Blocks) blocks = true; return !blocks; }); |
||||
|
if (!blocks) |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
@ -0,0 +1,146 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file EthereumHost.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <mutex> |
||||
|
#include <unordered_map> |
||||
|
#include <vector> |
||||
|
#include <unordered_set> |
||||
|
#include <memory> |
||||
|
#include <utility> |
||||
|
#include <thread> |
||||
|
|
||||
|
#include <libdevcore/Guards.h> |
||||
|
#include <libdevcore/Worker.h> |
||||
|
#include <libdevcore/RangeMask.h> |
||||
|
#include <libethcore/Common.h> |
||||
|
#include <libp2p/Common.h> |
||||
|
#include "CommonNet.h" |
||||
|
#include "EthereumPeer.h" //TODO: forward decl |
||||
|
#include "DownloadMan.h" |
||||
|
|
||||
|
|
||||
|
namespace dev |
||||
|
{ |
||||
|
|
||||
|
class RLPStream; |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
class EthereumHost; |
||||
|
class BlockQueue; |
||||
|
|
||||
|
/**
|
||||
|
* @brief BlockChain synchronization strategy class |
||||
|
* @doWork Syncs to peers and sends new blocks and transactions. |
||||
|
*/ |
||||
|
class BlockChainSync: public HasInvariants |
||||
|
{ |
||||
|
public: |
||||
|
BlockChainSync(EthereumHost& _host); |
||||
|
|
||||
|
/// Will block on network process events.
|
||||
|
virtual ~BlockChainSync(); |
||||
|
void abortSync(); |
||||
|
|
||||
|
DownloadMan const& downloadMan() const; |
||||
|
DownloadMan& downloadMan(); |
||||
|
virtual bool isSyncing() const = 0; |
||||
|
virtual void onPeerStatus(EthereumPeer* _peer); ///< Called by peer to report status
|
||||
|
virtual void onPeerBlocks(EthereumPeer* _peer, RLP const& _r) = 0; ///< Called by peer once it has new blocks during syn
|
||||
|
virtual void onPeerNewBlock(EthereumPeer* _peer, RLP const& _r) = 0; ///< Called by peer once it has new blocks
|
||||
|
virtual void onPeerNewHashes(EthereumPeer* _peer, h256s const& _hashes) = 0; ///< Called by peer once it has new hashes
|
||||
|
virtual void onPeerHashes(EthereumPeer* _peer, h256s const& _hashes) = 0; ///< Called by peer once it has another sequential block of hashes during sync
|
||||
|
virtual void onPeerAborting(EthereumPeer* _peer) = 0; ///< Called by peer when it is disconnecting
|
||||
|
virtual SyncStatus status() const = 0; |
||||
|
|
||||
|
static char const* stateName(SyncState _s) { return s_stateNames[static_cast<int>(_s)]; } |
||||
|
|
||||
|
private: |
||||
|
static char const* const s_stateNames[static_cast<int>(SyncState::Size)]; |
||||
|
|
||||
|
void setState(SyncState _s); |
||||
|
|
||||
|
bool invariants() const override = 0; |
||||
|
|
||||
|
EthereumHost& m_host; |
||||
|
Handler m_bqRoomAvailable; |
||||
|
HashDownloadMan m_hashMan; |
||||
|
|
||||
|
protected: |
||||
|
|
||||
|
EthereumHost& host() { return m_host; } |
||||
|
EthereumHost const& host() const { return m_host; } |
||||
|
unsigned estimateHashes(); |
||||
|
|
||||
|
mutable RecursiveMutex x_sync; |
||||
|
SyncState m_state = SyncState::Idle; ///< Current sync state
|
||||
|
SyncState m_lastActiveState = SyncState::Idle; ///< Saved state before entering waiting queue mode
|
||||
|
unsigned m_estimatedHashes = 0; ///< Number of estimated hashes for the last peer over PV60. Used for status reporting only.
|
||||
|
}; |
||||
|
|
||||
|
class PV60Sync: public BlockChainSync |
||||
|
{ |
||||
|
public: |
||||
|
|
||||
|
PV60Sync(EthereumHost& _host); |
||||
|
|
||||
|
bool isSyncing() const override { return !!m_syncer; } |
||||
|
void onPeerStatus(EthereumPeer* _peer) override; ///< Called by peer to report status
|
||||
|
void onPeerBlocks(EthereumPeer* _peer, RLP const& _r) override; ///< Called by peer once it has new blocks during syn
|
||||
|
void onPeerNewBlock(EthereumPeer* _peer, RLP const& _r) override; ///< Called by peer once it has new blocks
|
||||
|
void onPeerNewHashes(EthereumPeer* _peer, h256s const& _hashes) override; ///< Called by peer once it has new hashes
|
||||
|
void onPeerHashes(EthereumPeer* _peer, h256s const& _hashes) override; ///< Called by peer once it has another sequential block of hashes during sync
|
||||
|
void onPeerAborting(EthereumPeer* _peer) override; ///< Called by peer when it is disconnecting
|
||||
|
SyncStatus status() const override; |
||||
|
|
||||
|
void transition(EthereumPeer* _peer, SyncState _s, bool _force = false, bool _needHelp = true); |
||||
|
void resetNeedsSyncing(EthereumPeer* _peer) { setNeedsSyncing(_peer, h256(), 0); } |
||||
|
bool needsSyncing(EthereumPeer* _peer) const { return !!_peer->m_latestHash; } |
||||
|
|
||||
|
void setNeedsSyncing(EthereumPeer* _peer, h256 _latestHash, u256 _td); |
||||
|
bool shouldGrabBlocks(EthereumPeer* _peer) const; |
||||
|
void attemptSync(EthereumPeer* _peer); |
||||
|
void setState(EthereumPeer* _peer, SyncState _s, bool _isSyncing = false, bool _needHelp = false); |
||||
|
bool isSyncing(EthereumPeer* _peer) const; |
||||
|
void noteNeedsSyncing(EthereumPeer* _who); |
||||
|
void changeSyncer(EthereumPeer* _syncer, bool _needHelp); |
||||
|
void noteDoneBlocks(EthereumPeer* _who, bool _clemency); |
||||
|
void abortSync(EthereumPeer* _peer); |
||||
|
void requestBlocks(EthereumPeer* _peer); |
||||
|
|
||||
|
|
||||
|
private: |
||||
|
bool invariants() const override; |
||||
|
|
||||
|
h256s m_knownHashes; ///< List of block hashes we need to download.
|
||||
|
|
||||
|
h256s m_syncingNeededBlocks; ///< The blocks that we should download from this peer.
|
||||
|
h256 m_syncingLastReceivedHash; ///< Hash most recently received from peer.
|
||||
|
h256 m_syncingLatestHash; ///< Peer's latest block's hash, as of the current sync.
|
||||
|
u256 m_syncingTotalDifficulty; ///< Peer's latest block's total difficulty, as of the current sync.
|
||||
|
EthereumPeer* m_syncer = nullptr; // TODO: switch to weak_ptr
|
||||
|
|
||||
|
}; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue