You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
11 KiB
301 lines
11 KiB
/*
|
|
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 <libdevcore/RangeMask.h>
|
|
#include <libdevcrypto/Common.h>
|
|
#include <libdevcrypto/ECDHE.h>
|
|
#include "NodeTable.h"
|
|
#include "HostCapability.h"
|
|
#include "Network.h"
|
|
#include "Peer.h"
|
|
#include "RLPxFrameIO.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;
|
|
};
|
|
|
|
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;
|
|
|
|
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.
|
|
};
|
|
|
|
/**
|
|
* @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()
|
|
);
|
|
|
|
/// Will block on network process events.
|
|
virtual ~Host();
|
|
|
|
/// Default host for current version of client.
|
|
static std::string pocHost();
|
|
|
|
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(T* _t) { _t->m_host = this; std::shared_ptr<T> ret(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; }
|
|
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 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; }
|
|
|
|
/// 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_netPrefs.listenIPAddress.empty() ? "0.0.0.0" : m_netPrefs.listenIPAddress; }
|
|
|
|
/// Get the port we're listening on currently.
|
|
unsigned short listenPort() const { return m_netPrefs.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 { return m_run && !!m_nodeTable; }
|
|
|
|
NodeId id() const { return m_alias.pub(); }
|
|
|
|
/// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error.
|
|
void startPeerSession(Public const& _id, RLP const& _hello, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint);
|
|
|
|
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 PeerSlotRatio { Egress = 2, Ingress = 9 };
|
|
|
|
bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; }
|
|
|
|
/// 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(PeerSlotRatio _type) { Guard l(x_pendingNodeConns); return peerCount() + m_pendingPeerConns.size() < _type * m_idealPeerCount; }
|
|
|
|
/// 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.
|
|
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.
|
|
|
|
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;
|
|
};
|
|
|
|
}
|
|
}
|
|
|