From a17cad3404ad44bf62f173b9001dcd298193e807 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 27 Aug 2014 09:37:00 +0200 Subject: [PATCH 1/3] A start. --- libethereum/PeerServer.h | 106 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 5d12c807c..c38d49df4 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -40,12 +40,116 @@ namespace eth class RLPStream; class TransactionQueue; class BlockQueue; +/* +class BasePeerServer +{ + friend class BasePeerSession; + +public: + /// Start server, listening for connections on the given port. + BasePeerServer(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, listening for connections on a system-assigned port. + BasePeerServer(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, but don't listen. + BasePeerServer(std::string const& _clientVersion, u256 _networkId); + + /// Will block on network process events. + virtual ~BasePeerServer(); + + /// Closes all peers. + void disconnectPeers(); + + virtual unsigned protocolVersion(); + + /// Connect to a peer explicitly. + void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; + void connect(bi::tcp::endpoint const& _ep); + + /// Conduct I/O, polling, syncing, whatever. + /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. + /// This won't touch alter the blockchain. + void process() { if (isInitialised()) m_ioService.poll(); } + + /// @returns true iff we have the a peer of the given id. + bool havePeer(Public _id) const; + + /// Set ideal number of peers. + void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } + + /// Get peer information. + std::vector peers(bool _updatePing = false) const; + + /// Get number of peers connected; equivalent to, but faster than, peers().size(). + size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } + + /// Ping the peers, to update the latency information. + void pingAll(); + /// Get the port we're listening on currently. + unsigned short listenPort() const { return m_public.port(); } + + /// Serialise the set of known peers. + bytes savePeers() const; + + /// Deserialise the data and populate the set of known peers. + void restorePeers(bytesConstRef _b); + + void registerPeer(std::shared_ptr _s); + +protected: + /// Called when the session has provided us with a new peer we can connect to. + void noteNewPeers() {} + + void seal(bytes& _b); + void populateAddresses(); + void determinePublic(std::string const& _publicAddress, bool _upnp); + void ensureAccepting(); + + void growPeers(); + void prunePeers(); + + /// Check to see if the network peer-state initialisation has happened. + bool isInitialised() const { return m_latestBlockSent; } + /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. + bool ensureInitialised(TransactionQueue& _tq); + + std::map potentialPeers(); + + std::string m_clientVersion; + + unsigned short m_listenPort; + + ba::io_service m_ioService; + bi::tcp::acceptor m_acceptor; + bi::tcp::socket m_socket; + + UPnP* m_upnp = nullptr; + bi::tcp::endpoint m_public; + KeyPair m_key; + + u256 m_networkId; + + mutable std::mutex x_peers; + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + + mutable std::recursive_mutex m_incomingLock; + std::map> m_incomingPeers; + std::vector m_freePeers; + + std::chrono::steady_clock::time_point m_lastPeersRequest; + unsigned m_idealPeerCount = 5; + + std::vector m_addresses; + std::vector m_peerAddresses; + + bool m_accepting = false; +}; +*/ /** * @brief The PeerServer class * @warning None of this is thread-safe. You have been warned. */ -class PeerServer +class PeerServer//: public BasePeerServer { friend class PeerSession; From be1bf6b9bf0e8bfcc0b6390ca46b5d038673eff3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 27 Aug 2014 12:48:30 +0200 Subject: [PATCH 2/3] Split off general network layer. --- CMakeLists.txt | 3 +- alethzero/MainWin.cpp | 2 +- iethxi/MainWin.cpp | 2 +- {libethereum => libethential}/Guards.cpp | 0 {libethereum => libethential}/Guards.h | 4 + libethereum/All.h | 6 +- libethereum/BlockChain.h | 2 +- libethereum/BlockQueue.h | 2 +- libethereum/Client.cpp | 6 +- libethereum/Client.h | 6 +- .../{PeerNetwork.cpp => CommonNet.cpp} | 2 +- libethereum/{PeerNetwork.h => CommonNet.h} | 4 +- .../{PeerServer.cpp => EthereumHost.cpp} | 80 +-- libethereum/{PeerServer.h => EthereumHost.h} | 132 +---- .../{PeerSession.cpp => EthereumSession.cpp} | 50 +- .../{PeerSession.h => EthereumSession.h} | 16 +- libethereum/TransactionQueue.h | 2 +- libethereumx/Ethereum.h | 4 +- libethnet/CMakeLists.txt | 66 +++ libethnet/Common.cpp | 61 +++ libethnet/Common.h | 91 ++++ libethnet/PeerHost.cpp | 480 ++++++++++++++++++ libethnet/PeerHost.h | 145 ++++++ libethnet/PeerSession.cpp | 418 +++++++++++++++ libethnet/PeerSession.h | 105 ++++ libethnet/UPnP.cpp | 183 +++++++ libethnet/UPnP.h | 53 ++ libqethereum/QEthereum.cpp | 2 +- libqethereum/QmlEthereum.cpp | 2 +- test/fork.cpp | 2 +- test/network.cpp | 2 +- test/peer.cpp | 4 +- test/txTest.cpp | 2 +- third/MainWin.cpp | 2 +- walleth/MainWin.cpp | 2 +- 35 files changed, 1723 insertions(+), 220 deletions(-) rename {libethereum => libethential}/Guards.cpp (100%) rename {libethereum => libethential}/Guards.h (91%) rename libethereum/{PeerNetwork.cpp => CommonNet.cpp} (98%) rename libethereum/{PeerNetwork.h => CommonNet.h} (98%) rename libethereum/{PeerServer.cpp => EthereumHost.cpp} (86%) rename libethereum/{PeerServer.h => EthereumHost.h} (52%) rename libethereum/{PeerSession.cpp => EthereumSession.cpp} (92%) rename libethereum/{PeerSession.h => EthereumSession.h} (88%) create mode 100644 libethnet/CMakeLists.txt create mode 100644 libethnet/Common.cpp create mode 100644 libethnet/Common.h create mode 100644 libethnet/PeerHost.cpp create mode 100644 libethnet/PeerHost.h create mode 100644 libethnet/PeerSession.cpp create mode 100644 libethnet/PeerSession.h create mode 100644 libethnet/UPnP.cpp create mode 100644 libethnet/UPnP.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8d3dc391..36ab9dec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ add_subdirectory(lllc) add_subdirectory(sc) if (NOT LANGUAGES) add_subdirectory(secp256k1) + add_subdirectory(libethnet) add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libwhisper) @@ -363,7 +364,7 @@ if (NOT LANGUAGES) add_subdirectory(alethzero) add_subdirectory(third) if(QTQML) - add_subdirectory(iethxi) + #add_subdirectory(iethxi) add_subdirectory(walleth) endif() endif() diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 4c18364ba..64beb7e83 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include "MiningView.h" #include "BuildInfo.h" #include "MainWin.h" diff --git a/iethxi/MainWin.cpp b/iethxi/MainWin.cpp index 27814ebcd..276ff7630 100644 --- a/iethxi/MainWin.cpp +++ b/iethxi/MainWin.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" diff --git a/libethereum/Guards.cpp b/libethential/Guards.cpp similarity index 100% rename from libethereum/Guards.cpp rename to libethential/Guards.cpp diff --git a/libethereum/Guards.h b/libethential/Guards.h similarity index 91% rename from libethereum/Guards.h rename to libethential/Guards.h index 64a95eb78..b509b45ed 100644 --- a/libethereum/Guards.h +++ b/libethential/Guards.h @@ -27,6 +27,10 @@ namespace eth { +using Mutex = std::mutex; +using RecursiveMutex = std::recursive_mutex; +using SharedMutex = boost::shared_mutex; + using Guard = std::lock_guard; using RecursiveGuard = std::lock_guard; using ReadGuard = boost::shared_lock; diff --git a/libethereum/All.h b/libethereum/All.h index 96f23aa74..17151f1b0 100644 --- a/libethereum/All.h +++ b/libethereum/All.h @@ -6,9 +6,9 @@ #include "Defaults.h" #include "Executive.h" #include "ExtVM.h" -#include "PeerNetwork.h" -#include "PeerServer.h" -#include "PeerSession.h" +#include "CommonNet.h" +#include "EthereumHost.h" +#include "EthereumSession.h" #include "State.h" #include "Transaction.h" #include "TransactionQueue.h" diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 8fcf09691..cbae012ba 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -25,7 +25,7 @@ #include #include #include -#include "Guards.h" +#include #include "BlockDetails.h" #include "AddressState.h" #include "BlockQueue.h" diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index d66bfabd9..30686d192 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -24,7 +24,7 @@ #include #include #include "libethcore/CommonEth.h" -#include "Guards.h" +#include namespace eth { diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 6249c370b..c62fe42de 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -26,7 +26,7 @@ #include #include #include "Defaults.h" -#include "PeerServer.h" +#include "EthereumHost.h" using namespace std; using namespace eth; @@ -213,13 +213,13 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { - m_net.reset(new PeerServer(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); + m_net.reset(new EthereumHost(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); } catch (std::exception const&) { // Probably already have the port open. cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + m_net.reset(new EthereumHost(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); } } m_net->setIdealPeerCount(_peers); diff --git a/libethereum/Client.h b/libethereum/Client.h index b62a9066f..9499b0d4c 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -30,11 +30,11 @@ #include #include #include -#include "Guards.h" +#include #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" -#include "PeerNetwork.h" +#include "CommonNet.h" #include "PastMessage.h" #include "MessageFilter.h" #include "Miner.h" @@ -298,7 +298,7 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; diff --git a/libethereum/PeerNetwork.cpp b/libethereum/CommonNet.cpp similarity index 98% rename from libethereum/PeerNetwork.cpp rename to libethereum/CommonNet.cpp index 6585769e6..84443eb11 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/CommonNet.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "PeerNetwork.h" +#include "CommonNet.h" using namespace std; using namespace eth; diff --git a/libethereum/PeerNetwork.h b/libethereum/CommonNet.h similarity index 98% rename from libethereum/PeerNetwork.h rename to libethereum/CommonNet.h index f73f44e54..b76893d5c 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/CommonNet.h @@ -45,8 +45,8 @@ static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask class OverlayDB; class BlockChain; class TransactionQueue; -class PeerServer; -class PeerSession; +class EthereumHost; +class EthereumSession; struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; diff --git a/libethereum/PeerServer.cpp b/libethereum/EthereumHost.cpp similarity index 86% rename from libethereum/PeerServer.cpp rename to libethereum/EthereumHost.cpp index 71a80a90b..9ed782800 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/EthereumHost.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerNetwork.cpp +/** @file EthereumHost.cpp * @authors: * Gav Wood * Eric Lombrozo * @date 2014 */ -#include "PeerServer.h" +#include "EthereumHost.h" #include #ifdef _WIN32 @@ -40,7 +40,7 @@ #include "BlockChain.h" #include "TransactionQueue.h" #include "BlockQueue.h" -#include "PeerSession.h" +#include "EthereumSession.h" using namespace std; using namespace eth; @@ -55,7 +55,7 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(_port), @@ -71,7 +71,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (_m == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), @@ -90,7 +90,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), @@ -105,22 +105,22 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::~PeerServer() +EthereumHost::~EthereumHost() { disconnectPeers(); for (auto i: m_peers) - if (shared_ptr p = i.second.lock()) + if (shared_ptr p = i.second.lock()) p->giveUpOnFetch(); } -void PeerServer::registerPeer(std::shared_ptr _s) +void EthereumHost::registerPeer(std::shared_ptr _s) { Guard l(x_peers); m_peers[_s->m_id] = _s; } -void PeerServer::disconnectPeers() +void EthereumHost::disconnectPeers() { for (unsigned n = 0;; n = 0) { @@ -142,12 +142,12 @@ void PeerServer::disconnectPeers() delete m_upnp; } -unsigned PeerServer::protocolVersion() +unsigned EthereumHost::protocolVersion() { return c_protocolVersion; } -void PeerServer::seal(bytes& _b) +void EthereumHost::seal(bytes& _b) { _b[0] = 0x22; _b[1] = 0x40; @@ -160,7 +160,7 @@ void PeerServer::seal(bytes& _b) _b[7] = len & 0xff; } -void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) +void EthereumHost::determinePublic(string const& _publicAddress, bool _upnp) { if (_upnp) try @@ -202,7 +202,7 @@ void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) } } -void PeerServer::populateAddresses() +void EthereumHost::populateAddresses() { #ifdef _WIN32 WSAData wsaData; @@ -277,7 +277,7 @@ void PeerServer::populateAddresses() #endif } -std::map PeerServer::potentialPeers() +std::map EthereumHost::potentialPeers() { std::map ret; if (!m_public.address().is_unspecified()) @@ -295,7 +295,7 @@ std::map PeerServer::potentialPeers() return ret; } -void PeerServer::ensureAccepting() +void EthereumHost::ensureAccepting() { if (m_accepting == false) { @@ -311,7 +311,7 @@ void PeerServer::ensureAccepting() } catch (...){} bi::address remoteAddress = m_socket.remote_endpoint().address(); // Port defaults to 0 - we let the hello tell us which port the peer listens to - auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); + auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); p->start(); } catch (std::exception const& _e) @@ -325,7 +325,7 @@ void PeerServer::ensureAccepting() } } -void PeerServer::connect(std::string const& _addr, unsigned short _port) noexcept +void EthereumHost::connect(std::string const& _addr, unsigned short _port) noexcept { try { @@ -338,7 +338,7 @@ void PeerServer::connect(std::string const& _addr, unsigned short _port) noexcep } } -void PeerServer::connect(bi::tcp::endpoint const& _ep) +void EthereumHost::connect(bi::tcp::endpoint const& _ep) { clog(NetConnect) << "Attempting connection to " << _ep; bi::tcp::socket* s = new bi::tcp::socket(m_ioService); @@ -359,7 +359,7 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) } else { - auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); + auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); clog(NetConnect) << "Connected to " << _ep; p->start(); } @@ -367,7 +367,7 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) }); } -h256Set PeerServer::neededBlocks() +h256Set EthereumHost::neededBlocks() { Guard l(x_blocksNeeded); h256Set ret; @@ -386,7 +386,7 @@ h256Set PeerServer::neededBlocks() return ret; } -bool PeerServer::havePeer(Public _id) const +bool EthereumHost::havePeer(Public _id) const { Guard l(x_peers); @@ -400,7 +400,7 @@ bool PeerServer::havePeer(Public _id) const return !!m_peers.count(_id); } -bool PeerServer::ensureInitialised(TransactionQueue& _tq) +bool EthereumHost::ensureInitialised(TransactionQueue& _tq) { if (m_latestBlockSent == h256()) { @@ -416,7 +416,7 @@ bool PeerServer::ensureInitialised(TransactionQueue& _tq) return false; } -bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) +bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) { Guard l(x_blocksNeeded); m_blocksOnWay.erase(_hash); @@ -429,7 +429,7 @@ bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) return false; } -bool PeerServer::sync(TransactionQueue& _tq, BlockQueue& _bq) +bool EthereumHost::sync(TransactionQueue& _tq, BlockQueue& _bq) { bool netChange = ensureInitialised(_tq); @@ -453,7 +453,7 @@ bool PeerServer::sync(TransactionQueue& _tq, BlockQueue& _bq) return netChange; } -void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) +void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) { bool resendAll = (_currentHash != m_latestBlockSent); @@ -481,7 +481,7 @@ void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) if (n) { RLPStream ts; - PeerSession::prep(ts); + EthereumSession::prep(ts); ts.appendList(n + 1) << TransactionsPacket; ts.appendRaw(b, n).swapOut(b); seal(b); @@ -492,7 +492,7 @@ void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) } } -void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) +void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) { // Import new blocks { @@ -508,7 +508,7 @@ void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) if (_currentHash != m_latestBlockSent) { RLPStream ts; - PeerSession::prep(ts); + EthereumSession::prep(ts); bytes bs; unsigned c = 0; for (auto h: m_chain->treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) @@ -533,7 +533,7 @@ void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) m_latestBlockSent = _currentHash; } -void PeerServer::growPeers() +void EthereumHost::growPeers() { Guard l(x_peers); while (m_peers.size() < m_idealPeerCount) @@ -544,7 +544,7 @@ void PeerServer::growPeers() { RLPStream s; bytes b; - (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + (EthereumSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); seal(b); for (auto const& i: m_peers) if (auto p = i.second.lock()) @@ -567,7 +567,7 @@ void PeerServer::growPeers() } } -void PeerServer::noteHaveChain(std::shared_ptr const& _from) +void EthereumHost::noteHaveChain(std::shared_ptr const& _from) { auto td = _from->m_totalDifficulty; @@ -583,13 +583,13 @@ void PeerServer::noteHaveChain(std::shared_ptr const& _from) { Guard l(x_peers); for (auto const& i: m_peers) - if (shared_ptr p = i.second.lock()) + if (shared_ptr p = i.second.lock()) p->ensureGettingChain(); } } -void PeerServer::prunePeers() +void EthereumHost::prunePeers() { Guard l(x_peers); // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. @@ -598,7 +598,7 @@ void PeerServer::prunePeers() { // look for worst peer to kick off // first work out how many are old enough to kick off. - shared_ptr worst; + shared_ptr worst; unsigned agedPeers = 0; for (auto i: m_peers) if (auto p = i.second.lock()) @@ -621,11 +621,11 @@ void PeerServer::prunePeers() i = m_peers.erase(i); } -std::vector PeerServer::peers(bool _updatePing) const +std::vector EthereumHost::peers(bool _updatePing) const { Guard l(x_peers); if (_updatePing) - const_cast(this)->pingAll(); + const_cast(this)->pingAll(); this_thread::sleep_for(chrono::milliseconds(200)); std::vector ret; for (auto& i: m_peers) @@ -635,7 +635,7 @@ std::vector PeerServer::peers(bool _updatePing) const return ret; } -void PeerServer::pingAll() +void EthereumHost::pingAll() { Guard l(x_peers); for (auto& i: m_peers) @@ -643,7 +643,7 @@ void PeerServer::pingAll() j->ping(); } -bytes PeerServer::savePeers() const +bytes EthereumHost::savePeers() const { Guard l(x_peers); RLPStream ret; @@ -658,7 +658,7 @@ bytes PeerServer::savePeers() const return RLPStream(n).appendRaw(ret.out(), n).out(); } -void PeerServer::restorePeers(bytesConstRef _b) +void EthereumHost::restorePeers(bytesConstRef _b) { for (auto i: RLP(_b)) { diff --git a/libethereum/PeerServer.h b/libethereum/EthereumHost.h similarity index 52% rename from libethereum/PeerServer.h rename to libethereum/EthereumHost.h index c38d49df4..bbe0d8b86 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/EthereumHost.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerServer.h +/** @file EthereumHost.h * @author Gav Wood * @date 2014 */ @@ -28,9 +28,9 @@ #include #include #include +#include #include -#include "PeerNetwork.h" -#include "Guards.h" +#include "CommonNet.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; @@ -40,129 +40,25 @@ namespace eth class RLPStream; class TransactionQueue; class BlockQueue; -/* -class BasePeerServer -{ - friend class BasePeerSession; - -public: - /// Start server, listening for connections on the given port. - BasePeerServer(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); - /// Start server, listening for connections on a system-assigned port. - BasePeerServer(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); - /// Start server, but don't listen. - BasePeerServer(std::string const& _clientVersion, u256 _networkId); - - /// Will block on network process events. - virtual ~BasePeerServer(); - - /// Closes all peers. - void disconnectPeers(); - - virtual unsigned protocolVersion(); - - /// Connect to a peer explicitly. - void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; - void connect(bi::tcp::endpoint const& _ep); - - /// Conduct I/O, polling, syncing, whatever. - /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. - /// This won't touch alter the blockchain. - void process() { if (isInitialised()) m_ioService.poll(); } - - /// @returns true iff we have the a peer of the given id. - bool havePeer(Public _id) const; - - /// Set ideal number of peers. - void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } - - /// Get peer information. - std::vector peers(bool _updatePing = false) const; - - /// Get number of peers connected; equivalent to, but faster than, peers().size(). - size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } - - /// Ping the peers, to update the latency information. - void pingAll(); - - /// Get the port we're listening on currently. - unsigned short listenPort() const { return m_public.port(); } - - /// Serialise the set of known peers. - bytes savePeers() const; - - /// Deserialise the data and populate the set of known peers. - void restorePeers(bytesConstRef _b); - - void registerPeer(std::shared_ptr _s); -protected: - /// Called when the session has provided us with a new peer we can connect to. - void noteNewPeers() {} - - void seal(bytes& _b); - void populateAddresses(); - void determinePublic(std::string const& _publicAddress, bool _upnp); - void ensureAccepting(); - - void growPeers(); - void prunePeers(); - - /// Check to see if the network peer-state initialisation has happened. - bool isInitialised() const { return m_latestBlockSent; } - /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. - bool ensureInitialised(TransactionQueue& _tq); - - std::map potentialPeers(); - - std::string m_clientVersion; - - unsigned short m_listenPort; - - ba::io_service m_ioService; - bi::tcp::acceptor m_acceptor; - bi::tcp::socket m_socket; - - UPnP* m_upnp = nullptr; - bi::tcp::endpoint m_public; - KeyPair m_key; - - u256 m_networkId; - - mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. - - mutable std::recursive_mutex m_incomingLock; - std::map> m_incomingPeers; - std::vector m_freePeers; - - std::chrono::steady_clock::time_point m_lastPeersRequest; - unsigned m_idealPeerCount = 5; - - std::vector m_addresses; - std::vector m_peerAddresses; - - bool m_accepting = false; -}; -*/ /** - * @brief The PeerServer class + * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. */ -class PeerServer//: public BasePeerServer +class EthereumHost { - friend class PeerSession; + friend class EthereumSession; public: /// Start server, listening for connections on the given port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, listening for connections on a system-assigned port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, but don't listen. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); /// Will block on network process events. - ~PeerServer(); + ~EthereumHost(); /// Closes all peers. void disconnectPeers(); @@ -209,14 +105,14 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); - void registerPeer(std::shared_ptr _s); + void registerPeer(std::shared_ptr _s); private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. bool noteBlock(h256 _hash, bytesConstRef _data); /// Session has finished getting the chain of hashes. - void noteHaveChain(std::shared_ptr const& _who); + void noteHaveChain(std::shared_ptr const& _who); /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} @@ -236,7 +132,7 @@ private: h256Set neededBlocks(); /// Check to see if the network peer-state initialisation has happened. - bool isInitialised() const { return m_latestBlockSent; } + virtual bool isInitialised() const { return m_latestBlockSent; } /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. bool ensureInitialised(TransactionQueue& _tq); @@ -259,7 +155,7 @@ private: u256 m_networkId; mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; diff --git a/libethereum/PeerSession.cpp b/libethereum/EthereumSession.cpp similarity index 92% rename from libethereum/PeerSession.cpp rename to libethereum/EthereumSession.cpp index b095745f7..835509b63 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -14,24 +14,24 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.cpp +/** @file EthereumSession.cpp * @author Gav Wood * @date 2014 */ -#include "PeerSession.h" +#include "EthereumSession.h" #include #include #include #include "BlockChain.h" -#include "PeerServer.h" +#include "EthereumHost.h" using namespace std; using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): +EthereumSession::EthereumSession(EthereumHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), m_reqNetworkId(_rNId), @@ -43,7 +43,7 @@ PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); } -PeerSession::~PeerSession() +EthereumSession::~EthereumSession() { giveUpOnFetch(); @@ -56,7 +56,7 @@ PeerSession::~PeerSession() catch (...){} } -void PeerSession::giveUpOnFetch() +void EthereumSession::giveUpOnFetch() { if (m_askedBlocks.size()) { @@ -71,7 +71,7 @@ void PeerSession::giveUpOnFetch() } } -bi::tcp::endpoint PeerSession::endpoint() const +bi::tcp::endpoint EthereumSession::endpoint() const { if (m_socket.is_open()) try @@ -83,7 +83,7 @@ bi::tcp::endpoint PeerSession::endpoint() const return bi::tcp::endpoint(); } -bool PeerSession::interpret(RLP const& _r) +bool EthereumSession::interpret(RLP const& _r) { clogS(NetRight) << _r; switch (_r[0].toInt()) @@ -109,7 +109,7 @@ bool PeerSession::interpret(RLP const& _r) return false; } - if (m_protocolVersion != PeerServer::protocolVersion() || m_networkId != m_server->networkId() || !m_id) + if (m_protocolVersion != EthereumHost::protocolVersion() || m_networkId != m_server->networkId() || !m_id) { disconnect(IncompatibleProtocol); return false; @@ -343,7 +343,7 @@ bool PeerSession::interpret(RLP const& _r) return true; } -void PeerSession::ensureGettingChain() +void EthereumSession::ensureGettingChain() { if (!m_askedBlocks.size()) m_askedBlocks = m_server->neededBlocks(); @@ -361,25 +361,25 @@ void PeerSession::ensureGettingChain() clogS(NetMessageSummary) << "No blocks left to get."; } -void PeerSession::ping() +void EthereumSession::ping() { RLPStream s; sealAndSend(prep(s).appendList(1) << PingPacket); m_ping = std::chrono::steady_clock::now(); } -void PeerSession::getPeers() +void EthereumSession::getPeers() { RLPStream s; sealAndSend(prep(s).appendList(1) << GetPeersPacket); } -RLPStream& PeerSession::prep(RLPStream& _s) +RLPStream& EthereumSession::prep(RLPStream& _s) { return _s.appendRaw(bytes(8, 0)); } -void PeerSession::sealAndSend(RLPStream& _s) +void EthereumSession::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); @@ -387,7 +387,7 @@ void PeerSession::sealAndSend(RLPStream& _s) sendDestroy(b); } -bool PeerSession::checkPacket(bytesConstRef _msg) +bool EthereumSession::checkPacket(bytesConstRef _msg) { if (_msg.size() < 8) return false; @@ -402,7 +402,7 @@ bool PeerSession::checkPacket(bytesConstRef _msg) return true; } -void PeerSession::sendDestroy(bytes& _msg) +void EthereumSession::sendDestroy(bytes& _msg) { clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); @@ -415,7 +415,7 @@ void PeerSession::sendDestroy(bytes& _msg) writeImpl(buffer); } -void PeerSession::send(bytesConstRef _msg) +void EthereumSession::send(bytesConstRef _msg) { clogS(NetLeft) << RLP(_msg.cropped(8)); @@ -428,7 +428,7 @@ void PeerSession::send(bytesConstRef _msg) writeImpl(buffer); } -void PeerSession::writeImpl(bytes& _buffer) +void EthereumSession::writeImpl(bytes& _buffer) { // cerr << (void*)this << " writeImpl" << endl; if (!m_socket.is_open()) @@ -440,7 +440,7 @@ void PeerSession::writeImpl(bytes& _buffer) write(); } -void PeerSession::write() +void EthereumSession::write() { // cerr << (void*)this << " write" << endl; lock_guard l(m_writeLock); @@ -467,7 +467,7 @@ void PeerSession::write() }); } -void PeerSession::dropped() +void EthereumSession::dropped() { // cerr << (void*)this << " dropped" << endl; if (m_socket.is_open()) @@ -479,7 +479,7 @@ void PeerSession::dropped() catch (...) {} } -void PeerSession::disconnect(int _reason) +void EthereumSession::disconnect(int _reason) { clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; if (m_socket.is_open()) @@ -497,12 +497,12 @@ void PeerSession::disconnect(int _reason) } } -void PeerSession::start() +void EthereumSession::start() { RLPStream s; prep(s); s.appendList(9) << HelloPacket - << (uint)PeerServer::protocolVersion() + << (uint)EthereumHost::protocolVersion() << m_server->networkId() << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) @@ -517,7 +517,7 @@ void PeerSession::start() doRead(); } -void PeerSession::startInitialSync() +void EthereumSession::startInitialSync() { h256 c = m_server->m_chain->currentHash(); uint n = m_server->m_chain->number(); @@ -535,7 +535,7 @@ void PeerSession::startInitialSync() sealAndSend(s); } -void PeerSession::doRead() +void EthereumSession::doRead() { // ignore packets received while waiting to disconnect if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) diff --git a/libethereum/PeerSession.h b/libethereum/EthereumSession.h similarity index 88% rename from libethereum/PeerSession.h rename to libethereum/EthereumSession.h index 4cababbf4..ca44179c6 100644 --- a/libethereum/PeerSession.h +++ b/libethereum/EthereumSession.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.h +/** @file EthereumSession.h * @author Gav Wood * @date 2014 */ @@ -28,22 +28,22 @@ #include #include #include -#include "PeerNetwork.h" +#include "CommonNet.h" namespace eth { /** - * @brief The PeerSession class + * @brief The EthereumSession class * @todo Document fully. */ -class PeerSession: public std::enable_shared_from_this +class EthereumSession: public std::enable_shared_from_this { - friend class PeerServer; + friend class EthereumHost; public: - PeerSession(PeerServer* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); - ~PeerSession(); + EthereumSession(EthereumHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + ~EthereumSession(); void start(); void disconnect(int _reason); @@ -77,7 +77,7 @@ private: void send(bytesConstRef _msg); void writeImpl(bytes& _buffer); void write(); - PeerServer* m_server; + EthereumHost* m_server; std::recursive_mutex m_writeLock; std::deque m_writeQueue; diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 2c6556a71..41cefc56f 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -24,7 +24,7 @@ #include #include #include "libethcore/CommonEth.h" -#include "Guards.h" +#include namespace eth { diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index d55d4eaac..fb272c030 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -28,12 +28,12 @@ #include #include #include +#include #include #include -#include #include #include -#include +#include namespace eth { diff --git a/libethnet/CMakeLists.txt b/libethnet/CMakeLists.txt new file mode 100644 index 000000000..a694ab205 --- /dev/null +++ b/libethnet/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE ethnet) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} evm) +target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} secp256k1) +if(MINIUPNPC_LS) +target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) +endif() +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) +target_link_libraries(${EXECUTABLE} gmp) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libethnet/Common.cpp b/libethnet/Common.cpp new file mode 100644 index 000000000..5958ee9df --- /dev/null +++ b/libethnet/Common.cpp @@ -0,0 +1,61 @@ +/* + 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 Common.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Common.h" +using namespace std; +using namespace eth; + +// Helper function to determine if an address falls within one of the reserved ranges +// For V4: +// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" +// Not implemented yet for V6 +bool eth::isPrivateAddress(bi::address _addressToCheck) +{ + if (_addressToCheck.is_v4()) + { + bi::address_v4 v4Address = _addressToCheck.to_v4(); + bi::address_v4::bytes_type bytesToCheck = v4Address.to_bytes(); + if (bytesToCheck[0] == 10 || bytesToCheck[0] == 127) + return true; + if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <= 31)) + return true; + if (bytesToCheck[0] == 192 && bytesToCheck[1] == 168) + return true; + } + return false; +} + +std::string eth::reasonOf(DisconnectReason _r) +{ + switch (_r) + { + case DisconnectRequested: return "Disconnect was requested."; + case TCPError: return "Low-level TCP communication error."; + case BadProtocol: return "Data format error."; + case UselessPeer: return "Peer had no use for this node."; + case TooManyPeers: return "Peer had too many connections."; + case DuplicatePeer: return "Peer was already connected."; + case IncompatibleProtocol: return "Peer protocol versions are incompatible."; + case ClientQuit: return "Peer is exiting."; + default: return "Unknown reason."; + } +} + diff --git a/libethnet/Common.h b/libethnet/Common.h new file mode 100644 index 000000000..099fe085f --- /dev/null +++ b/libethnet/Common.h @@ -0,0 +1,91 @@ +/* + 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 Common.h + * @author Gav Wood + * @date 2014 + * + * Miscellanea required for the PeerHost/PeerSession classes. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace eth +{ + +bool isPrivateAddress(bi::address _addressToCheck); + +class PeerHost; +class PeerSession; + +struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; +struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; +struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; }; +struct NetConnect: public LogChannel { static const char* name() { return "+N+"; } static const int verbosity = 4; }; +struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; +struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 10; }; +struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 11; }; +struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 13; }; +struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 14; }; +struct NetLeft: public LogChannel { static const char* name() { return ". +*/ +/** @file PeerHost.cpp + * @authors: + * Gav Wood + * Eric Lombrozo + * @date 2014 + */ + +#include "PeerHost.h" + +#include +#ifdef _WIN32 +// winsock is already included +// #include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "PeerSession.h" +using namespace std; +using namespace eth; + +// Addresses we will skip during network interface discovery +// Use a vector as the list is small +// Why this and not names? +// Under MacOSX loopback (127.0.0.1) can be named lo0 and br0 are bridges (0.0.0.0) +static const set c_rejectAddresses = { + {bi::address_v4::from_string("127.0.0.1")}, + {bi::address_v6::from_string("::1")}, + {bi::address_v4::from_string("0.0.0.0")}, + {bi::address_v6::from_string("::")} +}; + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, string const& _publicAddress, bool _upnp): + m_clientVersion(_clientVersion), + m_listenPort(_port), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + populateAddresses(); + determinePublic(_publicAddress, _upnp); + ensureAccepting(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, string const& _publicAddress, bool _upnp): + m_clientVersion(_clientVersion), + m_listenPort(0), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + m_listenPort = m_acceptor.local_endpoint().port(); + + // populate addresses. + populateAddresses(); + determinePublic(_publicAddress, _upnp); + ensureAccepting(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId): + m_clientVersion(_clientVersion), + m_listenPort(0), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + // populate addresses. + populateAddresses(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::~PeerHost() +{ + disconnectPeers(); +} + +void PeerHost::registerPeer(std::shared_ptr _s) +{ + Guard l(x_peers); + m_peers[_s->m_id] = _s; +} + +void PeerHost::disconnectPeers() +{ + for (unsigned n = 0;; n = 0) + { + { + Guard l(x_peers); + for (auto i: m_peers) + if (auto p = i.second.lock()) + { + p->disconnect(ClientQuit); + n++; + } + } + if (!n) + break; + m_ioService.poll(); + this_thread::sleep_for(chrono::milliseconds(100)); + } + + delete m_upnp; +} + +void PeerHost::seal(bytes& _b) +{ + _b[0] = 0x22; + _b[1] = 0x40; + _b[2] = 0x08; + _b[3] = 0x91; + uint32_t len = (uint32_t)_b.size() - 8; + _b[4] = (len >> 24) & 0xff; + _b[5] = (len >> 16) & 0xff; + _b[6] = (len >> 8) & 0xff; + _b[7] = len & 0xff; +} + +void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) +{ + if (_upnp) + try + { + m_upnp = new UPnP; + } + catch (NoUPnPDevice) {} // let m_upnp continue as null - we handle it properly. + + bi::tcp::resolver r(m_ioService); + if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) + { + clog(NetNote) << "External addr: " << m_upnp->externalIP(); + int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); + if (p) + clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; + else + { + // couldn't map + clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; + p = m_listenPort; + } + + auto eip = m_upnp->externalIP(); + if (eip == string("0.0.0.0") && _publicAddress.empty()) + m_public = bi::tcp::endpoint(bi::address(), (unsigned short)p); + else + { + m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), (unsigned short)p); + m_addresses.push_back(m_public.address().to_v4()); + } + } + else + { + // No UPnP - fallback on given public address or, if empty, the assumed peer address. + m_public = bi::tcp::endpoint(_publicAddress.size() ? bi::address::from_string(_publicAddress) + : m_peerAddresses.size() ? m_peerAddresses[0] + : bi::address(), m_listenPort); + m_addresses.push_back(m_public.address().to_v4()); + } +} + +void PeerHost::populateAddresses() +{ +#ifdef _WIN32 + WSAData wsaData; + if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) + throw NoNetworking(); + + char ac[80]; + if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) + { + clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name."; + WSACleanup(); + throw NoNetworking(); + } + + struct hostent* phe = gethostbyname(ac); + if (phe == 0) + { + clog(NetWarn) << "Bad host lookup."; + WSACleanup(); + throw NoNetworking(); + } + + for (int i = 0; phe->h_addr_list[i] != 0; ++i) + { + struct in_addr addr; + memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); + char *addrStr = inet_ntoa(addr); + bi::address ad(bi::address::from_string(addrStr)); + m_addresses.push_back(ad.to_v4()); + bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); + if (!isLocal) + m_peerAddresses.push_back(ad.to_v4()); + clog(NetNote) << "Address: " << ac << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); + } + + WSACleanup(); +#else + ifaddrs* ifaddr; + if (getifaddrs(&ifaddr) == -1) + throw NoNetworking(); + + bi::tcp::resolver r(m_ioService); + + for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) + { + if (!ifa->ifa_addr) + continue; + if (ifa->ifa_addr->sa_family == AF_INET) + { + char host[NI_MAXHOST]; + if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) + continue; + try + { + auto it = r.resolve({host, "30303"}); + bi::tcp::endpoint ep = it->endpoint(); + bi::address ad = ep.address(); + m_addresses.push_back(ad.to_v4()); + bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); + if (!isLocal) + m_peerAddresses.push_back(ad.to_v4()); + clog(NetNote) << "Address: " << host << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); + } + catch (...) + { + clog(NetNote) << "Couldn't resolve: " << host; + } + } + } + + freeifaddrs(ifaddr); +#endif +} + +std::map PeerHost::potentialPeers() +{ + std::map ret; + if (!m_public.address().is_unspecified()) + ret.insert(make_pair(m_key.pub(), m_public)); + Guard l(x_peers); + for (auto i: m_peers) + if (auto j = i.second.lock()) + { + auto ep = j->endpoint(); + // Skip peers with a listen port of zero or are on a private network + bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); + if (peerOnNet && ep.port() && j->m_id) + ret.insert(make_pair(i.first, ep)); + } + return ret; +} + +void PeerHost::ensureAccepting() +{ + if (m_accepting == false) + { + clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; + m_accepting = true; + m_acceptor.async_accept(m_socket, [=](boost::system::error_code ec) + { + if (!ec) + try + { + try { + clog(NetConnect) << "Accepted connection from " << m_socket.remote_endpoint(); + } catch (...){} + bi::address remoteAddress = m_socket.remote_endpoint().address(); + // Port defaults to 0 - we let the hello tell us which port the peer listens to + auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); + p->start(); + } + catch (std::exception const& _e) + { + clog(NetWarn) << "ERROR: " << _e.what(); + } + m_accepting = false; + if (ec.value() != 1) + ensureAccepting(); + }); + } +} + +void PeerHost::connect(std::string const& _addr, unsigned short _port) noexcept +{ + try + { + connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); + } + catch (exception const& e) + { + // Couldn't connect + clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; + } +} + +void PeerHost::connect(bi::tcp::endpoint const& _ep) +{ + clog(NetConnect) << "Attempting connection to " << _ep; + bi::tcp::socket* s = new bi::tcp::socket(m_ioService); + s->async_connect(_ep, [=](boost::system::error_code const& ec) + { + if (ec) + { + clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")"; + for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) + if (i->second.first == _ep && i->second.second < 3) + { + m_freePeers.push_back(i->first); + goto OK; + } + // for-else + clog(NetConnect) << "Giving up."; + OK:; + } + else + { + auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); + clog(NetConnect) << "Connected to " << _ep; + p->start(); + } + delete s; + }); +} + +bool PeerHost::havePeer(Public _id) const +{ + Guard l(x_peers); + + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); + + return !!m_peers.count(_id); +} + +void PeerHost::growPeers() +{ + Guard l(x_peers); + while (m_peers.size() < m_idealPeerCount) + { + if (m_freePeers.empty()) + { + if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) + { + RLPStream s; + bytes b; + (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + seal(b); + for (auto const& i: m_peers) + if (auto p = i.second.lock()) + if (p->isOpen()) + p->send(&b); + m_lastPeersRequest = chrono::steady_clock::now(); + } + + + if (!m_accepting) + ensureAccepting(); + + break; + } + + auto x = time(0) % m_freePeers.size(); + m_incomingPeers[m_freePeers[x]].second++; + connect(m_incomingPeers[m_freePeers[x]].first); + m_freePeers.erase(m_freePeers.begin() + x); + } +} + +void PeerHost::prunePeers() +{ + Guard l(x_peers); + // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. + for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) + while (m_peers.size() > m_idealPeerCount) + { + // look for worst peer to kick off + // first work out how many are old enough to kick off. + shared_ptr worst; + unsigned agedPeers = 0; + for (auto i: m_peers) + if (auto p = i.second.lock()) + if (/*(m_mode != NodeMode::PeerHost || p->m_caps != 0x01) &&*/ chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers. + { + ++agedPeers; + if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones + worst = p; + } + if (!worst || agedPeers <= m_idealPeerCount) + break; + worst->disconnect(TooManyPeers); + } + + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); +} + +std::vector PeerHost::peers(bool _updatePing) const +{ + Guard l(x_peers); + if (_updatePing) + const_cast(this)->pingAll(); + this_thread::sleep_for(chrono::milliseconds(200)); + std::vector ret; + for (auto& i: m_peers) + if (auto j = i.second.lock()) + if (j->m_socket.is_open()) + ret.push_back(j->m_info); + return ret; +} + +void PeerHost::pingAll() +{ + Guard l(x_peers); + for (auto& i: m_peers) + if (auto j = i.second.lock()) + j->ping(); +} + +bytes PeerHost::savePeers() const +{ + Guard l(x_peers); + RLPStream ret; + int n = 0; + for (auto& i: m_peers) + if (auto p = i.second.lock()) + if (p->m_socket.is_open() && p->endpoint().port()) + { + ret.appendList(3) << p->endpoint().address().to_v4().to_bytes() << p->endpoint().port() << p->m_id; + n++; + } + return RLPStream(n).appendRaw(ret.out(), n).out(); +} + +void PeerHost::restorePeers(bytesConstRef _b) +{ + for (auto i: RLP(_b)) + { + auto k = (Public)i[2]; + if (!m_incomingPeers.count(k)) + { + m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()), 0))); + m_freePeers.push_back(k); + } + } +} diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h new file mode 100644 index 000000000..b23b1d1ee --- /dev/null +++ b/libethnet/PeerHost.h @@ -0,0 +1,145 @@ +/* + 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 EthereumHost.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace eth +{ + +class RLPStream; +class TransactionQueue; +class BlockQueue; + +class PeerHost +{ + friend class PeerSession; + +public: + /// Start server, listening for connections on the given port. + PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, listening for connections on a system-assigned port. + PeerHost(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, but don't listen. + PeerHost(std::string const& _clientVersion, u256 _networkId); + + /// Will block on network process events. + virtual ~PeerHost(); + + /// Closes all peers. + void disconnectPeers(); + + virtual u256 networkId() { return m_networkId; } + virtual unsigned protocolVersion() { return 0; } + + /// Connect to a peer explicitly. + void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; + void connect(bi::tcp::endpoint const& _ep); + + /// Conduct I/O, polling, syncing, whatever. + /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. + /// This won't touch alter the blockchain. + void process() { if (isInitialised()) m_ioService.poll(); } + + /// @returns true iff we have the a peer of the given id. + bool havePeer(Public _id) const; + + /// Set ideal number of peers. + void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } + + /// Get peer information. + std::vector peers(bool _updatePing = false) const; + + /// Get number of peers connected; equivalent to, but faster than, peers().size(). + size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } + + /// Ping the peers, to update the latency information. + void pingAll(); + + /// Get the port we're listening on currently. + unsigned short listenPort() const { return m_public.port(); } + + /// Serialise the set of known peers. + bytes savePeers() const; + + /// Deserialise the data and populate the set of known peers. + void restorePeers(bytesConstRef _b); + + void registerPeer(std::shared_ptr _s); + +protected: + /// Called when the session has provided us with a new peer we can connect to. + void noteNewPeers() {} + + virtual bool isInitialised() const { return true; } + void seal(bytes& _b); + void populateAddresses(); + void determinePublic(std::string const& _publicAddress, bool _upnp); + void ensureAccepting(); + + void growPeers(); + void prunePeers(); + + std::map potentialPeers(); + + std::string m_clientVersion; + + unsigned short m_listenPort; + + ba::io_service m_ioService; + bi::tcp::acceptor m_acceptor; + bi::tcp::socket m_socket; + + UPnP* m_upnp = nullptr; + bi::tcp::endpoint m_public; + KeyPair m_key; + + u256 m_networkId; + + mutable std::mutex x_peers; + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + + mutable std::recursive_mutex m_incomingLock; + std::map> m_incomingPeers; + std::vector m_freePeers; + + std::chrono::steady_clock::time_point m_lastPeersRequest; + unsigned m_idealPeerCount = 5; + + std::vector m_addresses; + std::vector m_peerAddresses; + + bool m_accepting = false; +}; + +} diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp new file mode 100644 index 000000000..a08357dc0 --- /dev/null +++ b/libethnet/PeerSession.cpp @@ -0,0 +1,418 @@ +/* + 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 PeerSession.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "PeerSession.h" + +#include +#include +#include +#include "PeerHost.h" +using namespace std; +using namespace eth; + +#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " + +PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): + m_server(_s), + m_socket(std::move(_socket)), + m_reqNetworkId(_rNId), + m_listenPort(_peerPort), + m_rating(0) +{ + m_disconnect = std::chrono::steady_clock::time_point::max(); + m_connect = std::chrono::steady_clock::now(); + m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); +} + +PeerSession::~PeerSession() +{ + // Read-chain finished for one reason or another. + try + { + if (m_socket.is_open()) + m_socket.close(); + } + catch (...){} +} + +bi::tcp::endpoint PeerSession::endpoint() const +{ + if (m_socket.is_open()) + try + { + return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_listenPort); + } + catch (...){} + + return bi::tcp::endpoint(); +} + +bool PeerSession::preInterpret(RLP const& _r) +{ + clogS(NetRight) << _r; + switch (_r[0].toInt()) + { + case HelloPacket: + { + m_protocolVersion = _r[1].toInt(); + m_networkId = _r[2].toInt(); + auto clientVersion = _r[3].toString(); + m_caps = _r[4].toInt(); + m_listenPort = _r[5].toInt(); + m_id = _r[6].toHash(); + + clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + + if (m_server->havePeer(m_id)) + { + // Already connected. + cwarn << "Already have peer id" << m_id.abridged();// << "at" << l->endpoint() << "rather than" << endpoint(); + disconnect(DuplicatePeer); + return false; + } + + if (m_protocolVersion != m_server->protocolVersion() || m_networkId != m_server->networkId() || !m_id) + { + disconnect(IncompatibleProtocol); + return false; + } + try + { m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } + catch (...) + { + disconnect(BadProtocol); + return false; + } + + m_server->registerPeer(shared_from_this()); + onNewPeer(); + break; + } + case DisconnectPacket: + { + string reason = "Unspecified"; + if (_r[1].isInt()) + reason = reasonOf((DisconnectReason)_r[1].toInt()); + + clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; + if (m_socket.is_open()) + clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); + else + clogS(NetNote) << "Remote closed."; + m_socket.close(); + return false; + } + case PingPacket: + { + clogS(NetTriviaSummary) << "Ping"; + RLPStream s; + sealAndSend(prep(s).appendList(1) << PongPacket); + break; + } + case PongPacket: + m_info.lastPing = std::chrono::steady_clock::now() - m_ping; + clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast(m_info.lastPing).count() << " ms"; + break; + case GetPeersPacket: + { + clogS(NetTriviaSummary) << "GetPeers"; + auto peers = m_server->potentialPeers(); + RLPStream s; + prep(s).appendList(peers.size() + 1); + s << PeersPacket; + for (auto i: peers) + { + clogS(NetTriviaDetail) << "Sending peer " << i.first.abridged() << i.second; + s.appendList(3) << bytesConstRef(i.second.address().to_v4().to_bytes().data(), 4) << i.second.port() << i.first; + } + sealAndSend(s); + break; + } + case PeersPacket: + clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; + for (unsigned i = 1; i < _r.itemCount(); ++i) + { + bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); + auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); + Public id = _r[i][2].toHash(); + if (isPrivateAddress(peerAddress)) + goto CONTINUE; + + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; + + // check that it's not us or one we already know: + if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) + goto CONTINUE; + + // check that we're not already connected to addr: + if (!ep.port()) + goto CONTINUE; + for (auto i: m_server->m_addresses) + if (ep.address() == i && ep.port() == m_server->listenPort()) + goto CONTINUE; + for (auto i: m_server->m_incomingPeers) + if (i.second.first == ep) + goto CONTINUE; + m_server->m_incomingPeers[id] = make_pair(ep, 0); + m_server->m_freePeers.push_back(id); + m_server->noteNewPeers(); + clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; + CONTINUE:; + } + break; + default: + return interpret(_r); + } + return true; +} + +void PeerSession::ping() +{ + RLPStream s; + sealAndSend(prep(s).appendList(1) << PingPacket); + m_ping = std::chrono::steady_clock::now(); +} + +void PeerSession::getPeers() +{ + RLPStream s; + sealAndSend(prep(s).appendList(1) << GetPeersPacket); +} + +RLPStream& PeerSession::prep(RLPStream& _s) +{ + return _s.appendRaw(bytes(8, 0)); +} + +void PeerSession::sealAndSend(RLPStream& _s) +{ + bytes b; + _s.swapOut(b); + m_server->seal(b); + sendDestroy(b); +} + +bool PeerSession::checkPacket(bytesConstRef _msg) +{ + if (_msg.size() < 8) + return false; + if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) + return false; + uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; + if (_msg.size() != len + 8) + return false; + RLP r(_msg.cropped(8)); + if (r.actualSize() != len) + return false; + return true; +} + +void PeerSession::sendDestroy(bytes& _msg) +{ + clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); + + if (!checkPacket(bytesConstRef(&_msg))) + { + cwarn << "INVALID PACKET CONSTRUCTED!"; + } + + bytes buffer = bytes(std::move(_msg)); + writeImpl(buffer); +} + +void PeerSession::send(bytesConstRef _msg) +{ + clogS(NetLeft) << RLP(_msg.cropped(8)); + + if (!checkPacket(_msg)) + { + cwarn << "INVALID PACKET CONSTRUCTED!"; + } + + bytes buffer = bytes(_msg.toBytes()); + writeImpl(buffer); +} + +void PeerSession::writeImpl(bytes& _buffer) +{ +// cerr << (void*)this << " writeImpl" << endl; + if (!m_socket.is_open()) + return; + + lock_guard l(m_writeLock); + m_writeQueue.push_back(_buffer); + if (m_writeQueue.size() == 1) + write(); +} + +void PeerSession::write() +{ +// cerr << (void*)this << " write" << endl; + lock_guard l(m_writeLock); + if (m_writeQueue.empty()) + return; + + const bytes& bytes = m_writeQueue[0]; + auto self(shared_from_this()); + ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) + { +// cerr << (void*)this << " write.callback" << endl; + + // must check queue, as write callback can occur following dropped() + if (ec) + { + cwarn << "Error sending: " << ec.message(); + dropped(); + } + else + { + m_writeQueue.pop_front(); + write(); + } + }); +} + +void PeerSession::dropped() +{ +// cerr << (void*)this << " dropped" << endl; + if (m_socket.is_open()) + try + { + clogS(NetConnect) << "Closing " << m_socket.remote_endpoint(); + m_socket.close(); + } + catch (...) {} +} + +void PeerSession::disconnect(int _reason) +{ + clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; + if (m_socket.is_open()) + { + if (m_disconnect == chrono::steady_clock::time_point::max()) + { + RLPStream s; + prep(s); + s.appendList(2) << DisconnectPacket << _reason; + sealAndSend(s); + m_disconnect = chrono::steady_clock::now(); + } + else + dropped(); + } +} + +void PeerSession::start() +{ + RLPStream s; + prep(s); + s.appendList(9) << HelloPacket + << m_server->protocolVersion() + << m_server->networkId() + << m_server->m_clientVersion + << m_server->m_public.port() + << m_server->m_key.pub(); + sealAndSend(s); + ping(); + getPeers(); + + doRead(); +} + +void PeerSession::doRead() +{ + // ignore packets received while waiting to disconnect + if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) + return; + + auto self(shared_from_this()); + m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) + { + // If error is end of file, ignore + if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) + { + // got here with length of 1241... + cwarn << "Error reading: " << ec.message(); + dropped(); + } + else if (ec && length == 0) + { + return; + } + else + { + try + { + m_incoming.resize(m_incoming.size() + length); + memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); + while (m_incoming.size() > 8) + { + if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) + { + cwarn << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); + disconnect(BadProtocol); + return; + } + else + { + uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); + uint32_t tlen = len + 8; + if (m_incoming.size() < tlen) + break; + + // enough has come in. + auto data = bytesConstRef(m_incoming.data(), tlen); + if (!checkPacket(data)) + { + cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; + cwarn << "INVALID MESSAGE RECEIVED"; + disconnect(BadProtocol); + return; + } + else + { + RLP r(data.cropped(8)); + if (!preInterpret(r)) + { + // error + dropped(); + return; + } + } + memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); + m_incoming.resize(m_incoming.size() - tlen); + } + } + doRead(); + } + catch (Exception const& _e) + { + clogS(NetWarn) << "ERROR: " << _e.description(); + dropped(); + } + catch (std::exception const& _e) + { + clogS(NetWarn) << "ERROR: " << _e.what(); + dropped(); + } + } + }); +} diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h new file mode 100644 index 000000000..f2f4bced7 --- /dev/null +++ b/libethnet/PeerSession.h @@ -0,0 +1,105 @@ +/* + 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 PeerSession.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "Common.h" + +namespace eth +{ + +/** + * @brief The PeerSession class + * @todo Document fully. + */ +class PeerSession: public std::enable_shared_from_this +{ + friend class PeerHost; + +public: + PeerSession(PeerHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + virtual ~PeerSession(); + + void start(); + void disconnect(int _reason); + + void ping(); + + bool isOpen() const { return m_socket.is_open(); } + + bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. + +protected: + virtual bool interpret(RLP const& _r); + virtual void onNewPeer(); + + static RLPStream& prep(RLPStream& _s); + void sealAndSend(RLPStream& _s); + void sendDestroy(bytes& _msg); + void send(bytesConstRef _msg); + +private: + void dropped(); + void doRead(); + void doWrite(std::size_t length); + void writeImpl(bytes& _buffer); + void write(); + + void getPeers(); + bool preInterpret(RLP const& _r); + + /// @returns true iff the _msg forms a valid message for sending or receiving on the network. + static bool checkPacket(bytesConstRef _msg); + + PeerHost* m_server; + + std::recursive_mutex m_writeLock; + std::deque m_writeQueue; + + bi::tcp::socket m_socket; + std::array m_data; + PeerInfo m_info; + Public m_id; + + bytes m_incoming; + uint m_protocolVersion; + u256 m_networkId; + u256 m_reqNetworkId; + unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. + uint m_caps; + + std::chrono::steady_clock::time_point m_ping; + std::chrono::steady_clock::time_point m_connect; + std::chrono::steady_clock::time_point m_disconnect; + + uint m_rating; + + bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. +}; + +} diff --git a/libethnet/UPnP.cpp b/libethnet/UPnP.cpp new file mode 100644 index 000000000..427450e03 --- /dev/null +++ b/libethnet/UPnP.cpp @@ -0,0 +1,183 @@ +/* + 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 UPnP.cpp + * @authors: + * Gav Wood + * @date 2014 + */ + +#include "UPnP.h" + +#include +#include +#if ETH_MINIUPNPC +#include +#include +#include +#endif +#include +#include +#include +using namespace std; +using namespace eth; + +UPnP::UPnP() +{ +#if ETH_MINIUPNPC + m_urls.reset(new UPNPUrls); + m_data.reset(new IGDdatas); + + m_ok = false; + + struct UPNPDev* devlist; + struct UPNPDev* dev; + char* descXML; + int descXMLsize = 0; + int upnperror = 0; + memset(m_urls.get(), 0, sizeof(struct UPNPUrls)); + memset(m_data.get(), 0, sizeof(struct IGDdatas)); + devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, &upnperror); + if (devlist) + { + dev = devlist; + while (dev) + { + if (strstr (dev->st, "InternetGatewayDevice")) + break; + dev = dev->pNext; + } + if (!dev) + dev = devlist; /* defaulting to first device */ + + cnote << "UPnP device:" << dev->descURL << "[st:" << dev->st << "]"; +#if MINIUPNPC_API_VERSION >= 9 + descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0); +#else + descXML = (char*)miniwget(dev->descURL, &descXMLsize); +#endif + if (descXML) + { + parserootdesc (descXML, descXMLsize, m_data.get()); + free (descXML); descXML = 0; +#if MINIUPNPC_API_VERSION >= 9 + GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL, 0); +#else + GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL); +#endif + m_ok = true; + } + freeUPNPDevlist(devlist); + } + else +#endif + { + cnote << "UPnP device not found."; + throw NoUPnPDevice(); + } +} + +UPnP::~UPnP() +{ + auto r = m_reg; + for (auto i: r) + removeRedirect(i); +} + +string UPnP::externalIP() +{ +#if ETH_MINIUPNPC + char addr[16]; + if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr)) + return addr; + else +#endif + return "0.0.0.0"; +} + +int UPnP::addRedirect(char const* _addr, int _port) +{ + (void)_addr; + (void)_port; +#if ETH_MINIUPNPC + if (m_urls->controlURL[0] == '\0') + { + cwarn << "UPnP::addRedirect() called without proper initialisation?"; + return -1; + } + + // Try direct mapping first (port external, port internal). + char port_str[16]; + sprintf(port_str, "%d", _port); + if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL)) + return _port; + + // Failed - now try (random external, port internal) and cycle up to 10 times. + for (uint i = 0; i < 10; ++i) + { + _port = rand() % 65535 - 1024 + 1024; + sprintf(port_str, "%d", _port); + if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, NULL, port_str, _addr, "ethereum", "TCP", NULL, NULL)) + return _port; + } + + // Failed. Try asking the router to give us a free external port. + if (UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, NULL, _addr, "ethereum", "TCP", NULL, NULL)) + // Failed. Exit. + return 0; + + // We got mapped, but we don't know which ports we got mapped to. Now to find... + unsigned num = 0; + UPNP_GetPortMappingNumberOfEntries(m_urls->controlURL, m_data->first.servicetype, &num); + for (unsigned i = 0; i < num; ++i) + { + char extPort[16]; + char intClient[16]; + char intPort[6]; + char protocol[4]; + char desc[80]; + char enabled[4]; + char rHost[64]; + char duration[16]; + UPNP_GetGenericPortMappingEntry(m_urls->controlURL, m_data->first.servicetype, toString(i).c_str(), extPort, intClient, intPort, protocol, desc, enabled, rHost, duration); + if (string("ethereum") == desc) + { + m_reg.insert(atoi(extPort)); + return atoi(extPort); + } + } + cerr << "ERROR: Mapped port not found." << endl; +#endif + return 0; +} + +void UPnP::removeRedirect(int _port) +{ + (void)_port; +#if ETH_MINIUPNPC + char port_str[16]; +// int t; + printf("TB : upnp_rem_redir (%d)\n", _port); + if (m_urls->controlURL[0] == '\0') + { + printf("TB : the init was not done !\n"); + return; + } + sprintf(port_str, "%d", _port); + UPNP_DeletePortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, "TCP", NULL); + m_reg.erase(_port); +#endif +} diff --git a/libethnet/UPnP.h b/libethnet/UPnP.h new file mode 100644 index 000000000..836e350b0 --- /dev/null +++ b/libethnet/UPnP.h @@ -0,0 +1,53 @@ +/* + 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 UPnP.h + * @authors: + * Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include + +struct UPNPUrls; +struct IGDdatas; + +namespace eth +{ + +class UPnP +{ +public: + UPnP(); + ~UPnP(); + + std::string externalIP(); + int addRedirect(char const* addr, int port); + void removeRedirect(int port); + + bool isValid() const { return m_ok; } + + std::set m_reg; + bool m_ok; + std::shared_ptr m_urls; + std::shared_ptr m_data; +}; + +} diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index e744cd148..96b06ec89 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "QEthereum.h" using namespace std; diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 7b8e1505a..80ed891eb 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include "QmlEthereum.h" using namespace std; diff --git a/test/fork.cpp b/test/fork.cpp index 09a866fb1..f0edb702f 100644 --- a/test/fork.cpp +++ b/test/fork.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/test/network.cpp b/test/network.cpp index 2a1614187..978d68934 100644 --- a/test/network.cpp +++ b/test/network.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/test/peer.cpp b/test/peer.cpp index 7370df34b..63f2b0861 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include using namespace std; using namespace eth; using boost::asio::ip::tcp; @@ -49,7 +49,7 @@ int peerTest(int argc, char** argv) } BlockChain ch(boost::filesystem::temp_directory_path().string()); - PeerServer pn("Test", ch, 0, listenPort); + EthereumHost pn("Test", ch, 0, listenPort); if (!remoteHost.empty()) pn.connect(remoteHost, remotePort); diff --git a/test/txTest.cpp b/test/txTest.cpp index 53df93a61..314cf9644 100644 --- a/test/txTest.cpp +++ b/test/txTest.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 59423d83e..3bebdb7dc 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index dfebd7dcd..7b8873d45 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" From ffe228b385f9e2a72c25780a331473bdacec0b9e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 13:16:37 +0200 Subject: [PATCH 3/3] Blockchain syncing fixed. --- libethereum/BlockChain.cpp | 21 +++++++++++++++++---- libethereum/EthereumHost.cpp | 14 +++++++++++++- libethereum/EthereumSession.cpp | 12 ++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index f196d540d..d345271a9 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -333,16 +333,23 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const { + cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); + if (!_from || !_to) + { + return h256s(); + } h256s ret; h256s back; unsigned fn = details(_from).number; unsigned tn = details(_to).number; + cdebug << "treeRoute" << fn << "..." << tn; while (fn > tn) { if (_pre) ret.push_back(_from); _from = details(_from).parent; fn--; + cdebug << "from:" << fn << _from.abridged(); } while (fn < tn) { @@ -350,15 +357,21 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo back.push_back(_to); _to = details(_to).parent; tn--; + cdebug << "to:" << tn << _to.abridged(); } while (_from != _to) { + assert(_from); + assert(_to); + _from = details(_from).parent; + _to = details(_to).parent; if (_pre) - _from = details(_from).parent; + ret.push_back(_from); if (_post) - _to = details(_to).parent; - ret.push_back(_from); - back.push_back(_to); + back.push_back(_to); + fn--; + tn--; + cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); } if (o_common) *o_common = _from; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 9ed782800..beb7a6fbd 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -571,15 +571,27 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) { auto td = _from->m_totalDifficulty; + if (_from->m_neededBlocks.empty()) + return; + + clog(NetNote) << "Hash-chain COMPLETE:" << log2((double)_from->m_totalDifficulty) << "vs" << log2((double)m_chain->details().totalDifficulty) << "," << log2((double)m_totalDifficultyOfNeeded) << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain->details().totalDifficulty) + { + clog(NetNote) << "Difficulty of hashchain LOWER. Ignoring."; return; + } + + clog(NetNote) << "Difficulty of hashchain HIGHER. Replacing fetch queue."; + // Looks like it's the best yet for total difficulty. Set to download. { Guard l(x_blocksNeeded); m_blocksNeeded = _from->m_neededBlocks; + m_blocksOnWay.clear(); + m_totalDifficultyOfNeeded = td; } - // Looks like it's the best yet for total difficulty. Set to download. { Guard l(x_peers); for (auto const& i: m_peers) diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index 835509b63..fd0002967 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -56,8 +56,19 @@ EthereumSession::~EthereumSession() catch (...){} } +string toString(h256s const& _bs) +{ + ostringstream out; + out << "[ "; + for (auto i: _bs) + out << i.abridged() << ", "; + out << "]"; + return out.str(); +} + void EthereumSession::giveUpOnFetch() { + clogS(NetNote) << "GIVE UP FETCH; can't get " << toString(m_askedBlocks); if (m_askedBlocks.size()) { Guard l (m_server->x_blocksNeeded); @@ -329,6 +340,7 @@ bool EthereumSession::interpret(RLP const& _r) } clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; ensureGettingChain(); + break; } case GetTransactionsPacket: {