/*
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 .
*/
/** @file BlockChainSync.h
* @author Gav Wood
* @date 2014
*/
#pragma once
#include
#include
#include
#include
#include "CommonNet.h"
#include "DownloadMan.h"
namespace dev
{
class RLPStream;
namespace eth
{
class EthereumHost;
class BlockQueue;
class EthereumPeer;
/**
* @brief Base BlockChain synchronization strategy class.
* Syncs to peers and keeps up to date. Base class handles blocks downloading but does not contain any details on state transfer logic.
*/
class BlockChainSync: public HasInvariants
{
public:
BlockChainSync(EthereumHost& _host);
virtual ~BlockChainSync();
void abortSync(); ///< Abort all sync activity
DownloadMan const& downloadMan() const;
DownloadMan& downloadMan();
/// @returns true is Sync is in progress
virtual bool isSyncing() const = 0;
/// Restart sync
virtual void restartSync() = 0;
/// Called by peer to report status
virtual void onPeerStatus(std::shared_ptr _peer);
/// Called by peer once it has new blocks during syn
virtual void onPeerBlocks(std::shared_ptr _peer, RLP const& _r);
/// Called by peer once it has new blocks
virtual void onPeerNewBlock(std::shared_ptr _peer, RLP const& _r);
/// Called by peer once it has new hashes
virtual void onPeerNewHashes(std::shared_ptr _peer, h256s const& _hashes) = 0;
/// Called by peer once it has another sequential block of hashes during sync
virtual void onPeerHashes(std::shared_ptr _peer, h256s const& _hashes) = 0;
/// Called by peer when it is disconnecting
virtual void onPeerAborting() = 0;
/// @returns Synchonization status
virtual SyncStatus status() const = 0;
static char const* stateName(SyncState _s) { return s_stateNames[static_cast(_s)]; }
protected:
//To be implemented in derived classes:
/// New valid peer appears
virtual void onNewPeer(std::shared_ptr _peer) = 0;
/// Peer done downloading blocks
virtual void peerDoneBlocks(std::shared_ptr _peer) = 0;
/// Resume downloading after witing state
virtual void continueSync() = 0;
/// Called after all blocks have been donloaded
virtual void completeSync() = 0;
/// Enter waiting state
virtual void pauseSync() = 0;
/// Restart sync for given peer
virtual void resetSyncFor(std::shared_ptr _peer, h256 const& _latestHash, u256 const& _td) = 0;
EthereumHost& host() { return m_host; }
EthereumHost const& host() const { return m_host; }
/// Estimates max number of hashes peers can give us.
unsigned estimatedHashes() const;
/// Request blocks from peer if needed
void requestBlocks(std::shared_ptr _peer);
protected:
Handler<> m_bqRoomAvailable; ///< Triggered once block queue
mutable RecursiveMutex x_sync;
SyncState m_state = SyncState::Idle; ///< Current sync state
unsigned m_estimatedHashes = 0; ///< Number of estimated hashes for the last peer over PV60. Used for status reporting only.
h256Hash m_knownNewHashes; ///< New hashes we know about use for logging only
private:
static char const* const s_stateNames[static_cast(SyncState::Size)];
bool invariants() const override = 0;
void logNewBlock(h256 const& _h);
EthereumHost& m_host;
};
/**
* @brief Syncrhonization over PV60. Selects a single peer and tries to downloading hashes from it. After hash download is complete
* syncs to peers and keeps up to date
*/
/**
* Transitions:
*
* Idle->Hashes
* Triggered when:
* * A new peer appears that we can sync to
* * Transtition to Idle, there are peers we can sync to
* Effects:
* * Set chain sync (m_syncingTotalDifficulty, m_syncingLatestHash, m_syncer)
* * Requests hashes from m_syncer
*
* Hashes->Idle
* Triggered when:
* * Received too many hashes
* * Received 0 total hashes from m_syncer
* * m_syncer aborts
* Effects:
* In case of too many hashes sync is reset
*
* Hashes->Blocks
* Triggered when:
* * Received known hash from m_syncer
* * Received 0 hashes from m_syncer and m_syncingTotalBlocks not empty
* Effects:
* * Set up download manager, clear m_syncingTotalBlocks. Set all peers to help with downloading if they can
*
* Blocks->Idle
* Triggered when:
* * m_syncer aborts
* * m_syncer does not have required block
* * All blocks downloaded
* * Block qeueue is full with unknown blocks
* Effects:
* * Download manager is reset
*
* Blocks->Waiting
* Triggered when:
* * Block queue is full with known blocks
* Effects:
* * Stop requesting blocks from peers
*
* Waiting->Blocks
* Triggered when:
* * Block queue has space for new blocks
* Effects:
* * Continue requesting blocks from peers
*
* Idle->NewBlocks
* Triggered when:
* * New block hashes arrive
* Effects:
* * Set up download manager, clear m_syncingTotalBlocks. Download blocks from a single peer. If downloaded blocks have unknown parents, set the peer to sync
*
* NewBlocks->Idle
* Triggered when:
* * m_syncer aborts
* * m_syncer does not have required block
* * All new blocks downloaded
* * Block qeueue is full with unknown blocks
* Effects:
* * Download manager is reset
*
*/
class PV60Sync: public BlockChainSync
{
public:
PV60Sync(EthereumHost& _host);
/// @returns true is Sync is in progress
bool isSyncing() const override { return !!m_syncer.lock(); }
/// Called by peer once it has new hashes
void onPeerNewHashes(std::shared_ptr _peer, h256s const& _hashes) override;
/// Called by peer once it has another sequential block of hashes during sync
void onPeerHashes(std::shared_ptr _peer, h256s const& _hashes) override;
/// Called by peer when it is disconnecting
void onPeerAborting() override;
/// @returns Sync status
SyncStatus status() const override;
protected:
void onNewPeer(std::shared_ptr _peer) override;
void continueSync() override;
void peerDoneBlocks(std::shared_ptr _peer) override;
void restartSync() override;
void completeSync() override;
void pauseSync() override;
void resetSyncFor(std::shared_ptr _peer, h256 const& _latestHash, u256 const& _td) override;
protected:
/// Transition sync state in a particular direction. @param _peer Peer that is responsible for state tranfer
void transition(std::shared_ptr _peer, SyncState _s, bool _force = false, bool _needHelp = true);
/// Reset peer syncing requirements state.
void resetNeedsSyncing(std::shared_ptr _peer) { setNeedsSyncing(_peer, h256(), 0); }
/// Update peer syncing requirements state.
void setNeedsSyncing(std::shared_ptr _peer, h256 const& _latestHash, u256 const& _td);
/// Do we presently need syncing with this peer?
bool needsSyncing(std::shared_ptr _peer) const;
/// Check whether the session should bother grabbing blocks from a peer.
bool shouldGrabBlocks(std::shared_ptr _peer) const;
/// Attempt to begin syncing with the peer; first check the peer has a more difficlult chain to download, then start asking for hashes, then move to blocks
void attemptSync(std::shared_ptr _peer);
/// Update our syncing state
void setState(std::shared_ptr _peer, SyncState _s, bool _isSyncing = false, bool _needHelp = false);
/// Check if peer is main syncer
bool isSyncing(std::shared_ptr _peer) const;
/// Check if we need (re-)syncing with the peer.
void noteNeedsSyncing(std::shared_ptr _who);
/// Set main syncing peer
void changeSyncer(std::shared_ptr _syncer, bool _needHelp);
/// Called when peer done downloading blocks
void noteDoneBlocks(std::shared_ptr _who, bool _clemency);
/// Start chainhash sync
virtual void syncHashes(std::shared_ptr _peer);
/// Request subchain, no-op for pv60
virtual void requestSubchain(std::shared_ptr /*_peer*/) {}
/// Abort syncing
void abortSync();
/// Reset hash chain syncing
void resetSync();
bool invariants() const override;
h256s m_syncingNeededBlocks; ///< The blocks that we should download from this peer.
h256 m_syncingLastReceivedHash; ///< Hash most recently received from peer.
h256 m_syncingLatestHash; ///< Latest block's hash of the peer we are syncing to, as of the current sync.
u256 m_syncingTotalDifficulty; ///< Latest block's total difficulty of the peer we aresyncing to, as of the current sync.
std::weak_ptr m_syncer; ///< Peer we are currently syncing with
};
/**
* @brief Syncrhonization over PV61. Selects a single peer and requests every c_hashSubchainSize hash, splitting the hashchain into subchains and downloading each subchain in parallel.
* Syncs to peers and keeps up to date
*/
class PV61Sync: public PV60Sync
{
public:
PV61Sync(EthereumHost& _host);
protected:
void restartSync() override;
void requestSubchain(std::shared_ptr _peer) override;
void syncHashes(std::shared_ptr _peer) override;
void onPeerHashes(std::shared_ptr _peer, h256s const& _hashes) override;
void onPeerAborting() override;
SyncStatus status() const override;
bool invariants() const override;
private:
/// Called when subchain is complete. Check if if hashchain is fully downloaded and proceed to downloading blocks
void completeSubchain(std::shared_ptr _peer, unsigned _n);
/// Find a subchain for peers to downloading
void requestSubchains();
/// Check if downloading hashes in parallel
bool isPV61Syncing() const;
struct SubChain
{
h256s hashes; ///< List of subchain hashes
h256 lastHash; ///< Last requested subchain hash
};
std::map m_completeChainMap; ///< Fully downloaded subchains
std::map m_readyChainMap; ///< Subchains ready for download
std::map m_downloadingChainMap; ///< Subchains currently being downloading. In sync with m_chainSyncPeers
std::map, unsigned, std::owner_less>> m_chainSyncPeers; ///< Peers to m_downloadingSubchain number map
h256Hash m_knownHashes; ///< Subchain start markers. Used to track suchain completion
unsigned m_syncingBlockNumber = 0; ///< Current subchain marker
bool m_hashScanComplete = false; ///< True if leading peer completed hashchain scan and we have a list of subchains ready
};
std::ostream& operator<<(std::ostream& _out, SyncStatus const& _sync);
}
}