/* 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); } }