PeerFinder work

This commit is contained in:
Vinnie Falco
2013-09-30 09:35:29 -07:00
parent 41879d8511
commit 4fe63f9f0d
44 changed files with 2327 additions and 489 deletions

View File

@@ -0,0 +1,383 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
/*
PeerFinder
----------
Implements the logic for announcing and discovering IP addresses for
for connecting into the Ripple network.
Introduction
------------
Each Peer (a computer running rippled) on the Ripple network requires a certain
number of connections to other peers. These connections form an "overlay
network." When a new peer wants to join the network, they need a robust source
of network addresses (IP adresses) in order to establish outgoing connections.
Once they have joined the network, they need a method of announcing their
availaibility of accepting incoming connections.
The Ripple network, like all peer to peer networks, defines a "directed graph"
where each node represents a computer running the rippled software, and each
vertex indicates a network connection. The direction of the connection tells
us whether it is an outbound or inbound connection (from the perspective of
a particular node).
Fact #1:
The total inbound and outbound connections of any overlay must be equal.
This follows that for each node that has an established outbound connection,
there must exist another node that has received the corresponding inbound
connection.
When a new peer joins the network it may or may not wish to receive inbound
connections. Some peers are unable to accept incoming connections for various.
For security reasons they may be behind a firewall that blocks accept requests.
The administers may decide they don't want the connection traffic. Or they
may wish to connect only to specific peers. Or they may simply be misconfigured.
If a peer decides that it wishes to receive incoming connections, it needs
a method to announce its IP address and port number, the features that it
offers (for example, that it also services client requests), and the number
of available connection slots. This is to handle the case where the peer
reaches its desired number of peer connections, but may still want to inform
the network that it will service clients. It may also be desired to indicate
the number of free client slots.
Pong
----
Once a peer is connected to the network we need a way both to inform our
neighbors of our status with respect to accepting connections, and also to
learn about new fresh addresses to connect to. For this we will define the "Pong"
message.
"Connection Strategy"
---------------------
This is the overall strategy a peer uses to maintain its position in the Ripple
network graph
We define these values:
peerCount (calculated)
The number of currently connected and established peers
outCount (calculated)
The number of peers in PeerCount that are outbound connections.
MinOutCount (hard-coded constant)
The minimum number of OutCount we want. This also puts a floor
on PeerCount. This protects against sybil attacks and makes
sure that ledgers can get retrieved reliably.
10 is the proposed value.
MaxPeerCount (a constant set in the rippled.cfg)
The maximum number of peer connections, inbound or outbound,
that a peer wishes to maintain. Setting MaxPeerCount equal to
or below MinOutCount would disallow incoming connections.
OutPercent (a baked-in program constant for now)
The peer's target value for OutCount. When the value of OutCount
is below this number, the peer will employ the Outgoing Strategy
to raise its value of OutCount. This value is initially a constant
in the program, defined by the developers. However, it
may be changed through the consensus process.
15% is a proposed value.
However, lets consider the case where OutDesired is exactly equal to MaxPeerCount / 2.
In this case, a stable state will be reached when every peer is full, and
has exactly the same number of inbound and outbound connections. The problem
here is that there are now no available incoming connection slots. No new
peers can enter the network.
Lets consider the case where OutDesired is exactly equal to (MaxPeerCount / 2) - 1.
The stable state for this network (assuming all peers can accept incoming) will
leave us with network degree equal to MaxPeerCount - 2, with all peers having two
available incoming connection slots. The global number of incoming connection slots
will be equal to twice the number of nodes on the network. While this might seem to
be a desirable outcome, note that the connectedness (degree of the overlay) plays
a large part in determining the levels of traffic and ability to receive validations
from desired nodes. Having every node with available incoming connections also
means that entries in pong caches will continually fall out with new values and
information will become less useful.
For this reason, we advise that the value of OutDesired be fractional. Upon startup,
a node will use its node ID (its 160 bit unique ID) to decide whether to round the
value of OutDesired up or down. Using this method, we can precisely control the
global number of available incoming connection slots.
"Outgoing Strategy"
-------------------
This is the method a peer uses to establish outgoing connections into the
Ripple network.
A peer whose PeerCount is zero will use these steps:
1. Attempt addresses from a local database of addresses
2. Attempt addresses from a set of "well known" domains in rippled.cfg
This is the method used by a peer that is already connected to the Ripple network,
to adjust the number of outgoing connections it is maintaining.
"Incoming Strategy"
------------------------------
This is the method used by a peer to announce its ability and desire to receive
incoming connections both for the purpose of obtaining additional peer connections
and also for receiving requests from clients.
Terms
Overlay Network
http://en.wikipedia.org/wiki/Overlay_network
Directed Graph
http://en.wikipedia.org/wiki/Directed_graph
References:
Gnutella 0.6 Protocol
2.2.2 Ping (0x00)
2.2.3 Pong (0x01)
2.2.4 Use of Ping and Pong messages
2.2.4.1 A simple pong caching scheme
2.2.4.2 Other pong caching schemes
http://rfc-gnutella.sourceforge.net/src/rfc-0_6-draft.html
Revised Gnutella Ping Pong Scheme
By Christopher Rohrs and Vincent Falco
http://rfc-gnutella.sourceforge.net/src/pong-caching.html
*/
#include "Logic.h"
#include "StoreSqdb.h"
namespace ripple {
namespace PeerFinder {
class ManagerImp
: public Manager
, public Thread
, public DeadlineTimer::Listener
, public LeakChecked <ManagerImp>
{
public:
ServiceQueue m_queue;
Journal m_journal;
StoreSqdb m_store;
Logic m_logic;
DeadlineTimer m_connectTimer;
DeadlineTimer m_endpointsTimer;
RelativeTime m_whenStoreLegacyEndpoints;
//--------------------------------------------------------------------------
ManagerImp (Stoppable& stoppable, Callback& callback, Journal journal)
: Manager (stoppable)
, Thread ("PeerFinder")
, m_journal (journal)
, m_store (journal)
, m_logic (callback, m_store, journal)
, m_connectTimer (this)
, m_endpointsTimer (this)
{
#if BEAST_MSVC
if (beast_isRunningUnderDebugger())
{
m_journal.sink().set_console (true);
m_journal.sink().set_severity (Journal::kLowestSeverity);
}
#endif
}
~ManagerImp ()
{
}
//--------------------------------------------------------------------------
//
// PeerFinder
//
void setConfig (Config const& config)
{
m_queue.dispatch (bind (&Logic::setConfig, &m_logic, config));
}
void addStrings (std::string const& name,
std::vector <std::string> const& strings)
{
m_queue.dispatch (bind (&Logic::addStaticSource, &m_logic,
SourceStrings::New (name, strings)));
}
void addURL (std::string const& name, std::string const& url)
{
}
void onPeerConnected (PeerID const& id,
IPEndpoint const& address, bool incoming)
{
m_queue.dispatch (bind (&Logic::onPeerConnected, &m_logic,
id, address, incoming));
}
void onPeerDisconnected (const PeerID& id)
{
m_queue.dispatch (bind (&Logic::onPeerDisconnected, &m_logic, id));
}
void onPeerLegacyEndpoint (IPEndpoint const& ep)
{
m_queue.dispatch (bind (&Logic::onPeerLegacyEndpoint, &m_logic,
ep));
}
void onPeerEndpoints (PeerID const& id,
std::vector <Endpoint> const& endpoints)
{
m_queue.dispatch (beast::bind (&Logic::onPeerEndpoints, &m_logic,
id, endpoints));
}
//--------------------------------------------------------------------------
//
// Stoppable
//
void onPrepare (Journal journal)
{
}
void onStart (Journal journal)
{
startThread();
}
void onStop (Journal journal)
{
if (this->Thread::isThreadRunning ())
{
m_journal.debug << "Stopping";
m_connectTimer.cancel();
m_endpointsTimer.cancel();
m_queue.dispatch (bind (&Thread::signalThreadShouldExit, this));
}
else
{
stopped();
}
}
//--------------------------------------------------------------------------
void onDeadlineTimer (DeadlineTimer& timer)
{
if (timer == m_connectTimer)
{
m_queue.dispatch (bind (&Logic::makeOutgoingConnections, &m_logic));
m_connectTimer.setExpiration (secondsPerConnect);
}
else if (timer == m_endpointsTimer)
{
m_queue.dispatch (bind (&Logic::sendEndpoints, &m_logic));
m_endpointsTimer.setExpiration (secondsPerEndpoints);
}
}
// Checks to see if its time to update legacy endpoints
void storeLegacyEndpoints()
{
RelativeTime const now (RelativeTime::fromStartup());
if (now >= m_whenStoreLegacyEndpoints)
{
m_logic.storeLegacyEndpoints ();
m_whenStoreLegacyEndpoints = now
+ RelativeTime (legacyEndpointUpdateSeconds);
}
}
void init ()
{
m_journal.debug << "Initializing";
File const file (File::getSpecialLocation (
File::userDocumentsDirectory).getChildFile ("PeerFinder.sqlite"));
m_journal.debug << "Opening database at '" << file.getFullPathName() << "'";
Error error (m_store.open (file));
if (error)
{
m_journal.fatal <<
"Failed to open '" << file.getFullPathName() << "'";
}
if (! error)
{
m_logic.load ();
}
m_connectTimer.setExpiration (secondsPerConnect);
m_endpointsTimer.setExpiration (secondsPerEndpoints);
m_queue.post (bind (&Logic::makeOutgoingConnections, &m_logic));
}
void run ()
{
m_journal.debug << "Started";
m_whenStoreLegacyEndpoints = RelativeTime::fromStartup()
+ RelativeTime (legacyEndpointUpdateSeconds);
init ();
while (! this->threadShouldExit())
{
storeLegacyEndpoints();
m_queue.run_one();
}
stopped();
}
};
//------------------------------------------------------------------------------
Manager::Manager (Stoppable& parent)
: Stoppable ("PeerFinder", parent)
{
}
Manager* Manager::New (Stoppable& parent, Callback& callback, Journal journal)
{
return new ManagerImp (parent, callback, journal);
}
}
}