/* 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 #include #include #include "HostCapability.h" #include "Common.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; namespace dev { class RLPStream; namespace p2p { class Host; enum class Origin { Unknown, Self, SelfThird, PerfectThird, Perfect, }; struct Node { NodeId id; ///< Their id/public key. unsigned index; ///< Index into m_nodesList bi::tcp::endpoint address; ///< As reported from the node itself. int score = 0; ///< All time cumulative. int rating = 0; ///< Trending. bool dead = false; ///< If true, we believe this node is permanently dead - forget all about it. std::chrono::system_clock::time_point lastConnected; std::chrono::system_clock::time_point lastAttempted; unsigned failedAttempts = 0; DisconnectReason lastDisconnect = NoDisconnect; ///< Reason for disconnect that happened last. Origin idOrigin = Origin::Unknown; ///< How did we get to know this node's id? int secondsSinceLastConnected() const { return lastConnected == std::chrono::system_clock::time_point() ? -1 : (int)std::chrono::duration_cast(std::chrono::system_clock::now() - lastConnected).count(); } int secondsSinceLastAttempted() const { return lastAttempted == std::chrono::system_clock::time_point() ? -1 : (int)std::chrono::duration_cast(std::chrono::system_clock::now() - lastAttempted).count(); } unsigned fallbackSeconds() const; bool shouldReconnect() const; bool isOffline() const { return lastAttempted > lastConnected; } bool operator<(Node const& _n) const { if (isOffline() != _n.isOffline()) return isOffline(); else if (isOffline()) if (lastAttempted == _n.lastAttempted) return failedAttempts < _n.failedAttempts; else return lastAttempted < _n.lastAttempted; else if (score == _n.score) if (rating == _n.rating) return failedAttempts < _n.failedAttempts; else return rating < _n.rating; else return score < _n.score; } void connect(Host* _h); }; using Nodes = std::vector; struct NetworkPreferences { NetworkPreferences(unsigned short p = 30303, std::string i = std::string(), bool u = true, bool l = false): listenPort(p), publicIP(i), upnp(u), localNetworking(l) {} unsigned short listenPort = 30303; std::string publicIP; bool upnp = true; bool localNetworking = false; }; /** * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. */ class Host: public Worker { friend class Session; friend class HostCapabilityFace; friend struct Node; public: /// Start server, listening for connections on the given port. Host(std::string const& _clientVersion, NetworkPreferences const& _n = NetworkPreferences(), bool _start = false); /// Will block on network process events. virtual ~Host(); /// Closes all peers. void disconnectPeers(); /// Basic peer network protocol version. unsigned protocolVersion() const; /// Register a peer-capability; all new peer connections will have this capability. template std::shared_ptr registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; } bool haveCapability(CapDesc const& _name) const { return m_capabilities.count(_name) != 0; } CapDescs caps() const { CapDescs ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } } /// Connect to a peer explicitly. static std::string pocHost(); void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; void connect(bi::tcp::endpoint const& _ep); /// @returns true iff we have the a peer of the given id. bool havePeer(NodeId _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 { RecursiveGuard 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 saveNodes() const; /// Deserialise the data and populate the set of known peers. void restoreNodes(bytesConstRef _b); Nodes nodes() const { RecursiveGuard l(x_peers); Nodes ret; for (auto const& i: m_nodes) ret.push_back(*i.second); return ret; } void setNetworkPreferences(NetworkPreferences const& _p) { stop(); m_netPrefs = _p; start(); } void start(); void stop(); bool isStarted() const { return isWorking(); } NodeId id() const { return m_key.pub(); } void registerPeer(std::shared_ptr _s, CapDescs const& _caps); std::shared_ptr node(NodeId _id) const { if (m_nodes.count(_id)) return m_nodes.at(_id); return std::shared_ptr(); } private: void seal(bytes& _b); void populateAddresses(); void determinePublic(std::string const& _publicAddress, bool _upnp); void ensureAccepting(); void growPeers(); void prunePeers(); /// 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. virtual void doWork(); std::shared_ptr noteNode(NodeId _id, bi::tcp::endpoint _a, Origin _o, bool _ready, NodeId _oldId = h256()); Nodes potentialPeers(RangeMask const& _known); std::string m_clientVersion; ///< Our version string. NetworkPreferences m_netPrefs; ///< Network settings. static const int NetworkStopped = -1; ///< The value meaning we're not actually listening. int m_listenPort = NetworkStopped; ///< What port are we listening on? ba::io_service m_ioService; ///< IOService for network stuff. bi::tcp::acceptor m_acceptor; ///< Listening acceptor. bi::tcp::socket m_socket; ///< Listening socket. UPnP* m_upnp = nullptr; ///< UPnP helper. bi::tcp::endpoint m_public; ///< Our public listening endpoint. KeyPair m_key; ///< Our unique ID. bool m_hadNewNodes = false; mutable RecursiveMutex x_peers; /// The nodes to which we are currently connected. /// Mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::map> m_peers; /// Nodes to which we may connect (or to which we have connected). /// TODO: does this need a lock? std::map > m_nodes; /// A list of node IDs. This contains every index from m_nodes; the order is guaranteed to remain the same. std::vector m_nodesList; RangeMask m_ready; ///< Indices into m_nodesList over to which nodes we are not currently connected, connecting or otherwise ignoring. RangeMask m_private; ///< Indices into m_nodesList over to which nodes are private. unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to. // Our addresses. std::vector m_addresses; ///< Addresses for us. std::vector m_peerAddresses; ///< Addresses that peers (can) know us by. // Our capabilities. std::map> m_capabilities; ///< Each of the capabilities we support. std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers. bool m_accepting = false; }; } }