/*
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 BlockChainSync.h
* @ author Gav Wood < i @ gavwood . com >
* @ date 2014
*/
# pragma once
# include <mutex>
# include <libdevcore/Guards.h>
# include <libethcore/Common.h>
# include <libp2p/Common.h>
# 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 < EthereumPeer > _peer ) ;
/// Called by peer once it has new blocks during syn
virtual void onPeerBlocks ( std : : shared_ptr < EthereumPeer > _peer , RLP const & _r ) ;
/// Called by peer once it has new blocks
virtual void onPeerNewBlock ( std : : shared_ptr < EthereumPeer > _peer , RLP const & _r ) ;
/// Called by peer once it has new hashes
virtual void onPeerNewHashes ( std : : shared_ptr < EthereumPeer > _peer , h256s const & _hashes ) = 0 ;
/// Called by peer once it has another sequential block of hashes during sync
virtual void onPeerHashes ( std : : shared_ptr < EthereumPeer > _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 < int > ( _s ) ] ; }
protected :
//To be implemented in derived classes:
/// New valid peer appears
virtual void onNewPeer ( std : : shared_ptr < EthereumPeer > _peer ) = 0 ;
/// Peer done downloading blocks
virtual void peerDoneBlocks ( std : : shared_ptr < EthereumPeer > _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 < EthereumPeer > _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 < EthereumPeer > _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 < int > ( 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 < EthereumPeer > _peer , h256s const & _hashes ) override ;
/// Called by peer once it has another sequential block of hashes during sync
void onPeerHashes ( std : : shared_ptr < EthereumPeer > _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 < EthereumPeer > _peer ) override ;
void continueSync ( ) override ;
void peerDoneBlocks ( std : : shared_ptr < EthereumPeer > _peer ) override ;
void restartSync ( ) override ;
void completeSync ( ) override ;
void pauseSync ( ) override ;
void resetSyncFor ( std : : shared_ptr < EthereumPeer > _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 < EthereumPeer > _peer , SyncState _s , bool _force = false , bool _needHelp = true ) ;
/// Reset peer syncing requirements state.
void resetNeedsSyncing ( std : : shared_ptr < EthereumPeer > _peer ) { setNeedsSyncing ( _peer , h256 ( ) , 0 ) ; }
/// Update peer syncing requirements state.
void setNeedsSyncing ( std : : shared_ptr < EthereumPeer > _peer , h256 const & _latestHash , u256 const & _td ) ;
/// Do we presently need syncing with this peer?
bool needsSyncing ( std : : shared_ptr < EthereumPeer > _peer ) const ;
/// Check whether the session should bother grabbing blocks from a peer.
bool shouldGrabBlocks ( std : : shared_ptr < EthereumPeer > _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 < EthereumPeer > _peer ) ;
/// Update our syncing state
void setState ( std : : shared_ptr < EthereumPeer > _peer , SyncState _s , bool _isSyncing = false , bool _needHelp = false ) ;
/// Check if peer is main syncer
bool isSyncing ( std : : shared_ptr < EthereumPeer > _peer ) const ;
/// Check if we need (re-)syncing with the peer.
void noteNeedsSyncing ( std : : shared_ptr < EthereumPeer > _who ) ;
/// Set main syncing peer
void changeSyncer ( std : : shared_ptr < EthereumPeer > _syncer , bool _needHelp ) ;
/// Called when peer done downloading blocks
void noteDoneBlocks ( std : : shared_ptr < EthereumPeer > _who , bool _clemency ) ;
/// Start chainhash sync
virtual void syncHashes ( std : : shared_ptr < EthereumPeer > _peer ) ;
/// Request subchain, no-op for pv60
virtual void requestSubchain ( std : : shared_ptr < EthereumPeer > /*_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 < EthereumPeer > 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 completeSync ( ) override ;
void requestSubchain ( std : : shared_ptr < EthereumPeer > _peer ) override ;
void syncHashes ( std : : shared_ptr < EthereumPeer > _peer ) override ;
void onPeerHashes ( std : : shared_ptr < EthereumPeer > _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 < EthereumPeer > _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 < unsigned , SubChain > m_completeChainMap ; ///< Fully downloaded subchains
std : : map < unsigned , SubChain > m_readyChainMap ; ///< Subchains ready for download
std : : map < unsigned , SubChain > m_downloadingChainMap ; ///< Subchains currently being downloading. In sync with m_chainSyncPeers
std : : map < std : : weak_ptr < EthereumPeer > , unsigned , std : : owner_less < std : : weak_ptr < EthereumPeer > > > 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 ) ;
}
}