123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- /*
- This file is part of cpp-ethereum.
- cpp-ethereum is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- cpp-ethereum is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
- */
- /** @file Host.h
- * @author Alex Leverington <nessence@gmail.com>
- * @author Gav Wood <i@gavwood.com>
- * @date 2014
- */
- #pragma once
- #include <mutex>
- #include <map>
- #include <vector>
- #include <set>
- #include <memory>
- #include <utility>
- #include <thread>
- #include <chrono>
- #include <libdevcore/Guards.h>
- #include <libdevcore/Worker.h>
- #include <libdevcrypto/Common.h>
- #include <libdevcrypto/ECDHE.h>
- #include "NodeTable.h"
- #include "HostCapability.h"
- #include "Network.h"
- #include "Peer.h"
- #include "RLPXSocket.h"
- #include "RLPXFrameCoder.h"
- #include "Common.h"
- namespace ba = boost::asio;
- namespace bi = ba::ip;
- namespace std
- {
- template<> struct hash<pair<dev::p2p::NodeID, string>>
- {
- size_t operator()(pair<dev::p2p::NodeID, string> const& _value) const
- {
- size_t ret = hash<dev::p2p::NodeID>()(_value.first);
- return ret ^ (hash<string>()(_value.second) + 0x9e3779b9 + (ret << 6) + (ret >> 2));
- }
- };
- }
- namespace dev
- {
- namespace p2p
- {
- class Host;
- class HostNodeTableHandler: public NodeTableEventHandler
- {
- public:
- HostNodeTableHandler(Host& _host);
- Host const& host() const { return m_host; }
- private:
- virtual void processEvent(NodeID const& _n, NodeTableEventType const& _e);
- Host& m_host;
- };
- struct SubReputation
- {
- bool isRude = false;
- int utility = 0;
- bytes data;
- };
- struct Reputation
- {
- std::unordered_map<std::string, SubReputation> subs;
- };
- class ReputationManager
- {
- public:
- ReputationManager();
- void noteRude(Session const& _s, std::string const& _sub = std::string());
- bool isRude(Session const& _s, std::string const& _sub = std::string()) const;
- void setData(Session const& _s, std::string const& _sub, bytes const& _data);
- bytes data(Session const& _s, std::string const& _subs) const;
- private:
- std::unordered_map<std::pair<p2p::NodeID, std::string>, Reputation> m_nodes; ///< Nodes that were impolite while syncing. We avoid syncing from these if possible.
- SharedMutex mutable x_nodes;
- };
- struct NodeInfo
- {
- NodeInfo() = default;
- NodeInfo(NodeID const& _id, std::string const& _address, unsigned _port, std::string const& _version):
- id(_id), address(_address), port(_port), version(_version) {}
- std::string enode() const { return "enode://" + id.hex() + "@" + address + ":" + toString(port); }
- NodeID id;
- std::string address;
- unsigned port;
- std::string version;
- };
- /**
- * @brief The Host class
- * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe.
- *
- * @todo determinePublic: ipv6, udp
- * @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency
- */
- class Host: public Worker
- {
- friend class HostNodeTableHandler;
- friend class RLPXHandshake;
-
- friend class Session;
- friend class HostCapabilityFace;
- public:
- /// Start server, listening for connections on the given port.
- Host(
- std::string const& _clientVersion,
- NetworkPreferences const& _n = NetworkPreferences(),
- bytesConstRef _restoreNetwork = bytesConstRef()
- );
- /// Alternative constructor that allows providing the node key directly
- /// without restoring the network.
- Host(
- std::string const& _clientVersion,
- KeyPair const& _alias,
- NetworkPreferences const& _n = NetworkPreferences()
- );
- /// Will block on network process events.
- virtual ~Host();
- /// Default hosts for current version of client.
- static std::unordered_map<Public, std::string> const& pocHosts();
- /// Register a peer-capability; all new peer connections will have this capability.
- template <class T> std::shared_ptr<T> registerCapability(std::shared_ptr<T> const& _t) { _t->m_host = this; m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = _t; return _t; }
- template <class T> void addCapability(std::shared_ptr<T> const & _p, std::string const& _name, u256 const& _version) { m_capabilities[std::make_pair(_name, _version)] = _p; }
- 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 <class T> std::shared_ptr<T> cap() const { try { return std::static_pointer_cast<T>(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } }
- /// Add a potential peer.
- void addPeer(NodeSpec const& _s, PeerType _t);
- /// Add node as a peer candidate. Node is added if discovery ping is successful and table has capacity.
- void addNode(NodeID const& _node, NodeIPEndpoint const& _endpoint);
-
- /// Create Peer and attempt keeping peer connected.
- void requirePeer(NodeID const& _node, NodeIPEndpoint const& _endpoint);
- /// Create Peer and attempt keeping peer connected.
- void requirePeer(NodeID const& _node, bi::address const& _addr, unsigned short _udpPort, unsigned short _tcpPort) { requirePeer(_node, NodeIPEndpoint(_addr, _udpPort, _tcpPort)); }
- /// Note peer as no longer being required.
- void relinquishPeer(NodeID const& _node);
-
- /// Set ideal number of peers.
- void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; }
- /// Set multipier for max accepted connections.
- void setPeerStretch(unsigned _n) { m_stretchPeers = _n; }
-
- /// Get peer information.
- PeerSessionInfos peerSessionInfo() const;
- /// Get number of peers connected.
- size_t peerCount() const;
- /// Get the address we're listening on currently.
- std::string listenAddress() const { return m_tcpPublic.address().is_unspecified() ? "0.0.0.0" : m_tcpPublic.address().to_string(); }
- /// Get the port we're listening on currently.
- unsigned short listenPort() const { return std::max(0, m_listenPort); }
- /// Serialise the set of known peers.
- bytes saveNetwork() const;
- // TODO: P2P this should be combined with peers into a HostStat object of some kind; coalesce data, as it's only used for status information.
- Peers getPeers() const { RecursiveGuard l(x_sessions); Peers ret; for (auto const& i: m_peers) ret.push_back(*i.second); return ret; }
- NetworkPreferences const& networkPreferences() const { return m_netPrefs; }
- void setNetworkPreferences(NetworkPreferences const& _p, bool _dropPeers = false) { m_dropPeers = _dropPeers; auto had = isStarted(); if (had) stop(); m_netPrefs = _p; if (had) start(); }
- /// Start network. @threadsafe
- void start();
- /// Stop network. @threadsafe
- /// Resets acceptor, socket, and IO service. Called by deallocator.
- void stop();
- /// @returns if network has been started.
- bool isStarted() const { return isWorking(); }
- /// @returns our reputation manager.
- ReputationManager& repMan() { return m_repMan; }
- /// @returns if network is started and interactive.
- bool haveNetwork() const { Guard l(x_runTimer); return m_run && !!m_nodeTable; }
-
- /// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error.
- void startPeerSession(Public const& _id, RLP const& _hello, std::unique_ptr<RLPXFrameCoder>&& _io, std::shared_ptr<RLPXSocket> const& _s);
- /// Get session by id
- std::shared_ptr<Session> peerSession(NodeID const& _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? m_sessions[_id].lock() : std::shared_ptr<Session>(); }
- /// Get our current node ID.
- NodeID id() const { return m_alias.pub(); }
- /// Get the public TCP endpoint.
- bi::tcp::endpoint const& tcpPublic() const { return m_tcpPublic; }
- /// Get the public endpoint information.
- std::string enode() const { return "enode://" + id().hex() + "@" + (networkPreferences().publicIPAddress.empty() ? m_tcpPublic.address().to_string() : networkPreferences().publicIPAddress) + ":" + toString(m_tcpPublic.port()); }
- /// Get the node information.
- p2p::NodeInfo nodeInfo() const { return NodeInfo(id(), (networkPreferences().publicIPAddress.empty() ? m_tcpPublic.address().to_string() : networkPreferences().publicIPAddress), m_tcpPublic.port(), m_clientVersion); }
- protected:
- void onNodeTableEvent(NodeID const& _n, NodeTableEventType const& _e);
- /// Deserialise the data and populate the set of known peers.
- void restoreNetwork(bytesConstRef _b);
- private:
- enum PeerSlotType { Egress, Ingress };
-
- unsigned peerSlots(PeerSlotType _type) { return _type == Egress ? m_idealPeerCount : m_idealPeerCount * m_stretchPeers; }
-
- bool havePeerSession(NodeID const& _id) { return !!peerSession(_id); }
- /// Determines and sets m_tcpPublic to publicly advertised address.
- void determinePublic();
- void connect(std::shared_ptr<Peer> const& _p);
- /// Returns true if pending and connected peer count is less than maximum
- bool peerSlotsAvailable(PeerSlotType _type = Ingress) { Guard l(x_pendingNodeConns); return peerCount() + m_pendingPeerConns.size() < peerSlots(_type); }
-
- /// Ping the peers to update the latency information and disconnect peers which have timed out.
- void keepAlivePeers();
- /// Disconnect peers which didn't respond to keepAlivePeers ping prior to c_keepAliveTimeOut.
- void disconnectLatePeers();
- /// Called only from startedWorking().
- void runAcceptor();
- /// Called by Worker. Not thread-safe; to be called only by worker.
- virtual void startedWorking();
- /// Called by startedWorking. Not thread-safe; to be called only be Worker.
- void run(boost::system::error_code const& error); ///< Run network. Called serially via ASIO deadline timer. Manages connection state transitions.
- /// Run network. Not thread-safe; to be called only by worker.
- virtual void doWork();
- /// Shutdown network. Not thread-safe; to be called only by worker.
- virtual void doneWorking();
- /// Get or create host identifier (KeyPair).
- static KeyPair networkAlias(bytesConstRef _b);
- bytes m_restoreNetwork; ///< Set by constructor and used to set Host key and restore network peers & nodes.
- bool m_run = false; ///< Whether network is running.
- mutable std::mutex x_runTimer; ///< Start/stop mutex.
- std::string m_clientVersion; ///< Our version string.
- NetworkPreferences m_netPrefs; ///< Network settings.
- /// Interface addresses (private, public)
- std::set<bi::address> m_ifAddresses; ///< Interface addresses.
- int m_listenPort = -1; ///< What port are we listening on. -1 means binding failed or acceptor hasn't been initialized.
- ba::io_service m_ioService; ///< IOService for network stuff.
- bi::tcp::acceptor m_tcp4Acceptor; ///< Listening acceptor.
- std::unique_ptr<boost::asio::deadline_timer> m_timer; ///< Timer which, when network is running, calls scheduler() every c_timerInterval ms.
- static const unsigned c_timerInterval = 100; ///< Interval which m_timer is run when network is connected.
- std::set<Peer*> m_pendingPeerConns; /// Used only by connect(Peer&) to limit concurrently connecting to same node. See connect(shared_ptr<Peer>const&).
- Mutex x_pendingNodeConns;
- bi::tcp::endpoint m_tcpPublic; ///< Our public listening endpoint.
- KeyPair m_alias; ///< Alias for network communication. Network address is k*G. k is key material. TODO: Replace KeyPair.
- std::shared_ptr<NodeTable> m_nodeTable; ///< Node table (uses kademlia-like discovery).
- /// Shared storage of Peer objects. Peers are created or destroyed on demand by the Host. Active sessions maintain a shared_ptr to a Peer;
- std::unordered_map<NodeID, std::shared_ptr<Peer>> m_peers;
-
- /// Peers we try to connect regardless of p2p network.
- std::set<NodeID> m_requiredPeers;
- Mutex x_requiredPeers;
- /// The nodes to which we are currently connected. Used by host to service peer requests and keepAlivePeers and for shutdown. (see run())
- /// Mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method.
- mutable std::unordered_map<NodeID, std::weak_ptr<Session>> m_sessions;
- mutable RecursiveMutex x_sessions;
-
- std::list<std::weak_ptr<RLPXHandshake>> m_connecting; ///< Pending connections.
- Mutex x_connecting; ///< Mutex for m_connecting.
- unsigned m_idealPeerCount = 11; ///< Ideal number of peers to be connected to.
- unsigned m_stretchPeers = 7; ///< Accepted connection multiplier (max peers = ideal*stretch).
- std::map<CapDesc, std::shared_ptr<HostCapabilityFace>> m_capabilities; ///< Each of the capabilities we support.
-
- /// Deadline timers used for isolated network events. GC'd by run.
- std::list<std::shared_ptr<boost::asio::deadline_timer>> m_timers;
- Mutex x_timers;
- std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers.
- bool m_accepting = false;
- bool m_dropPeers = false;
- ReputationManager m_repMan;
- };
- }
- }
|